简体   繁体   中英

Rails: How to override or make private a given scope from an included Concern?

I have a concern that adds a scope to ActiveRecord classes that include it. Most of the time its fine the way it is but depending on the class there might extra criteria that needs to be met. I don't want to have to rewrite the scope in the model but be able to just add on an extra criteria method.

Here's what I'm trying to do:

module MyConcern
  extend ActiveSupport::Concern

  included do
    # I've included the fact I pass a limit variable here because my scope also does this, in case that's relevant for solving this.
    # I'd like this to be private or not accessible via `MyModel._my_scope`
    scope :_my_scope, ->(limit = nil) { (joins, wheres, order etc etc).limit(limit) }
    scope :my_scope, ->(limit = nil) { _my_scope(limit) }
  end
end

class MyModel < ApplicationRecord
  include MyConcern

  # Including the "private" scope to add my extra criteria.
  scope :my_scope, ->(limit = nil) { _my_scope(limit).where(foo: 'bar') }
end

class AnotherModel < ApplicationRecord
  include MyConcern

  # I like the default behavior here so nothing else to do
end

This works but from outside the class you can do this: MyModel._my_scope which I guess is ok - maybe theres a time when I'd want the default behavior - but in this case I dont think I do and I feel like encapsulating _my_scope is the way to go.

I'm assuming it's possible to make a private class method in MyConcern that gets included in MyModel but that doesn't seem to work and I'm not really a Concern /mixin master yet so not sure how to go about doing this. Also, are Concerns considered to be mixins? Is there a difference? That would be good to know also.

You can achieve the same functionality of scopes with class methods, that you can inherit and extend for this case. It's not much different than your implementation; just avoids the use of the extra _ method by using a class method instead of a scope. Eg

module MyConcern
  extend ActiveSupport::Concern

  class_methods do
    def my_scope(limit = nil)
      (joins, wheres, order etc etc).limit(limit)
    end
  end
end

class MyModel < ApplicationRecord
  include MyConcern

  def self.my_scope(limit = nil)
    super.where(foo: 'bar')
  end
end

class AnotherModel < ApplicationRecord
  include MyConcern
end

For the second part of your question: A concern is technically a ruby Mixin; it's just a convention to organize/group the Mixins that are included in your Models only as concerns. Using ActiveSupport::Concern allows you to add additional Model related functionality to your Mixins like scope, validations etc. which you can't get using a normal Module. Eg You can't do

module MyConcern
  scope :my_scope, ->(limit = nil) { _my_scope(limit) }
end

class MyModel < ApplicationRecord
  include MyConcern # this will raise an error
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM