ihower speaks English RSS

Follow ihower on Twitter

I'm Wen-Tien Chang (a.k.a. ihower). I work on Ruby and Ruby on Rails professionally. Besides, I run Ruby Taiwan community and RubyConf Taiwan.

Here is my english blog. My chinese blog is at http://ihower.tw

ihower {at} GMail.com

Archive

Mar
18th
Thu
permalink

Rails3: ActiveSupport::Concern

(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!