mirror of
https://github.com/github/rails.git
synced 2026-01-12 08:08:31 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b7754c950 | ||
|
|
75638c576b |
134
activesupport/lib/active_support/concern.rb
Normal file
134
activesupport/lib/active_support/concern.rb
Normal file
@@ -0,0 +1,134 @@
|
||||
module ActiveSupport
|
||||
# A typical module looks like this:
|
||||
#
|
||||
# module M
|
||||
# def self.included(base)
|
||||
# base.extend ClassMethods
|
||||
# base.class_eval do
|
||||
# scope :disabled, -> { where(disabled: true) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# module ClassMethods
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
|
||||
# written as:
|
||||
#
|
||||
# require 'active_support/concern'
|
||||
#
|
||||
# module M
|
||||
# extend ActiveSupport::Concern
|
||||
#
|
||||
# included do
|
||||
# scope :disabled, -> { where(disabled: true) }
|
||||
# end
|
||||
#
|
||||
# module ClassMethods
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
# module Foo
|
||||
# def self.included(base)
|
||||
# base.class_eval do
|
||||
# def self.method_injected_by_foo
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# module Bar
|
||||
# def self.included(base)
|
||||
# base.method_injected_by_foo
|
||||
# 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
|
||||
#
|
||||
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
|
||||
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
|
||||
#
|
||||
# module Bar
|
||||
# include Foo
|
||||
# def self.included(base)
|
||||
# base.method_injected_by_foo
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Host
|
||||
# include Bar
|
||||
# end
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
# require 'active_support/concern'
|
||||
#
|
||||
# module Foo
|
||||
# extend ActiveSupport::Concern
|
||||
# included do
|
||||
# def self.method_injected_by_foo
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# module Bar
|
||||
# extend ActiveSupport::Concern
|
||||
# include Foo
|
||||
#
|
||||
# included do
|
||||
# self.method_injected_by_foo
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Host
|
||||
# include Bar # works, Bar takes care now of its dependencies
|
||||
# end
|
||||
module Concern
|
||||
class MultipleIncludedBlocks < StandardError #:nodoc:
|
||||
def initialize
|
||||
super "Cannot define multiple 'included' blocks for a Concern"
|
||||
end
|
||||
end
|
||||
|
||||
def self.extended(base) #:nodoc:
|
||||
base.instance_variable_set(:@_dependencies, [])
|
||||
end
|
||||
|
||||
def append_features(base)
|
||||
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
|
||||
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
||||
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
||||
end
|
||||
end
|
||||
|
||||
def included(base = nil, &block)
|
||||
if base.nil?
|
||||
raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
|
||||
|
||||
@_included_block = block
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
activesupport/test/concern_test.rb
Normal file
98
activesupport/test/concern_test.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
require 'abstract_unit'
|
||||
require 'active_support/concern'
|
||||
|
||||
class ConcernTest < ActiveSupport::TestCase
|
||||
module Baz
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
def baz
|
||||
"baz"
|
||||
end
|
||||
|
||||
def included_ran=(value)
|
||||
@@included_ran = value
|
||||
end
|
||||
|
||||
def included_ran
|
||||
@@included_ran
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
self.included_ran = true
|
||||
end
|
||||
|
||||
def baz
|
||||
"baz"
|
||||
end
|
||||
end
|
||||
|
||||
module Bar
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Baz
|
||||
|
||||
def bar
|
||||
"bar"
|
||||
end
|
||||
|
||||
def baz
|
||||
"bar+" + super
|
||||
end
|
||||
end
|
||||
|
||||
module Foo
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Bar, Baz
|
||||
end
|
||||
|
||||
def setup
|
||||
@klass = Class.new
|
||||
end
|
||||
|
||||
def test_module_is_included_normally
|
||||
@klass.send(:include, Baz)
|
||||
assert_equal "baz", @klass.new.baz
|
||||
assert @klass.included_modules.include?(ConcernTest::Baz)
|
||||
end
|
||||
|
||||
def test_class_methods_are_extended
|
||||
@klass.send(:include, Baz)
|
||||
assert_equal "baz", @klass.baz
|
||||
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
|
||||
end
|
||||
|
||||
def test_included_block_is_ran
|
||||
@klass.send(:include, Baz)
|
||||
assert_equal true, @klass.included_ran
|
||||
end
|
||||
|
||||
def test_modules_dependencies_are_met
|
||||
@klass.send(:include, Bar)
|
||||
assert_equal "bar", @klass.new.bar
|
||||
assert_equal "bar+baz", @klass.new.baz
|
||||
assert_equal "baz", @klass.baz
|
||||
assert @klass.included_modules.include?(ConcernTest::Bar)
|
||||
end
|
||||
|
||||
def test_dependencies_with_multiple_modules
|
||||
@klass.send(:include, Foo)
|
||||
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
|
||||
end
|
||||
|
||||
def test_raise_on_multiple_included_calls
|
||||
assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
|
||||
Module.new do
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
end
|
||||
|
||||
included do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user