debian-mirror-gitlab/doc/development/gotchas.md

241 lines
6.8 KiB
Markdown
Raw Normal View History

2016-06-02 11:05:42 +05:30
# 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.
2017-08-17 22:00:37 +05:30
## Do not assert against the absolute value of a sequence-generated attribute
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
Consider the following factory:
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
```ruby
2018-03-17 18:26:18 +05:30
FactoryBot.define do
2017-08-17 22:00:37 +05:30
factory :label do
sequence(:title) { |n| "label#{n}" }
end
end
```
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
Consider the following API spec:
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
```ruby
2019-12-04 20:38:33 +05:30
require 'spec_helper'
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
describe API::Labels do
it 'creates a first label' do
create(:label)
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
get api("/projects/#{project.id}/labels", user)
2016-06-02 11:05:42 +05:30
2020-03-09 13:42:32 +05:30
expect(response).to have_gitlab_http_status(:ok)
2017-08-17 22:00:37 +05:30
expect(json_response.first['name']).to eq('label1')
end
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
it 'creates a second label' do
create(:label)
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
get api("/projects/#{project.id}/labels", user)
2016-06-02 11:05:42 +05:30
2020-03-09 13:42:32 +05:30
expect(response).to have_gitlab_http_status(:ok)
2017-08-17 22:00:37 +05:30
expect(json_response.first['name']).to eq('label1')
end
end
2016-06-02 11:05:42 +05:30
```
2017-08-17 22:00:37 +05:30
When run, this spec doesn't do what we might expect:
2016-06-02 11:05:42 +05:30
2020-03-09 13:42:32 +05:30
```shell
2017-08-17 22:00:37 +05:30
1) API::API reproduce sequence issue creates a second label
Failure/Error: expect(json_response.first['name']).to eq('label1')
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
expected: "label1"
got: "label2"
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
(compared using ==)
2016-06-02 11:05:42 +05:30
```
2019-10-12 21:52:04 +05:30
This is because FactoryBot sequences are not reset for each example.
2017-08-17 22:00:37 +05:30
Please remember that sequence-generated values exist only to avoid having to
explicitly set attributes that have a uniqueness constraint when using a factory.
2016-06-02 11:05:42 +05:30
### Solution
2017-08-17 22:00:37 +05:30
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:
2016-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
```ruby
2019-12-04 20:38:33 +05:30
require 'spec_helper'
2017-08-17 22:00:37 +05:30
describe API::Labels do
it 'creates a first label' do
create(:label, title: 'foo')
get api("/projects/#{project.id}/labels", user)
2020-03-09 13:42:32 +05:30
expect(response).to have_gitlab_http_status(:ok)
2017-08-17 22:00:37 +05:30
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-03-09 13:42:32 +05:30
expect(response).to have_gitlab_http_status(:ok)
2017-08-17 22:00:37 +05:30
expect(json_response.first['name']).to eq('bar')
end
end
2016-06-02 11:05:42 +05:30
```
2019-02-15 15:39:39 +05:30
## Avoid using `expect_any_instance_of` or `allow_any_instance_of` in RSpec
2018-11-08 19:23:39 +05:30
### Why
2019-03-02 22:35:43 +05:30
- 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-11-08 19:23:39 +05:30
in a prepended module, which is very likely the case in EE. We could see
error like this:
2019-09-30 21:07:59 +05:30
```
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-11-08 19:23:39 +05:30
2019-12-21 20:55:43 +05:30
### Alternative: `expect_next_instance_of` or `allow_next_instance_of`
2018-11-08 19:23:39 +05:30
Instead of writing:
```ruby
# Don't do this:
2019-02-15 15:39:39 +05:30
expect_any_instance_of(Project).to receive(:add_import_job)
2019-12-21 20:55:43 +05:30
# Don't do this:
allow_any_instance_of(Project).to receive(:add_import_job)
2018-11-08 19:23:39 +05:30
```
We could write:
```ruby
# Do this:
expect_next_instance_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
2019-12-21 20:55:43 +05:30
# Do this:
allow_next_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
2018-11-08 19:23:39 +05:30
```
2019-12-21 20:55:43 +05:30
If we also want to initialize the instance with some particular arguments, we
could also pass it like:
2018-11-08 19:23:39 +05:30
```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-08-17 22:00:37 +05:30
## Do not `rescue Exception`
2019-12-21 20:55:43 +05:30
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).
2017-08-17 22:00:37 +05:30
_**Note:** This rule is [enforced automatically by
2020-01-01 13:55:28 +05:30
Rubocop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._
2017-08-17 22:00:37 +05:30
## Do not use inline JavaScript in views
Using the inline `:javascript` Haml filters comes with a
performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
2019-12-21 20:55:43 +05:30
_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab/blob/master/config/initializers/hamlit.rb)
2017-08-17 22:00:37 +05:30
in an initializer._
2016-06-02 11:05:42 +05:30
### Further reading
2019-12-21 20:55:43 +05:30
- Stack Overflow: [Why you should not write inline JavaScript](https://softwareengineering.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting)
2019-12-26 22:10:19 +05:30
## Auto loading
Rails auto-loading on `development` differs from the load policy in the `production` environment.
In development mode, `config.eager_load` is set to `false`, which means classes
are loaded as needed. With the classic Rails autoloader, it is known that this can lead to
[Rails resolving the wrong class](https://guides.rubyonrails.org/v5.2/autoloading_and_reloading_constants.html#when-constants-aren-t-missed-relative-references)
if the class name is ambiguous. This can be fixed by specifying the complete namespace to the class.
### Error prone example
```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
end
# app/controllers/projects/application_controller.rb
class Projects::ApplicationController < ApplicationController
...
private
def project
...
end
end
# app/controllers/projects/submodule/some_controller.rb
module Projects
module Submodule
class SomeController < ApplicationController
def index
@some_id = project.id
end
end
end
end
```
In this case, if for any reason the top level `ApplicationController`
is loaded but `Projects::ApplicationController` is not, `ApplicationController`
would be resolved to `::ApplicationController` and then the `project` method will
be undefined and we will get an error.
#### Solution
```ruby
# app/controllers/projects/submodule/some_controller.rb
module Projects
module Submodule
class SomeController < Projects::ApplicationController
def index
@some_id = project.id
end
end
end
end
```
By specifying `Projects::`, we tell Rails exactly what class we are referring
to and we would avoid the issue.
NOTE: **Note:**
This problem will disappear as soon as we upgrade to Rails 6 and use the Zeitwerk autoloader.
### Further reading
- Rails Guides: [Autoloading and Reloading Constants (Classic Mode)](https://guides.rubyonrails.org/autoloading_and_reloading_constants_classic_mode.html)
- Ruby Constant lookup: [Everything you ever wanted to know about constant lookup in Ruby](http://cirw.in/blog/constant-lookup)
- Rails 6 and Zeitwerk autoloader: [Understanding Zeitwerk in Rails 6](https://medium.com/cedarcode/understanding-zeitwerk-in-rails-6-f168a9f09a1f)