2017-07-09 08:06:36 -04:00
|
|
|
# frozen_string_literal: true
|
2017-07-10 09:39:13 -04:00
|
|
|
|
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
|
2012-11-15 13:16:36 -05:00
|
|
|
# base.class_eval do
|
|
|
|
# scope :disabled, -> { where(disabled: true) }
|
|
|
|
# end
|
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
|
|
|
|
# end
|
|
|
|
#
|
2012-09-17 01:22:18 -04:00
|
|
|
# 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
|
2012-09-21 13:29:24 -04:00
|
|
|
# scope :disabled, -> { where(disabled: true) }
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
#
|
2014-02-23 14:06:18 -05:00
|
|
|
# class_methods do
|
2010-08-14 11:04:17 -04:00
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2012-09-17 01:22:18 -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
|
|
|
|
#
|
2012-09-17 01:22:18 -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
|
2012-06-22 17:29:59 -04:00
|
|
|
# include Foo
|
2010-08-14 10:52:05 -04:00
|
|
|
# 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
|
|
|
|
#
|
2012-09-17 01:22:18 -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
|
2012-11-15 13:17:53 -05:00
|
|
|
# def self.method_injected_by_foo
|
|
|
|
# ...
|
2010-08-14 10:52:05 -04:00
|
|
|
# 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
|
2014-09-19 17:30:59 -04:00
|
|
|
# include Bar # It works, now Bar takes care of its dependencies
|
2010-08-14 10:52:05 -04:00
|
|
|
# end
|
2009-05-28 12:35:36 -04:00
|
|
|
module Concern
|
2013-05-16 14:11:27 -04:00
|
|
|
class MultipleIncludedBlocks < StandardError #:nodoc:
|
|
|
|
def initialize
|
|
|
|
super "Cannot define multiple 'included' blocks for a Concern"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-06-22 17:29:59 -04:00
|
|
|
def self.extended(base) #:nodoc:
|
2013-06-10 23:22:08 -04:00
|
|
|
base.instance_variable_set(:@_dependencies, [])
|
2009-10-14 00:32:32 -04:00
|
|
|
end
|
2009-05-28 12:35:36 -04:00
|
|
|
|
|
|
|
def append_features(base)
|
2013-06-10 23:22:08 -04:00
|
|
|
if base.instance_variable_defined?(:@_dependencies)
|
|
|
|
base.instance_variable_get(:@_dependencies) << self
|
2017-10-28 04:20:38 -04:00
|
|
|
false
|
2009-10-14 00:32:32 -04:00
|
|
|
else
|
|
|
|
return false if base < self
|
2015-01-31 23:12:37 -05:00
|
|
|
@_dependencies.each { |dep| base.include(dep) }
|
2009-10-14 00:32:32 -04:00
|
|
|
super
|
2013-06-10 23:22:08 -04:00
|
|
|
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
|
|
|
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
2009-05-28 12:35:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def included(base = nil, &block)
|
|
|
|
if base.nil?
|
2013-06-10 23:22:08 -04:00
|
|
|
raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
|
2013-05-16 14:11:27 -04:00
|
|
|
|
2009-05-28 12:35:36 -04:00
|
|
|
@_included_block = block
|
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
2014-02-23 14:06:18 -05:00
|
|
|
|
|
|
|
def class_methods(&class_methods_module_definition)
|
2015-06-09 13:00:24 -04:00
|
|
|
mod = const_defined?(:ClassMethods, false) ?
|
2014-02-23 14:06:18 -05:00
|
|
|
const_get(:ClassMethods) :
|
|
|
|
const_set(:ClassMethods, Module.new)
|
|
|
|
|
|
|
|
mod.module_eval(&class_methods_module_definition)
|
|
|
|
end
|
2009-05-28 12:35:36 -04:00
|
|
|
end
|
|
|
|
end
|