(Chinese version)
ActiveSupport::Concern is an important tool for Rails3 modularity. It makes managing module dependencies management very easy and intuitional.
Suppose we have two modules which have dependency relationship. Module Bar depends on module Foo. And there is a class Host which intends to include Bar features. Therefore we can write code like this:
module Foo
# self.included will be executed when Foo is included
def self.included(base)
# Do some enhancements for Host class
base.send(:do_host_something)
end
end
module Bar
def self.included(base)
base.send(:do_host_something)
end
end
class Host
include Foo, Bar
end
But there is a hateful disadvantage: we have to include both Foo and Bar inside Host class. It means we have to include all dependent modules. That’s bad! Why should we need to know modules dependency inside Host class? :/
We wish that we can write dependency relationship in modules instead of Host class, so we can just include the module which we want to use it inside Host class. For this reason, we rewrite it to:
module Bar
include Foo # We include Foo here because Bar depends on Foo
def self.included(base)
base.send(:do_host_something)
end
end
class Host
include Bar # Only need to include Bar. Need not to know Bar's dependency
end
It looks fine, but there is a fatal error which prevents it running. The reason is that we include Foo in Bar module now, so when we are inside Foo’s self.included method, its base parameter becomes Bar module (not Host class anymore). So it can not access any methods and variables in Host class. It fails when do_host_something.
Okay,ActiveSupport::Concern is the antidote. We wish Host class need not to know module dependencies. The dependencies are written in module.
require 'active_support/concern'
module Foo
extend ActiveSupport::Concern
included do
self.send(:do_host_something)
end
end
module Bar
extend ActiveSupport::Concern
include Foo # We include Foo here because Bar depends on Foo
included do
self.send(:do_host_something)
end
end
class Host
include Bar # Only need to include Bar. Need not to know Bar's dependency
end
It’s done. One more thing: If you define module ClassMethods and module InstanceMethods, it will automatically load to Host class. So you need not to write send(:include, InstanceMethods) and send(:extend, ClassMethods). For example:
module Foo
extend ActiveSupport::Concern
included do
self.send(:do_host_something)
end
module ClassMethods
def bite
# do something
end
end
module InstanceMethods
def poke
# do something
end
end
end
If you want to know what exactly ActiveSupport::Concern doing, please checkout /activesupport/lib/active_support/concern.rb. Only 29 lines, and it does not depend any other library. It’s awesome!