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. E.g.
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. E.g. 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