If I add several scopes in a single concern, how can I include them later separately where I need them?
for example:
module Concern
extend ActiveSupport::Concern
included do
scope :by_name, ->(name) { where('LOWER(name) like ?', "%#{ name.downcase }%") }
end
included do
# ...
end
end
Solution 1:
When including a module you can't actually just include part of the module. All the constants and instance methods of a module are included since the module is added to the ancestors chain of the class.
However the Module#included
hook (which is wrapped by ActiveSupport) is actually just a plain old method thats passed the class thats its being included in - self
in the block is also the class. So you can do:
module MyConcern
extend ActiveSupport::Concern
included do |klass|
if klass::SEACHABLE
scope :by_name, ->(name) { where('LOWER(name) like ?', "%#{ name.downcase }%") }
end
end
end
class Foo < ApplicationRecord
SEARCHABLE = true
include MyConcern
end
class Bar < ApplicationRecord
SEARCHABLE = false
include MyConcern
end
It should be noted that scope
here doesn't actually create a method in the module. Its just using metaprogramming to define a method on the class itself. While you might causually say that its "a scope included in the concern" thats not actually true.
If this is actually a good way to solve your problem is questionable but it can be good to understand whats actually happening underneath the magic.
If you want to provide a degree of customizability to the functionality provided by a module you can write "macro-methods" which is a class method that modifies the class - attr_accessor
and the scope
method are examples of this.
module MyConcern
extend ActiveSupport::Concern
class_methods do
def make_searchable(attr)
method_name = "search_by_#{attr}".intern
sql = "LOWER(#{attr}) like ?"
scope method_name, ->(arg) { where(sql, "%#{ arg.downcase }%") }
end
end
end
class Foo < ApplicationRecord
include MyConcern
make_searchable :name
end
Here the scope is defined when not when the module is included but when the make_searchable
method is explicitly called. This pattern lets you pass arguments which is not possible when just including a module.
The drawback is added complexity and that it can hide the method from documentation and code coverage tools.