Concerns learn to be prepended

This commit is contained in:
Jason Karns 2019-09-12 11:33:53 -04:00 committed by Kasper Timm Hansen
parent 6e2ca1a186
commit ba2bea5e07
No known key found for this signature in database
GPG Key ID: 191153215EDA53D8
3 changed files with 99 additions and 1 deletions

View File

@ -106,6 +106,12 @@ module ActiveSupport
end
end
class MultiplePrependBlocks < StandardError #:nodoc:
def initialize
super "Cannot define multiple 'prepended' blocks for a Concern"
end
end
def self.extended(base) #:nodoc:
base.instance_variable_set(:@_dependencies, [])
end
@ -123,6 +129,19 @@ module ActiveSupport
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,
# so that you can write class macros here.
# When you define more than one +included+ block, it raises an exception.
@ -140,6 +159,23 @@ module ActiveSupport
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.
# You can define private class methods as well.
#

View File

@ -19,12 +19,24 @@ class ConcernTest < ActiveSupport::TestCase
def included_ran
@included_ran
end
def prepended_ran=(value)
@prepended_ran = value
end
def prepended_ran
@prepended_ran
end
end
included do
self.included_ran = true
end
prepended do
self.prepended_ran = true
end
def baz
"baz"
end
@ -71,12 +83,24 @@ class ConcernTest < ActiveSupport::TestCase
assert_includes @klass.included_modules, ConcernTest::Baz
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
@klass.include(Baz)
assert_equal "baz", @klass.baz
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; included_modules; end)[0]
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
::Object.include(Qux)
Object.extend(Qux::ClassMethods)
@ -102,6 +126,21 @@ class ConcernTest < ActiveSupport::TestCase
assert_equal true, @klass.included_ran
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
@klass.include(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]
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
assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
Module.new do
@ -129,7 +173,21 @@ class ConcernTest < ActiveSupport::TestCase
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
2.times do
load File.expand_path("../fixtures/concern/some_concern.rb", __FILE__)

View File

@ -8,4 +8,8 @@ module SomeConcern
included do
# shouldn't raise when module is loaded more than once
end
prepended do
# shouldn't raise when module is loaded more than once
end
end