1--- 2stage: none 3group: unassigned 4info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments 5--- 6 7# Gotchas 8 9The purpose of this guide is to document potential "gotchas" that contributors 10might encounter or should avoid during development of GitLab CE and EE. 11 12## Do not read files from app/assets directory 13 14In GitLab 10.8 and later, Omnibus has [dropped the `app/assets` directory](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2456), 15after asset compilation. The `ee/app/assets`, `vendor/assets` directories are dropped as well. 16 17This means that reading files from that directory fails in Omnibus-installed GitLab instances: 18 19```ruby 20file = Rails.root.join('app/assets/images/logo.svg') 21 22# This file does not exist, read will fail with: 23# Errno::ENOENT: No such file or directory @ rb_sysopen 24File.read(file) 25``` 26 27## Do not assert against the absolute value of a sequence-generated attribute 28 29Consider the following factory: 30 31```ruby 32FactoryBot.define do 33 factory :label do 34 sequence(:title) { |n| "label#{n}" } 35 end 36end 37``` 38 39Consider the following API spec: 40 41```ruby 42require 'spec_helper' 43 44RSpec.describe API::Labels do 45 it 'creates a first label' do 46 create(:label) 47 48 get api("/projects/#{project.id}/labels", user) 49 50 expect(response).to have_gitlab_http_status(:ok) 51 expect(json_response.first['name']).to eq('label1') 52 end 53 54 it 'creates a second label' do 55 create(:label) 56 57 get api("/projects/#{project.id}/labels", user) 58 59 expect(response).to have_gitlab_http_status(:ok) 60 expect(json_response.first['name']).to eq('label1') 61 end 62end 63``` 64 65When run, this spec doesn't do what we might expect: 66 67```shell 681) API::API reproduce sequence issue creates a second label 69 Failure/Error: expect(json_response.first['name']).to eq('label1') 70 71 expected: "label1" 72 got: "label2" 73 74 (compared using ==) 75``` 76 77This is because FactoryBot sequences are not reset for each example. 78 79Please remember that sequence-generated values exist only to avoid having to 80explicitly set attributes that have a uniqueness constraint when using a factory. 81 82### Solution 83 84If you assert against a sequence-generated attribute's value, you should set it 85explicitly. Also, the value you set shouldn't match the sequence pattern. 86 87For instance, using our `:label` factory, writing `create(:label, title: 'foo')` 88is ok, but `create(:label, title: 'label1')` is not. 89 90Following is the fixed API spec: 91 92```ruby 93require 'spec_helper' 94 95RSpec.describe API::Labels do 96 it 'creates a first label' do 97 create(:label, title: 'foo') 98 99 get api("/projects/#{project.id}/labels", user) 100 101 expect(response).to have_gitlab_http_status(:ok) 102 expect(json_response.first['name']).to eq('foo') 103 end 104 105 it 'creates a second label' do 106 create(:label, title: 'bar') 107 108 get api("/projects/#{project.id}/labels", user) 109 110 expect(response).to have_gitlab_http_status(:ok) 111 expect(json_response.first['name']).to eq('bar') 112 end 113end 114``` 115 116## Avoid using `expect_any_instance_of` or `allow_any_instance_of` in RSpec 117 118### Why 119 120- Because it is not isolated therefore it might be broken at times. 121- Because it doesn't work whenever the method we want to stub was defined 122 in a prepended module, which is very likely the case in EE. We could see 123 error like this: 124 125 ```plaintext 126 1.1) Failure/Error: expect_any_instance_of(ApplicationSetting).to receive_messages(messages) 127 Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported. 128 ``` 129 130### Alternative: `expect_next_instance_of`, `allow_next_instance_of`, `expect_next_found_instance_of` or `allow_next_found_instance_of` 131 132Instead of writing: 133 134```ruby 135# Don't do this: 136expect_any_instance_of(Project).to receive(:add_import_job) 137 138# Don't do this: 139allow_any_instance_of(Project).to receive(:add_import_job) 140``` 141 142We could write: 143 144```ruby 145# Do this: 146expect_next_instance_of(Project) do |project| 147 expect(project).to receive(:add_import_job) 148end 149 150# Do this: 151allow_next_instance_of(Project) do |project| 152 allow(project).to receive(:add_import_job) 153end 154 155# Do this: 156expect_next_found_instance_of(Project) do |project| 157 expect(project).to receive(:add_import_job) 158end 159 160# Do this: 161allow_next_found_instance_of(Project) do |project| 162 allow(project).to receive(:add_import_job) 163end 164``` 165 166Since Active Record is not calling the `.new` method on model classes to instantiate the objects, 167you should use `expect_next_found_instance_of` or `allow_next_found_instance_of` mock helpers to setup mock on objects returned by Active Record query & finder methods._ 168 169If we also want to initialize the instance with some particular arguments, we 170could also pass it like: 171 172```ruby 173# Do this: 174expect_next_instance_of(MergeRequests::RefreshService, project, user) do |refresh_service| 175 expect(refresh_service).to receive(:execute).with(oldrev, newrev, ref) 176end 177``` 178 179This would expect the following: 180 181```ruby 182# Above expects: 183refresh_service = MergeRequests::RefreshService.new(project, user) 184refresh_service.execute(oldrev, newrev, ref) 185``` 186 187## Do not `rescue Exception` 188 189See ["Why is it bad style to `rescue Exception => e` in Ruby?"](https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby). 190 191This rule is [enforced automatically by 192RuboCop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._ 193 194## Do not use inline JavaScript in views 195 196Using the inline `:javascript` Haml filters comes with a 197performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided. 198 199We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/initializers/hamlit.rb) 200in an initializer. 201 202### Further reading 203 204- Stack Overflow: [Why you should not write inline JavaScript](https://softwareengineering.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting) 205 206## Storing assets that do not require pre-compiling 207 208Assets that need to be served to the user are stored under the `app/assets` directory, which is later pre-compiled and placed in the `public/` directory. 209 210However, you cannot access the content of any file from within `app/assets` from the application code, as we do not include that folder in production installations as a [space saving measure](https://gitlab.com/gitlab-org/omnibus-gitlab/-/commit/ca049f990b223f5e1e412830510a7516222810be). 211 212```ruby 213support_bot = User.support_bot 214 215# accessing a file from the `app/assets` folder 216support_bot.avatar = Rails.root.join('app', 'assets', 'images', 'bot_avatars', 'support_bot.png').open 217 218support_bot.save! 219``` 220 221While the code above works in local environments, it errors out in production installations as the `app/assets` folder is not included. 222 223### Solution 224 225The alternative is the `lib/assets` folder. Use it if you need to add assets (like images) to the repository that meet the following conditions: 226 227- The assets do not need to be directly served to the user (and hence need not be pre-compiled). 228- The assets do need to be accessed via application code. 229 230In short: 231 232Use `app/assets` for storing any asset that needs to be precompiled and served to the end user. 233Use `lib/assets` for storing any asset that does not need to be served to the end user directly, but is still required to be accessed by the application code. 234 235MR for reference: [!37671](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37671) 236