2009-05-28 12:35:36 -04:00
|
|
|
module ActiveSupport
|
2010-08-14 10:52:05 -04:00
|
|
|
# A typical module looks like this:
|
|
|
|
#
|
|
|
|
# module M
|
|
|
|
# def self.included(base)
|
2010-12-17 19:54:57 -05:00
|
|
|
# base.extend ClassMethods
|
2010-08-14 10:52:05 -04:00
|
|
|
# base.send(:include, InstanceMethods)
|
2010-08-14 11:04:17 -04:00
|
|
|
# scope :disabled, where(:disabled => true)
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module ClassMethods
|
2010-08-14 11:04:17 -04:00
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module InstanceMethods
|
2010-08-14 11:04:17 -04:00
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
|
2010-08-14 11:04:17 -04:00
|
|
|
#
|
2010-08-14 10:52:05 -04:00
|
|
|
# require 'active_support/concern'
|
|
|
|
#
|
|
|
|
# module M
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
#
|
|
|
|
# included do
|
2010-08-14 11:04:17 -04:00
|
|
|
# scope :disabled, where(:disabled => true)
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module ClassMethods
|
2010-08-14 11:04:17 -04:00
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module InstanceMethods
|
2010-08-14 11:04:17 -04:00
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2010-08-14 11:04:17 -04:00
|
|
|
# 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:
|
2010-08-14 10:52:05 -04:00
|
|
|
#
|
|
|
|
# module Foo
|
|
|
|
# def self.included(base)
|
|
|
|
# base.class_eval do
|
2010-08-14 11:04:17 -04:00
|
|
|
# def self.method_injected_by_foo
|
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module Bar
|
|
|
|
# def self.included(base)
|
2010-08-14 11:04:17 -04:00
|
|
|
# base.method_injected_by_foo
|
2010-08-14 10:52:05 -04:00
|
|
|
# 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
|
|
|
|
#
|
2010-08-14 11:04:17 -04:00
|
|
|
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
|
|
|
|
# these from +Host+ directly including +Foo+ in +Bar+:
|
2010-08-14 10:52:05 -04:00
|
|
|
#
|
|
|
|
# module Bar
|
|
|
|
# include Foo
|
|
|
|
# def self.included(base)
|
2010-08-14 11:04:17 -04:00
|
|
|
# base.method_injected_by_foo
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class Host
|
|
|
|
# include Bar
|
|
|
|
# end
|
|
|
|
#
|
2010-08-14 11:04:17 -04:00
|
|
|
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
|
|
|
|
# not the +Host+ class. With <tt>ActiveSupport::Concern</tt>, module dependencies are properly resolved:
|
2010-08-14 10:52:05 -04:00
|
|
|
#
|
|
|
|
# require 'active_support/concern'
|
|
|
|
#
|
|
|
|
# module Foo
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
# included do
|
|
|
|
# class_eval do
|
2010-08-14 11:04:17 -04:00
|
|
|
# def self.method_injected_by_foo
|
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module Bar
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
# include Foo
|
|
|
|
#
|
|
|
|
# included do
|
2010-08-14 11:04:17 -04:00
|
|
|
# self.method_injected_by_foo
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class Host
|
2010-08-14 11:04:17 -04:00
|
|
|
# include Bar # works, Bar takes care now of its dependencies
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
2009-05-28 12:35:36 -04:00
|
|
|
module Concern
|
2009-10-14 00:32:32 -04:00
|
|
|
def self.extended(base)
|
|
|
|
base.instance_variable_set("@_dependencies", [])
|
|
|
|
end
|
2009-05-28 12:35:36 -04:00
|
|
|
|
|
|
|
def append_features(base)
|
2009-10-14 00:32:32 -04:00
|
|
|
if base.instance_variable_defined?("@_dependencies")
|
|
|
|
base.instance_variable_get("@_dependencies") << self
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
return false if base < self
|
|
|
|
@_dependencies.each { |dep| base.send(:include, dep) }
|
|
|
|
super
|
2009-05-28 12:35:36 -04:00
|
|
|
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
|
2009-05-29 17:28:54 -04:00
|
|
|
base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
|
2009-05-28 12:35:36 -04:00
|
|
|
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def included(base = nil, &block)
|
|
|
|
if base.nil?
|
|
|
|
@_included_block = block
|
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|