2017-07-09 12:06:36 +00:00
|
|
|
# frozen_string_literal: true
|
2017-07-10 13:39:13 +00:00
|
|
|
|
2009-05-28 16:35:36 +00:00
|
|
|
module ActiveSupport
|
2023-04-23 13:48:37 +00:00
|
|
|
# = Active Support \Concern
|
|
|
|
#
|
2010-08-14 14:52:05 +00:00
|
|
|
# A typical module looks like this:
|
|
|
|
#
|
|
|
|
# module M
|
|
|
|
# def self.included(base)
|
2010-12-18 00:54:57 +00:00
|
|
|
# base.extend ClassMethods
|
2012-11-15 18:16:36 +00:00
|
|
|
# base.class_eval do
|
|
|
|
# scope :disabled, -> { where(disabled: true) }
|
|
|
|
# end
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module ClassMethods
|
2010-08-14 15:04:17 +00:00
|
|
|
# ...
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2023-05-24 21:52:32 +00:00
|
|
|
# By using +ActiveSupport::Concern+ the above module could instead be
|
2012-09-17 05:22:18 +00:00
|
|
|
# written as:
|
2010-08-14 15:04:17 +00:00
|
|
|
#
|
2020-03-29 23:30:52 +00:00
|
|
|
# require "active_support/concern"
|
2010-08-14 14:52:05 +00:00
|
|
|
#
|
|
|
|
# module M
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
#
|
|
|
|
# included do
|
2012-09-21 17:29:24 +00:00
|
|
|
# scope :disabled, -> { where(disabled: true) }
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
#
|
2014-02-23 19:06:18 +00:00
|
|
|
# class_methods do
|
2010-08-14 15:04:17 +00:00
|
|
|
# ...
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2012-09-17 05:22:18 +00: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 14:52:05 +00:00
|
|
|
#
|
|
|
|
# module Foo
|
|
|
|
# def self.included(base)
|
|
|
|
# base.class_eval do
|
2010-08-14 15:04:17 +00:00
|
|
|
# def self.method_injected_by_foo
|
|
|
|
# ...
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module Bar
|
|
|
|
# def self.included(base)
|
2010-08-14 15:04:17 +00:00
|
|
|
# base.method_injected_by_foo
|
2010-08-14 14:52:05 +00: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 05:22:18 +00: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 14:52:05 +00:00
|
|
|
#
|
|
|
|
# module Bar
|
2012-06-22 21:29:59 +00:00
|
|
|
# include Foo
|
2010-08-14 14:52:05 +00:00
|
|
|
# def self.included(base)
|
2010-08-14 15:04:17 +00:00
|
|
|
# base.method_injected_by_foo
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class Host
|
|
|
|
# include Bar
|
|
|
|
# end
|
|
|
|
#
|
2012-09-17 05:22:18 +00:00
|
|
|
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
|
2023-05-24 21:52:32 +00:00
|
|
|
# is the +Bar+ module, not the +Host+ class. With +ActiveSupport::Concern+,
|
2012-09-17 05:22:18 +00:00
|
|
|
# module dependencies are properly resolved:
|
2010-08-14 14:52:05 +00:00
|
|
|
#
|
2020-03-29 23:30:52 +00:00
|
|
|
# require "active_support/concern"
|
2010-08-14 14:52:05 +00:00
|
|
|
#
|
|
|
|
# module Foo
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
# included do
|
2012-11-15 18:17:53 +00:00
|
|
|
# def self.method_injected_by_foo
|
|
|
|
# ...
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# module Bar
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
# include Foo
|
|
|
|
#
|
|
|
|
# included do
|
2010-08-14 15:04:17 +00:00
|
|
|
# self.method_injected_by_foo
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class Host
|
2014-09-19 21:30:59 +00:00
|
|
|
# include Bar # It works, now Bar takes care of its dependencies
|
2010-08-14 14:52:05 +00:00
|
|
|
# end
|
2019-09-12 15:53:24 +00:00
|
|
|
#
|
|
|
|
# === Prepending concerns
|
|
|
|
#
|
2020-11-17 13:12:18 +00:00
|
|
|
# Just like <tt>include</tt>, concerns also support <tt>prepend</tt> with a corresponding
|
|
|
|
# <tt>prepended do</tt> callback. <tt>module ClassMethods</tt> or <tt>class_methods do</tt> are
|
2020-02-14 17:03:05 +00:00
|
|
|
# prepended as well.
|
2019-09-12 15:53:24 +00:00
|
|
|
#
|
2020-11-17 13:12:18 +00:00
|
|
|
# <tt>prepend</tt> is also used for any dependencies.
|
2009-05-28 16:35:36 +00:00
|
|
|
module Concern
|
2021-07-29 21:18:07 +00:00
|
|
|
class MultipleIncludedBlocks < StandardError # :nodoc:
|
2013-05-16 18:11:27 +00:00
|
|
|
def initialize
|
|
|
|
super "Cannot define multiple 'included' blocks for a Concern"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-07-29 21:18:07 +00:00
|
|
|
class MultiplePrependBlocks < StandardError # :nodoc:
|
2019-09-12 15:33:53 +00:00
|
|
|
def initialize
|
|
|
|
super "Cannot define multiple 'prepended' blocks for a Concern"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-07-29 21:18:07 +00:00
|
|
|
def self.extended(base) # :nodoc:
|
2013-06-11 03:22:08 +00:00
|
|
|
base.instance_variable_set(:@_dependencies, [])
|
2009-10-14 04:32:32 +00:00
|
|
|
end
|
2009-05-28 16:35:36 +00:00
|
|
|
|
2021-07-29 21:18:07 +00:00
|
|
|
def append_features(base) # :nodoc:
|
2013-06-11 03:22:08 +00:00
|
|
|
if base.instance_variable_defined?(:@_dependencies)
|
|
|
|
base.instance_variable_get(:@_dependencies) << self
|
2017-10-28 08:20:38 +00:00
|
|
|
false
|
2009-10-14 04:32:32 +00:00
|
|
|
else
|
|
|
|
return false if base < self
|
2015-02-01 04:12:37 +00:00
|
|
|
@_dependencies.each { |dep| base.include(dep) }
|
2009-10-14 04:32:32 +00:00
|
|
|
super
|
2013-06-11 03:22:08 +00:00
|
|
|
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
|
|
|
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
2009-05-28 16:35:36 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-07-29 21:18:07 +00:00
|
|
|
def prepend_features(base) # :nodoc:
|
2019-09-12 15:33:53 +00:00
|
|
|
if base.instance_variable_defined?(:@_dependencies)
|
|
|
|
base.instance_variable_get(:@_dependencies).unshift self
|
|
|
|
false
|
|
|
|
else
|
|
|
|
return false if base < self
|
|
|
|
@_dependencies.each { |dep| base.prepend(dep) }
|
|
|
|
super
|
2020-02-14 17:03:05 +00:00
|
|
|
base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
2019-09-12 15:33:53 +00:00
|
|
|
base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-16 01:55:47 +00:00
|
|
|
# Evaluate given block in context of base class,
|
|
|
|
# so that you can write class macros here.
|
|
|
|
# When you define more than one +included+ block, it raises an exception.
|
2009-05-28 16:35:36 +00:00
|
|
|
def included(base = nil, &block)
|
|
|
|
if base.nil?
|
2018-11-29 18:36:20 +00:00
|
|
|
if instance_variable_defined?(:@_included_block)
|
|
|
|
if @_included_block.source_location != block.source_location
|
|
|
|
raise MultipleIncludedBlocks
|
|
|
|
end
|
|
|
|
else
|
|
|
|
@_included_block = block
|
|
|
|
end
|
2009-05-28 16:35:36 +00:00
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
2014-02-23 19:06:18 +00:00
|
|
|
|
2019-09-12 15:33:53 +00:00
|
|
|
# Evaluate given block in context of base class,
|
|
|
|
# so that you can write class macros here.
|
|
|
|
# When you define more than one +prepended+ block, it raises an exception.
|
|
|
|
def prepended(base = nil, &block)
|
|
|
|
if base.nil?
|
|
|
|
if instance_variable_defined?(:@_prepended_block)
|
|
|
|
if @_prepended_block.source_location != block.source_location
|
|
|
|
raise MultiplePrependBlocks
|
|
|
|
end
|
|
|
|
else
|
|
|
|
@_prepended_block = block
|
|
|
|
end
|
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-16 01:55:47 +00:00
|
|
|
# Define class methods from given block.
|
|
|
|
# You can define private class methods as well.
|
|
|
|
#
|
|
|
|
# module Example
|
|
|
|
# extend ActiveSupport::Concern
|
|
|
|
#
|
|
|
|
# class_methods do
|
|
|
|
# def foo; puts 'foo'; end
|
|
|
|
#
|
|
|
|
# private
|
|
|
|
# def bar; puts 'bar'; end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# class Buzz
|
|
|
|
# include Example
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Buzz.foo # => "foo"
|
|
|
|
# Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
|
2014-02-23 19:06:18 +00:00
|
|
|
def class_methods(&class_methods_module_definition)
|
2015-06-09 17:00:24 +00:00
|
|
|
mod = const_defined?(:ClassMethods, false) ?
|
2014-02-23 19:06:18 +00:00
|
|
|
const_get(:ClassMethods) :
|
|
|
|
const_set(:ClassMethods, Module.new)
|
|
|
|
|
|
|
|
mod.module_eval(&class_methods_module_definition)
|
|
|
|
end
|
2009-05-28 16:35:36 +00:00
|
|
|
end
|
|
|
|
end
|