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.