2018-03-04 15:09:32 +00:00
|
|
|
require_relative '../../spec_helper'
|
|
|
|
require_relative 'fixtures/classes'
|
2017-05-07 12:04:49 +00:00
|
|
|
|
|
|
|
describe "Module#prepend" do
|
|
|
|
it "is a public method" do
|
|
|
|
Module.should have_public_instance_method(:prepend, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not affect the superclass" do
|
|
|
|
Class.new { prepend Module.new }.superclass.should == Object
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #prepend_features(self) in reversed order on each module" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
m = Module.new do
|
|
|
|
def self.prepend_features(mod)
|
|
|
|
ScratchPad << [ self, mod ]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
m2 = Module.new do
|
|
|
|
def self.prepend_features(mod)
|
|
|
|
ScratchPad << [ self, mod ]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
m3 = Module.new do
|
|
|
|
def self.prepend_features(mod)
|
|
|
|
ScratchPad << [ self, mod ]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
c = Class.new { prepend(m, m2, m3) }
|
|
|
|
|
|
|
|
ScratchPad.recorded.should == [ [ m3, c], [ m2, c ], [ m, c ] ]
|
|
|
|
end
|
|
|
|
|
2021-06-02 14:34:07 +02:00
|
|
|
it "updates the method when a module is prepended" do
|
|
|
|
m_module = Module.new do
|
|
|
|
def foo
|
|
|
|
"m"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class = Class.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
a_class.class_eval do
|
|
|
|
prepend m_module
|
|
|
|
end
|
|
|
|
foo.call.should == 'm'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when a prepended module is updated" do
|
|
|
|
m_module = Module.new
|
|
|
|
a_class = Class.new do
|
|
|
|
prepend m_module
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
m_module.module_eval do
|
|
|
|
def foo
|
|
|
|
"m"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
foo.call.should == 'm'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when there is a base included method and the prepended module overrides it" do
|
|
|
|
base_module = Module.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class = Class.new do
|
|
|
|
include base_module
|
|
|
|
end
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
m_module = Module.new do
|
|
|
|
def foo
|
|
|
|
"m"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class.prepend m_module
|
|
|
|
foo.call.should == 'm'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when there is a base included method and the prepended module is later updated" do
|
|
|
|
base_module = Module.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class = Class.new do
|
|
|
|
include base_module
|
|
|
|
end
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
m_module = Module.new
|
|
|
|
a_class.prepend m_module
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
m_module.module_eval do
|
|
|
|
def foo
|
|
|
|
"m"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
foo.call.should == 'm'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when a module prepended after a call is later updated" do
|
|
|
|
m_module = Module.new
|
|
|
|
a_class = Class.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
a_class.prepend m_module
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
m_module.module_eval do
|
|
|
|
def foo
|
|
|
|
"m"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
foo.call.should == 'm'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when a module is prepended after another and the method is defined later on that module" do
|
|
|
|
m_module = Module.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class = Class.new
|
|
|
|
a_class.prepend m_module
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
n_module = Module.new
|
|
|
|
a_class.prepend n_module
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
n_module.module_eval do
|
|
|
|
def foo
|
|
|
|
"n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
foo.call.should == 'n'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when a module is included in a prepended module and the method is defined later" do
|
|
|
|
a_class = Class.new
|
|
|
|
base_module = Module.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
a_class.prepend base_module
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
m_module = Module.new
|
|
|
|
n_module = Module.new
|
|
|
|
m_module.include n_module
|
|
|
|
a_class.prepend m_module
|
|
|
|
|
|
|
|
n_module.module_eval do
|
|
|
|
def foo
|
|
|
|
"n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
foo.call.should == 'n'
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the method when a new module with an included module is prepended" do
|
|
|
|
a_class = Class.new do
|
|
|
|
def foo
|
|
|
|
'a'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
n_module = Module.new do
|
|
|
|
def foo
|
|
|
|
'n'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
m_module = Module.new do
|
|
|
|
include n_module
|
|
|
|
end
|
|
|
|
|
|
|
|
a = a_class.new
|
|
|
|
foo = -> { a.foo }
|
|
|
|
|
|
|
|
foo.call.should == 'a'
|
|
|
|
|
|
|
|
a_class.class_eval do
|
|
|
|
prepend m_module
|
|
|
|
end
|
|
|
|
|
|
|
|
foo.call.should == 'n'
|
|
|
|
end
|
|
|
|
|
2017-05-07 12:04:49 +00:00
|
|
|
it "raises a TypeError when the argument is not a Module" do
|
2019-07-27 12:40:09 +02:00
|
|
|
-> { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError)
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does not raise a TypeError when the argument is an instance of a subclass of Module" do
|
2019-07-27 12:40:09 +02:00
|
|
|
-> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "imports constants" do
|
|
|
|
m1 = Module.new
|
|
|
|
m1::MY_CONSTANT = 1
|
|
|
|
m2 = Module.new { prepend(m1) }
|
|
|
|
m2.constants.should include(:MY_CONSTANT)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "imports instance methods" do
|
|
|
|
Module.new { prepend ModuleSpecs::A }.instance_methods.should include(:ma)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not import methods to modules and classes" do
|
|
|
|
Module.new { prepend ModuleSpecs::A }.methods.should_not include(:ma)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "allows wrapping methods" do
|
|
|
|
m = Module.new { def calc(x) super + 3 end }
|
|
|
|
c = Class.new { def calc(x) x*2 end }
|
|
|
|
c.prepend(m)
|
|
|
|
c.new.calc(1).should == 5
|
|
|
|
end
|
|
|
|
|
|
|
|
it "also prepends included modules" do
|
|
|
|
a = Module.new { def calc(x) x end }
|
|
|
|
b = Module.new { include a }
|
|
|
|
c = Class.new { prepend b }
|
|
|
|
c.new.calc(1).should == 1
|
|
|
|
end
|
|
|
|
|
|
|
|
it "prepends multiple modules in the right order" do
|
|
|
|
m1 = Module.new { def chain; super << :m1; end }
|
|
|
|
m2 = Module.new { def chain; super << :m2; end; prepend(m1) }
|
|
|
|
c = Class.new { def chain; [:c]; end; prepend(m2) }
|
|
|
|
c.new.chain.should == [:c, :m2, :m1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "includes prepended modules in ancestors" do
|
|
|
|
m = Module.new
|
|
|
|
Class.new { prepend(m) }.ancestors.should include(m)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "reports the prepended module as the method owner" do
|
|
|
|
m = Module.new { def meth; end }
|
|
|
|
c = Class.new { def meth; end; prepend(m) }
|
|
|
|
c.new.method(:meth).owner.should == m
|
|
|
|
end
|
|
|
|
|
|
|
|
it "reports the prepended module as the unbound method owner" do
|
|
|
|
m = Module.new { def meth; end }
|
|
|
|
c = Class.new { def meth; end; prepend(m) }
|
|
|
|
c.instance_method(:meth).owner.should == m
|
|
|
|
c.public_instance_method(:meth).owner.should == m
|
|
|
|
end
|
|
|
|
|
|
|
|
it "causes the prepended module's method to be aliased by alias_method" do
|
|
|
|
m = Module.new { def meth; :m end }
|
|
|
|
c = Class.new { def meth; :c end; prepend(m); alias_method :alias, :meth }
|
|
|
|
c.new.alias.should == :m
|
|
|
|
end
|
|
|
|
|
2018-03-26 20:48:21 +00:00
|
|
|
it "reports the class for the owner of an aliased method on the class" do
|
|
|
|
m = Module.new
|
|
|
|
c = Class.new { prepend(m); def meth; :c end; alias_method :alias, :meth }
|
|
|
|
c.instance_method(:alias).owner.should == c
|
|
|
|
end
|
|
|
|
|
2018-04-28 19:50:06 +00:00
|
|
|
it "reports the class for the owner of a method aliased from the prepended module" do
|
|
|
|
m = Module.new { def meth; :m end }
|
|
|
|
c = Class.new { prepend(m); alias_method :alias, :meth }
|
|
|
|
c.instance_method(:alias).owner.should == c
|
2018-03-26 20:48:21 +00:00
|
|
|
end
|
|
|
|
|
2017-05-07 12:04:49 +00:00
|
|
|
it "sees an instance of a prepended class as kind of the prepended module" do
|
|
|
|
m = Module.new
|
|
|
|
c = Class.new { prepend(m) }
|
|
|
|
c.new.should be_kind_of(m)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "keeps the module in the chain when dupping the class" do
|
|
|
|
m = Module.new
|
|
|
|
c = Class.new { prepend(m) }
|
|
|
|
c.dup.new.should be_kind_of(m)
|
|
|
|
end
|
|
|
|
|
2020-09-15 21:54:31 +02:00
|
|
|
ruby_version_is '0'...'3.0' do
|
Ensure origins for all included, prepended, and refined modules
This fixes various issues when a module is included in or prepended
to a module or class, and then refined, or refined and then included
or prepended to a module or class.
Implement by renaming ensure_origin to rb_ensure_origin, making it
non-static, and calling it when refining a module.
Fix Module#initialize_copy to handle origins correctly. Previously,
Module#initialize_copy did not handle origins correctly. For example,
this code:
```ruby
module B; end
class A
def b; 2 end
prepend B
end
a = A.dup.new
class A
def b; 1 end
end
p a.b
```
Printed 1 instead of 2. This is because the super chain for
a.singleton_class was:
```
a.singleton_class
A.dup
B(iclass)
B(iclass origin)
A(origin) # not A.dup(origin)
```
The B iclasses would not be modified, so the includer entry would be
still be set to A and not A.dup.
This modifies things so that if the class/module has an origin,
all iclasses between the class/module and the origin are duplicated
and have the correct includer entry set, and the correct origin
is created.
This requires other changes to make sure all tests still pass:
* rb_undef_methods_from doesn't automatically handle classes with
origins, so pass it the origin for Comparable when undefing
methods in Complex. This fixed a failure in the Complex tests.
* When adding a method, the method cache was not cleared
correctly if klass has an origin. Clear the method cache for
the klass before switching to the origin of klass. This fixed
failures in the autoload tests related to overridding require,
without breaking the optimization tests. Also clear the method
cache for both the module and origin when removing a method.
* Module#include? is fixed to skip origin iclasses.
* Refinements are fixed to use the origin class of the module that
has an origin.
* RCLASS_REFINED_BY_ANY is removed as it was only used in a single
place and is no longer needed.
* Marshal#dump is fixed to skip iclass origins.
* rb_method_entry_make is fixed to handled overridden optimized
methods for modules that have origins.
Fixes [Bug #16852]
2020-05-23 20:16:27 -07:00
|
|
|
it "keeps the module in the chain when dupping an intermediate module" do
|
|
|
|
m1 = Module.new { def calc(x) x end }
|
|
|
|
m2 = Module.new { prepend(m1) }
|
|
|
|
c1 = Class.new { prepend(m2) }
|
|
|
|
m2dup = m2.dup
|
|
|
|
m2dup.ancestors.should == [m2dup,m1,m2]
|
|
|
|
c2 = Class.new { prepend(m2dup) }
|
|
|
|
c1.ancestors[0,3].should == [m1,m2,c1]
|
|
|
|
c1.new.should be_kind_of(m1)
|
|
|
|
c2.ancestors[0,4].should == [m2dup,m1,m2,c2]
|
|
|
|
c2.new.should be_kind_of(m1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-15 21:54:31 +02:00
|
|
|
ruby_version_is '3.0' do
|
Ensure origins for all included, prepended, and refined modules
This fixes various issues when a module is included in or prepended
to a module or class, and then refined, or refined and then included
or prepended to a module or class.
Implement by renaming ensure_origin to rb_ensure_origin, making it
non-static, and calling it when refining a module.
Fix Module#initialize_copy to handle origins correctly. Previously,
Module#initialize_copy did not handle origins correctly. For example,
this code:
```ruby
module B; end
class A
def b; 2 end
prepend B
end
a = A.dup.new
class A
def b; 1 end
end
p a.b
```
Printed 1 instead of 2. This is because the super chain for
a.singleton_class was:
```
a.singleton_class
A.dup
B(iclass)
B(iclass origin)
A(origin) # not A.dup(origin)
```
The B iclasses would not be modified, so the includer entry would be
still be set to A and not A.dup.
This modifies things so that if the class/module has an origin,
all iclasses between the class/module and the origin are duplicated
and have the correct includer entry set, and the correct origin
is created.
This requires other changes to make sure all tests still pass:
* rb_undef_methods_from doesn't automatically handle classes with
origins, so pass it the origin for Comparable when undefing
methods in Complex. This fixed a failure in the Complex tests.
* When adding a method, the method cache was not cleared
correctly if klass has an origin. Clear the method cache for
the klass before switching to the origin of klass. This fixed
failures in the autoload tests related to overridding require,
without breaking the optimization tests. Also clear the method
cache for both the module and origin when removing a method.
* Module#include? is fixed to skip origin iclasses.
* Refinements are fixed to use the origin class of the module that
has an origin.
* RCLASS_REFINED_BY_ANY is removed as it was only used in a single
place and is no longer needed.
* Marshal#dump is fixed to skip iclass origins.
* rb_method_entry_make is fixed to handled overridden optimized
methods for modules that have origins.
Fixes [Bug #16852]
2020-05-23 20:16:27 -07:00
|
|
|
it "uses only new module when dupping the module" do
|
|
|
|
m1 = Module.new { def calc(x) x end }
|
|
|
|
m2 = Module.new { prepend(m1) }
|
|
|
|
c1 = Class.new { prepend(m2) }
|
|
|
|
m2dup = m2.dup
|
|
|
|
m2dup.ancestors.should == [m1,m2dup]
|
|
|
|
c2 = Class.new { prepend(m2dup) }
|
|
|
|
c1.ancestors[0,3].should == [m1,m2,c1]
|
|
|
|
c1.new.should be_kind_of(m1)
|
|
|
|
c2.ancestors[0,3].should == [m1,m2dup,c2]
|
|
|
|
c2.new.should be_kind_of(m1)
|
|
|
|
end
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "depends on prepend_features to add the module" do
|
|
|
|
m = Module.new { def self.prepend_features(mod) end }
|
|
|
|
Class.new { prepend(m) }.ancestors.should_not include(m)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "adds the module in the subclass chains" do
|
|
|
|
parent = Class.new { def chain; [:parent]; end }
|
|
|
|
child = Class.new(parent) { def chain; super << :child; end }
|
|
|
|
mod = Module.new { def chain; super << :mod; end }
|
|
|
|
parent.prepend(mod)
|
|
|
|
parent.ancestors[0,2].should == [mod, parent]
|
|
|
|
child.ancestors[0,3].should == [child, mod, parent]
|
|
|
|
|
|
|
|
parent.new.chain.should == [:parent, :mod]
|
|
|
|
child.new.chain.should == [:parent, :mod, :child]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "inserts a later prepended module into the chain" do
|
|
|
|
m1 = Module.new { def chain; super << :m1; end }
|
|
|
|
m2 = Module.new { def chain; super << :m2; end }
|
|
|
|
c1 = Class.new { def chain; [:c1]; end; prepend m1 }
|
|
|
|
c2 = Class.new(c1) { def chain; super << :c2; end }
|
|
|
|
c2.new.chain.should == [:c1, :m1, :c2]
|
|
|
|
c1.prepend(m2)
|
|
|
|
c2.new.chain.should == [:c1, :m1, :m2, :c2]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "works with subclasses" do
|
|
|
|
m = Module.new do
|
|
|
|
def chain
|
|
|
|
super << :module
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
c = Class.new do
|
|
|
|
prepend m
|
|
|
|
def chain
|
|
|
|
[:class]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
s = Class.new(c) do
|
|
|
|
def chain
|
|
|
|
super << :subclass
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
s.new.chain.should == [:class, :module, :subclass]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "throws a NoMethodError when there is no more superclass" do
|
|
|
|
m = Module.new do
|
|
|
|
def chain
|
|
|
|
super << :module
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
c = Class.new do
|
|
|
|
prepend m
|
|
|
|
def chain
|
|
|
|
super << :class
|
|
|
|
end
|
|
|
|
end
|
2019-07-27 12:40:09 +02:00
|
|
|
-> { c.new.chain }.should raise_error(NoMethodError)
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "calls prepended after prepend_features" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
m = Module.new do
|
|
|
|
def self.prepend_features(klass)
|
|
|
|
ScratchPad << [:prepend_features, klass]
|
|
|
|
end
|
|
|
|
def self.prepended(klass)
|
|
|
|
ScratchPad << [:prepended, klass]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
c = Class.new { prepend(m) }
|
|
|
|
ScratchPad.recorded.should == [[:prepend_features, c], [:prepended, c]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "detects cyclic prepends" do
|
2019-07-27 12:40:09 +02:00
|
|
|
-> {
|
2017-05-07 12:04:49 +00:00
|
|
|
module ModuleSpecs::P
|
|
|
|
prepend ModuleSpecs::P
|
|
|
|
end
|
|
|
|
}.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
|
2019-04-27 18:53:23 +02:00
|
|
|
it "doesn't accept no-arguments" do
|
2019-07-27 12:40:09 +02:00
|
|
|
-> {
|
2019-04-27 18:53:23 +02:00
|
|
|
Module.new do
|
|
|
|
prepend
|
|
|
|
end
|
|
|
|
}.should raise_error(ArgumentError)
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "returns the class it's included into" do
|
|
|
|
m = Module.new
|
|
|
|
r = nil
|
|
|
|
c = Class.new { r = prepend m }
|
|
|
|
r.should == c
|
|
|
|
end
|
|
|
|
|
|
|
|
it "clears any caches" do
|
|
|
|
module ModuleSpecs::M3
|
|
|
|
module PM1
|
|
|
|
def foo
|
|
|
|
:m1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
module PM2
|
|
|
|
def foo
|
|
|
|
:m2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
klass = Class.new do
|
|
|
|
prepend PM1
|
|
|
|
|
|
|
|
def get
|
|
|
|
foo
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
o = klass.new
|
|
|
|
o.get.should == :m1
|
|
|
|
|
|
|
|
klass.class_eval do
|
|
|
|
prepend PM2
|
|
|
|
end
|
|
|
|
|
|
|
|
o.get.should == :m2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "supports super when the module is prepended into a singleton class" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
mod = Module.new do
|
|
|
|
def self.inherited(base)
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
module_with_singleton_class_prepend = Module.new do
|
|
|
|
singleton_class.prepend(mod)
|
|
|
|
end
|
|
|
|
|
|
|
|
klass = Class.new(ModuleSpecs::RecordIncludedModules) do
|
|
|
|
include module_with_singleton_class_prepend
|
|
|
|
end
|
|
|
|
|
|
|
|
ScratchPad.recorded.should == klass
|
|
|
|
end
|
|
|
|
|
|
|
|
it "supports super when the module is prepended into a singleton class with a class super" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
base_class = Class.new(ModuleSpecs::RecordIncludedModules) do
|
|
|
|
def self.inherited(base)
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
prepended_module = Module.new
|
|
|
|
base_class.singleton_class.prepend(prepended_module)
|
|
|
|
|
|
|
|
child_class = Class.new(base_class)
|
|
|
|
ScratchPad.recorded.should == child_class
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not interfere with a define_method super in the original class" do
|
|
|
|
base_class = Class.new do
|
|
|
|
def foo(ary)
|
|
|
|
ary << 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
child_class = Class.new(base_class) do
|
|
|
|
define_method :foo do |ary|
|
|
|
|
ary << 2
|
|
|
|
super(ary)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
prep_mod = Module.new do
|
|
|
|
def foo(ary)
|
|
|
|
ary << 3
|
|
|
|
super(ary)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
child_class.prepend(prep_mod)
|
|
|
|
|
|
|
|
ary = []
|
|
|
|
child_class.new.foo(ary)
|
|
|
|
ary.should == [3, 2, 1]
|
|
|
|
end
|
2017-09-14 15:56:33 +00:00
|
|
|
|
|
|
|
describe "called on a module" do
|
|
|
|
describe "included into a class"
|
|
|
|
it "does not obscure the module's methods from reflective access" do
|
|
|
|
mod = Module.new do
|
|
|
|
def foo; end
|
|
|
|
end
|
|
|
|
cls = Class.new do
|
|
|
|
include mod
|
|
|
|
end
|
|
|
|
pre = Module.new
|
|
|
|
mod.prepend pre
|
|
|
|
|
|
|
|
cls.instance_methods.should include(:foo)
|
|
|
|
end
|
|
|
|
end
|
2017-05-07 12:04:49 +00:00
|
|
|
end
|