Module.included Vs ActiveSupport::Concern

How to include dependencies of a module to the base class?

It was a Friday evening and I was refactoring some pieces of code then I noticed a difference in the ancestor chain of the base class when used with Ruby Module’s included hook or Rails ActiveSupport::Concern.

Consider a general service MessageSearch that searches over a couple of filters:

class MessageSearch
include Search::Modules::Main
include Search::Modules::Sort
include Search::Modules::Decorator
  def perform!
# ... code that needs to be executed
end
end

And here is the ancestor chain:

=> MessageSearch.ancestors
[MessageSearch, Search::Modules::Decorator, Search::Modules::Sort, Search::Modules::Main, ...]

Now consider that this search is also used by other similar services so in that case, we try to extract the basic logic into a new module(say, MessageBase) which adds the common message feature to the base class.

So new module(MessageBase) can be written in 2 flavors:

With ActiveSupport::Concern

module Search
module Modules
module MessageBase
extend ActiveSupport::Concern
      include Search::Modules::Main
include Search::Modules::Sort
include Search::Modules::Decorator
      def perform!
# ... code that needs to be executed
end
end
end
end
class MessageSearch
include Search::Modules::MessageBase
end

This way Search::Modules::MessageBase class will be unaware about any dependencies of MessageBase module

=> MessageSearch.ancestors
[MessageSearch, Search::Modules::MessageBase, Search::Modules::Decorator, Search::Modules::Sort, Search::Modules::Main, ...]

Here, Search::Modules::MessageBase module is next to the class in the ancestor hierarchy which should be expected by the developers(at least from my perspective).

With Module.included hook

This is the very basic way where we inject other modules in the base class.

module Search
module Modules
module MessageBase
def self.included(base_class)
base_class.send(:include, Search::Modules::Main)
base_class.send(:include, Search::Modules::Sort)
base_class.send(:include, Search::Modules::Decorator)
end
      def perform!
# ... code that needs to be executed
end
end
end
end
class MessageSearch
include Search::Modules::MessageBase
end

This way too, the base class(MessageSearch) will be unaware about the Search::Modules::MessageBase’s dependencies. Here is the ancestor chain:

=> MessageSearch.ancestors
[MessageSearch, Search::Modules::Decorator, Search::Modules::Sort, Search::Modules::Main, Search::Modules::MessageBase, ...]

Yes, you spot it correctly!!!! The ancestor chain has been changed this time and Search::Modules::MessageBase is placed after Search::Modules::Main which may not be the required sequence by some of the developers.

So, use either of the approaches very carefully because both may be correct in different scenarios.

P.S. Naming and Architecture of modules may be irrelevant but I just want to focus on the ancestor chain.

Comments & Critics are welcome