Update docs to put prepend as the last line
This updates the documentation for adding EE specific features to clarify that `prepend` should be on the last line, and how to deal with code that depends on class methods redefined in EE. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/53919
This commit is contained in:
parent
9cebb1e490
commit
bf91b06b1d
1 changed files with 124 additions and 29 deletions
|
@ -119,10 +119,20 @@ This also applies to views.
|
||||||
|
|
||||||
### EE features based on CE features
|
### EE features based on CE features
|
||||||
|
|
||||||
For features that build on existing CE features, write a module in the
|
For features that build on existing CE features, write a module in the `EE`
|
||||||
`EE` namespace and `prepend` it in the CE class. This makes conflicts
|
namespace and `prepend` it in the CE class, on the last line of the file that
|
||||||
less likely to happen during CE to EE merges because only one line is
|
the class resides in. This makes conflicts less likely to happen during CE to EE
|
||||||
added to the CE class - the `prepend` line.
|
merges because only one line is added to the CE class - the `prepend` line. For
|
||||||
|
example, to prepend a module into the `User` class you would use the following
|
||||||
|
approach:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class User < ActiveRecord::Base
|
||||||
|
# ... lots of code here ...
|
||||||
|
end
|
||||||
|
|
||||||
|
User.prepend(EE::User)
|
||||||
|
```
|
||||||
|
|
||||||
Since the module would require an `EE` namespace, the file should also be
|
Since the module would require an `EE` namespace, the file should also be
|
||||||
put in an `ee/` sub-directory. For example, we want to extend the user model
|
put in an `ee/` sub-directory. For example, we want to extend the user model
|
||||||
|
@ -231,7 +241,6 @@ the existing file:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
prepend EE::ApplicationController
|
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
def after_sign_out_path_for(resource)
|
def after_sign_out_path_for(resource)
|
||||||
|
@ -240,6 +249,8 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ApplicationController.prepend(EE::ApplicationController)
|
||||||
```
|
```
|
||||||
|
|
||||||
And create a new file in the `ee/` sub-directory with the altered
|
And create a new file in the `ee/` sub-directory with the altered
|
||||||
|
@ -533,8 +544,6 @@ module API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
prepend EE::API::MergeRequests
|
|
||||||
|
|
||||||
params :optional_params do
|
params :optional_params do
|
||||||
# CE specific params go here...
|
# CE specific params go here...
|
||||||
|
|
||||||
|
@ -542,6 +551,8 @@ module API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
API::MergeRequests.prepend(EE::API::MergeRequests)
|
||||||
```
|
```
|
||||||
|
|
||||||
And then we could override it in EE module:
|
And then we could override it in EE module:
|
||||||
|
@ -582,10 +593,10 @@ module API
|
||||||
authorize_read_builds!
|
authorize_read_builds!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
prepend EE::API::JobArtifacts
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
API::JobArtifacts.prepend(EE::API::JobArtifacts)
|
||||||
```
|
```
|
||||||
|
|
||||||
And then we can follow regular object-oriented practices to override it:
|
And then we can follow regular object-oriented practices to override it:
|
||||||
|
@ -626,8 +637,6 @@ module API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
prepend EE::API::MergeRequests
|
|
||||||
|
|
||||||
put ':id/merge_requests/:merge_request_iid/merge' do
|
put ':id/merge_requests/:merge_request_iid/merge' do
|
||||||
merge_request = find_project_merge_request(params[:merge_request_iid])
|
merge_request = find_project_merge_request(params[:merge_request_iid])
|
||||||
|
|
||||||
|
@ -639,6 +648,8 @@ module API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
API::MergeRequests.prepend(EE::API::MergeRequests)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `update_merge_request_ee` doesn't do anything in CE, but
|
Note that `update_merge_request_ee` doesn't do anything in CE, but
|
||||||
|
@ -676,27 +687,37 @@ or not we really need to extend it from EE. For now we're not using it much.
|
||||||
|
|
||||||
Sometimes we need to use different arguments for a particular API route, and we
|
Sometimes we need to use different arguments for a particular API route, and we
|
||||||
can't easily extend it with an EE module because Grape has different context in
|
can't easily extend it with an EE module because Grape has different context in
|
||||||
different blocks. In order to overcome this, we could use class methods from the
|
different blocks. In order to overcome this, we need to move the data to a class
|
||||||
API class.
|
method that resides in a separate module or class. This allows us to extend that
|
||||||
|
module or class before its data is used, without having to place a `prepend` in
|
||||||
|
the middle of CE code.
|
||||||
|
|
||||||
For example, in one place we need to pass an extra argument to
|
For example, in one place we need to pass an extra argument to
|
||||||
`at_least_one_of` so that the API could consider an EE-only argument as the
|
`at_least_one_of` so that the API could consider an EE-only argument as the
|
||||||
least argument. This is not quite beautiful but it's working:
|
least argument. We would approach this as follows:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
|
# api/merge_requests/parameters.rb
|
||||||
module API
|
module API
|
||||||
class MergeRequests < Grape::API
|
class MergeRequests < Grape::API
|
||||||
|
module Parameters
|
||||||
def self.update_params_at_least_one_of
|
def self.update_params_at_least_one_of
|
||||||
%i[
|
%i[
|
||||||
assignee_id
|
assignee_id
|
||||||
description
|
description
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
prepend EE::API::MergeRequests
|
API::MergeRequests::Parameters.prepend(EE::API::MergeRequests::Parameters)
|
||||||
|
|
||||||
|
# api/merge_requests.rb
|
||||||
|
module API
|
||||||
|
class MergeRequests < Grape::API
|
||||||
params do
|
params do
|
||||||
at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
|
at_least_one_of(*Parameters.update_params_at_least_one_of)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -708,6 +729,7 @@ And then we could easily extend that argument in the EE class method:
|
||||||
module EE
|
module EE
|
||||||
module API
|
module API
|
||||||
module MergeRequests
|
module MergeRequests
|
||||||
|
module Parameters
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
|
@ -722,12 +744,85 @@ module EE
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
It could be annoying if we need this for a lot of routes, but it might be the
|
It could be annoying if we need this for a lot of routes, but it might be the
|
||||||
simplest solution right now.
|
simplest solution right now.
|
||||||
|
|
||||||
|
This approach can also be used when models define validations that depend on
|
||||||
|
class methods. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# app/models/identity.rb
|
||||||
|
class Identity < ActiveRecord::Base
|
||||||
|
def self.uniqueness_scope
|
||||||
|
[:provider]
|
||||||
|
end
|
||||||
|
|
||||||
|
prepend EE::Identity
|
||||||
|
|
||||||
|
validates :extern_uid,
|
||||||
|
allow_blank: true,
|
||||||
|
uniqueness: { scope: uniqueness_scope, case_sensitive: false }
|
||||||
|
end
|
||||||
|
|
||||||
|
# ee/app/models/ee/identity.rb
|
||||||
|
module EE
|
||||||
|
module Identity
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
extend ::Gitlab::Utils::Override
|
||||||
|
|
||||||
|
def uniqueness_scope
|
||||||
|
[*super, :saml_provider_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of taking this approach, we would refactor our code into the following:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# ee/app/models/ee/identity/uniqueness_scopes.rb
|
||||||
|
module EE
|
||||||
|
module Identity
|
||||||
|
module UniquenessScopes
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
extend ::Gitlab::Utils::Override
|
||||||
|
|
||||||
|
def uniqueness_scope
|
||||||
|
[*super, :saml_provider_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# app/models/identity/uniqueness_scopes.rb
|
||||||
|
class Identity < ActiveRecord::Base
|
||||||
|
module UniquenessScopes
|
||||||
|
def self.uniqueness_scope
|
||||||
|
[:provider]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Identity::UniquenessScopes.prepend(EE::Identity::UniquenessScopes)
|
||||||
|
|
||||||
|
# app/models/identity.rb
|
||||||
|
class Identity < ActiveRecord::Base
|
||||||
|
validates :extern_uid,
|
||||||
|
allow_blank: true,
|
||||||
|
uniqueness: { scope: Identity::UniquenessScopes.scopes, case_sensitive: false }
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
### Code in `spec/`
|
### Code in `spec/`
|
||||||
|
|
||||||
When you're testing EE-only features, avoid adding examples to the
|
When you're testing EE-only features, avoid adding examples to the
|
||||||
|
|
Loading…
Reference in a new issue