Concerns learn to be prepended
This commit is contained in:
parent
6e2ca1a186
commit
ba2bea5e07
|
@ -106,6 +106,12 @@ module ActiveSupport
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class MultiplePrependBlocks < StandardError #:nodoc:
|
||||||
|
def initialize
|
||||||
|
super "Cannot define multiple 'prepended' blocks for a Concern"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.extended(base) #:nodoc:
|
def self.extended(base) #:nodoc:
|
||||||
base.instance_variable_set(:@_dependencies, [])
|
base.instance_variable_set(:@_dependencies, [])
|
||||||
end
|
end
|
||||||
|
@ -123,6 +129,19 @@ module ActiveSupport
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepend_features(base) #:nodoc:
|
||||||
|
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
|
||||||
|
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
||||||
|
base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Evaluate given block in context of base class,
|
# Evaluate given block in context of base class,
|
||||||
# so that you can write class macros here.
|
# so that you can write class macros here.
|
||||||
# When you define more than one +included+ block, it raises an exception.
|
# When you define more than one +included+ block, it raises an exception.
|
||||||
|
@ -140,6 +159,23 @@ module ActiveSupport
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# Define class methods from given block.
|
# Define class methods from given block.
|
||||||
# You can define private class methods as well.
|
# You can define private class methods as well.
|
||||||
#
|
#
|
||||||
|
|
|
@ -19,12 +19,24 @@ class ConcernTest < ActiveSupport::TestCase
|
||||||
def included_ran
|
def included_ran
|
||||||
@included_ran
|
@included_ran
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepended_ran=(value)
|
||||||
|
@prepended_ran = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepended_ran
|
||||||
|
@prepended_ran
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
included do
|
included do
|
||||||
self.included_ran = true
|
self.included_ran = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prepended do
|
||||||
|
self.prepended_ran = true
|
||||||
|
end
|
||||||
|
|
||||||
def baz
|
def baz
|
||||||
"baz"
|
"baz"
|
||||||
end
|
end
|
||||||
|
@ -71,12 +83,24 @@ class ConcernTest < ActiveSupport::TestCase
|
||||||
assert_includes @klass.included_modules, ConcernTest::Baz
|
assert_includes @klass.included_modules, ConcernTest::Baz
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_module_is_prepended_normally
|
||||||
|
@klass.prepend(Baz)
|
||||||
|
assert_equal "baz", @klass.new.baz
|
||||||
|
assert_includes @klass.included_modules, ConcernTest::Baz
|
||||||
|
end
|
||||||
|
|
||||||
def test_class_methods_are_extended
|
def test_class_methods_are_extended
|
||||||
@klass.include(Baz)
|
@klass.include(Baz)
|
||||||
assert_equal "baz", @klass.baz
|
assert_equal "baz", @klass.baz
|
||||||
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; included_modules; end)[0]
|
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; included_modules; end)[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_class_methods_are_extended_when_prepended
|
||||||
|
@klass.prepend(Baz)
|
||||||
|
assert_equal "baz", @klass.baz
|
||||||
|
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; included_modules; end)[0]
|
||||||
|
end
|
||||||
|
|
||||||
def test_class_methods_are_extended_only_on_expected_objects
|
def test_class_methods_are_extended_only_on_expected_objects
|
||||||
::Object.include(Qux)
|
::Object.include(Qux)
|
||||||
Object.extend(Qux::ClassMethods)
|
Object.extend(Qux::ClassMethods)
|
||||||
|
@ -102,6 +126,21 @@ class ConcernTest < ActiveSupport::TestCase
|
||||||
assert_equal true, @klass.included_ran
|
assert_equal true, @klass.included_ran
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_included_block_is_not_ran_when_prepended
|
||||||
|
@klass.prepend(Baz)
|
||||||
|
assert_nil @klass.included_ran
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_prepended_block_is_ran
|
||||||
|
@klass.prepend(Baz)
|
||||||
|
assert_equal true, @klass.prepended_ran
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_prepended_block_is_not_ran_when_included
|
||||||
|
@klass.include(Baz)
|
||||||
|
assert_nil @klass.prepended_ran
|
||||||
|
end
|
||||||
|
|
||||||
def test_modules_dependencies_are_met
|
def test_modules_dependencies_are_met
|
||||||
@klass.include(Bar)
|
@klass.include(Bar)
|
||||||
assert_equal "bar", @klass.new.bar
|
assert_equal "bar", @klass.new.bar
|
||||||
|
@ -115,6 +154,11 @@ class ConcernTest < ActiveSupport::TestCase
|
||||||
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
|
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_dependencies_with_multiple_modules_when_prepended
|
||||||
|
@klass.prepend(Foo)
|
||||||
|
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
|
||||||
|
end
|
||||||
|
|
||||||
def test_raise_on_multiple_included_calls
|
def test_raise_on_multiple_included_calls
|
||||||
assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
|
assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
|
||||||
Module.new do
|
Module.new do
|
||||||
|
@ -129,7 +173,21 @@ class ConcernTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_no_raise_on_same_included_call
|
def test_raise_on_multiple_prepended_calls
|
||||||
|
assert_raises(ActiveSupport::Concern::MultiplePrependBlocks) do
|
||||||
|
Module.new do
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
prepended do
|
||||||
|
end
|
||||||
|
|
||||||
|
prepended do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_raise_on_same_included_or_prepended_call
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
2.times do
|
2.times do
|
||||||
load File.expand_path("../fixtures/concern/some_concern.rb", __FILE__)
|
load File.expand_path("../fixtures/concern/some_concern.rb", __FILE__)
|
||||||
|
|
|
@ -8,4 +8,8 @@ module SomeConcern
|
||||||
included do
|
included do
|
||||||
# shouldn't raise when module is loaded more than once
|
# shouldn't raise when module is loaded more than once
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prepended do
|
||||||
|
# shouldn't raise when module is loaded more than once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue