A typical module looks like this:
module M
def self.included(base)
base.extend ClassMethods
scope :disabled, where(:disabled => true)
end
module ClassMethods
...
end
end
By using ActiveSupport::Concern the above module could instead be written as:
module M
extend ActiveSupport::Concern
included do
scope :disabled, where(:disabled => true)
end
module ClassMethods
...
end
end
Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+ module which depends on the former, we would typically write the following:
module Foo
def self.included(base)
base.class_eval do
def self.method_injected_by_foo
...
end
end
end
end
module Bar
def self.included(base)
base.method_injected_by_foo
end
end
class Host
include Foo # We need to include this dependency for Bar
include Bar # Bar is the module that Host really needs
end
But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide these from +Host+ directly including +Foo+ in +Bar+:
module Bar
include Foo
def self.included(base)
base.method_injected_by_foo
end
end
class Host
include Bar
end
Unfortunately this won't work, since when Foo is included, its base is the Bar module, not the Host class. With ActiveSupport::Concern, module dependencies are properly resolved:
module Foo
extend ActiveSupport::Concern
included do
class_eval do
def self.method_injected_by_foo
...
end
end
end
end
module Bar
extend ActiveSupport::Concern
include Foo
included do
self.method_injected_by_foo
end
end
class Host
include Bar # works, Bar takes care now of its dependencies
end