2020-10-30 18:08:56 +00:00
---
stage: none
group: unassigned
2020-11-26 06:09:20 +00:00
info: 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
2020-10-30 18:08:56 +00:00
---
2016-02-17 03:09:00 +00:00
# Gotchas
The purpose of this guide is to document potential "gotchas" that contributors
might encounter or should avoid during development of GitLab CE and EE.
2020-10-02 18:08:56 +00:00
## Do not read files from app/assets directory
2020-10-15 00:08:42 +00:00
In GitLab 10.8 and later, Omnibus has [dropped the `app/assets` directory ](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2456 ),
2020-10-02 18:08:56 +00:00
after asset compilation. The `ee/app/assets` , `vendor/assets` directories are dropped as well.
2020-12-07 15:09:49 +00:00
This means that reading files from that directory fails in Omnibus-installed GitLab instances:
2020-10-02 18:08:56 +00:00
```ruby
file = Rails.root.join('app/assets/images/logo.svg')
# This file does not exist, read will fail with:
# Errno::ENOENT: No such file or directory @ rb_sysopen
File.read(file)
```
2017-02-16 13:42:40 +00:00
## Do not assert against the absolute value of a sequence-generated attribute
2016-11-10 14:24:23 +00:00
Consider the following factory:
```ruby
2017-12-14 00:13:44 +00:00
FactoryBot.define do
2016-11-10 14:24:23 +00:00
factory :label do
sequence(:title) { |n| "label#{n}" }
end
end
```
Consider the following API spec:
```ruby
2019-08-29 07:56:52 +00:00
require 'spec_helper'
2016-11-10 14:24:23 +00:00
2020-06-22 21:08:42 +00:00
RSpec.describe API::Labels do
2016-11-10 14:24:23 +00:00
it 'creates a first label' do
create(:label)
get api("/projects/#{project.id}/labels", user)
2020-01-15 18:08:34 +00:00
expect(response).to have_gitlab_http_status(:ok)
2016-11-10 14:24:23 +00:00
expect(json_response.first['name']).to eq('label1')
end
it 'creates a second label' do
create(:label)
get api("/projects/#{project.id}/labels", user)
2020-01-15 18:08:34 +00:00
expect(response).to have_gitlab_http_status(:ok)
2016-11-10 14:24:23 +00:00
expect(json_response.first['name']).to eq('label1')
end
end
```
When run, this spec doesn't do what we might expect:
2020-01-30 15:09:15 +00:00
```shell
2016-11-10 14:24:23 +00:00
1) API::API reproduce sequence issue creates a second label
Failure/Error: expect(json_response.first['name']).to eq('label1')
expected: "label1"
got: "label2"
(compared using ==)
```
2019-07-19 20:27:05 +00:00
This is because FactoryBot sequences are not reset for each example.
2016-11-10 14:24:23 +00:00
Please remember that sequence-generated values exist only to avoid having to
explicitly set attributes that have a uniqueness constraint when using a factory.
### Solution
If you assert against a sequence-generated attribute's value, you should set it
explicitly. Also, the value you set shouldn't match the sequence pattern.
For instance, using our `:label` factory, writing `create(:label, title: 'foo')`
is ok, but `create(:label, title: 'label1')` is not.
Following is the fixed API spec:
```ruby
2019-08-29 07:56:52 +00:00
require 'spec_helper'
2016-11-10 14:24:23 +00:00
2020-06-22 21:08:42 +00:00
RSpec.describe API::Labels do
2016-11-10 14:24:23 +00:00
it 'creates a first label' do
create(:label, title: 'foo')
get api("/projects/#{project.id}/labels", user)
2020-01-15 18:08:34 +00:00
expect(response).to have_gitlab_http_status(:ok)
2016-11-10 14:24:23 +00:00
expect(json_response.first['name']).to eq('foo')
end
it 'creates a second label' do
create(:label, title: 'bar')
get api("/projects/#{project.id}/labels", user)
2020-01-15 18:08:34 +00:00
expect(response).to have_gitlab_http_status(:ok)
2016-11-10 14:24:23 +00:00
expect(json_response.first['name']).to eq('bar')
end
end
```
2018-12-03 13:12:53 +00:00
## Avoid using `expect_any_instance_of` or `allow_any_instance_of` in RSpec
2018-06-20 13:29:15 +00:00
### Why
2018-11-13 06:07:16 +00:00
- Because it is not isolated therefore it might be broken at times.
- Because it doesn't work whenever the method we want to stub was defined
2018-06-20 13:29:15 +00:00
in a prepended module, which is very likely the case in EE. We could see
error like this:
2020-03-25 06:07:58 +00:00
```plaintext
2019-07-09 07:16:17 +00:00
1.1) Failure/Error: expect_any_instance_of(ApplicationSetting).to receive_messages(messages)
Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported.
```
2018-06-20 13:29:15 +00:00
2020-09-01 09:10:28 +00:00
### Alternative: `expect_next_instance_of`, `allow_next_instance_of`, `expect_next_found_instance_of` or `allow_next_found_instance_of`
2018-06-20 13:29:15 +00:00
Instead of writing:
```ruby
# Don't do this:
2018-12-03 13:12:53 +00:00
expect_any_instance_of(Project).to receive(:add_import_job)
2019-10-11 21:05:59 +00:00
2019-10-15 18:06:01 +00:00
# Don't do this:
2019-10-11 21:05:59 +00:00
allow_any_instance_of(Project).to receive(:add_import_job)
2018-06-20 13:29:15 +00:00
```
We could write:
```ruby
# Do this:
expect_next_instance_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
2019-10-11 21:05:59 +00:00
2019-10-15 18:06:01 +00:00
# Do this:
2019-10-11 21:05:59 +00:00
allow_next_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
2020-09-01 09:10:28 +00:00
# Do this:
expect_next_found_instance_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
# Do this:
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
2018-06-20 13:29:15 +00:00
```
2020-11-04 00:09:12 +00:00
Since Active Record is not calling the `.new` method on model classes to instantiate the objects,
2020-09-01 09:10:28 +00:00
you 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._
2022-02-23 18:16:59 +00:00
It is also possible to set mocks and expectations for multiple instances of the same Active Record model by using the `expect_next_found_(number)_instances_of` and `allow_next_found_(number)_instances_of` helpers, like so;
```ruby
expect_next_found_2_instances_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
allow_next_found_2_instances_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
```
2019-10-15 18:06:01 +00:00
If we also want to initialize the instance with some particular arguments, we
2019-10-11 21:05:59 +00:00
could also pass it like:
2018-06-20 13:29:15 +00:00
```ruby
# Do this:
expect_next_instance_of(MergeRequests::RefreshService, project, user) do |refresh_service|
expect(refresh_service).to receive(:execute).with(oldrev, newrev, ref)
end
```
This would expect the following:
```ruby
# Above expects:
refresh_service = MergeRequests::RefreshService.new(project, user)
refresh_service.execute(oldrev, newrev, ref)
```
2017-02-16 13:42:40 +00:00
## Do not `rescue Exception`
2016-02-17 03:09:00 +00:00
2019-09-27 12:06:07 +00:00
See ["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 ).
2016-02-17 03:09:00 +00:00
2022-08-08 03:09:21 +00:00
This rule is [enforced automatically by RuboCop ](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914 )._
2016-02-17 03:09:00 +00:00
2017-02-16 13:42:40 +00:00
## Do not use inline JavaScript in views
2016-02-17 03:09:00 +00:00
2016-10-27 17:09:58 +00:00
Using the inline `:javascript` Haml filters comes with a
2016-08-04 10:30:27 +00:00
performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
2016-02-17 03:09:00 +00:00
2021-06-08 18:10:23 +00:00
We've [removed these two filters ](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/initializers/hamlit.rb )
2020-11-04 00:09:12 +00:00
in an initializer.
2016-02-17 03:09:00 +00:00
### Further reading
2019-09-27 12:06:07 +00:00
- Stack Overflow: [Why you should not write inline JavaScript ](https://softwareengineering.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting )
2019-11-16 00:06:14 +00:00
2020-07-24 18:09:45 +00:00
## Storing assets that do not require pre-compiling
Assets 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.
However, 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 ).
```ruby
support_bot = User.support_bot
# accessing a file from the `app/assets` folder
support_bot.avatar = Rails.root.join('app', 'assets', 'images', 'bot_avatars', 'support_bot.png').open
support_bot.save!
```
While the code above works in local environments, it errors out in production installations as the `app/assets` folder is not included.
### Solution
2020-12-11 06:10:17 +00:00
The alternative is the `lib/assets` folder. Use it if you need to add assets (like images) to the repository that meet the following conditions:
2020-07-24 18:09:45 +00:00
- The assets do not need to be directly served to the user (and hence need not be pre-compiled).
- The assets do need to be accessed via application code.
In short:
Use `app/assets` for storing any asset that needs to be precompiled and served to the end user.
Use `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.
MR for reference: [!37671 ](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37671 )