Reclosing module
I need something like this:
module One def test; puts 'Test One'; end end module Two def test; puts 'Test Two'; end end class Foo include One include Two include One end
In this case, I want the result "Test One", but obviously it returns "Test Two". I need a simple easy way to re-enable my module.
Any suggestion?
Thanks!
a source to share
class Module
def include_again(mod)
mod.instance_methods.each { |m|
self.send(:define_method, m) { |*args|
mod.instance_method(m).bind(self).call(*args)
}
}
end
end
module One
def test(a); puts "test one #{a}"; end
end
module Two
def test; puts "test two"; end
end
class Foo
include One
include Two
end
Foo.new.test #=> "test two"
class Foo
include_again One
end
Foo.new.test(1) #=> "test one 1"
a source to share
I'm not sure about a workaround other than remove_method
, but the reason you showed not working is the way Ruby does a method lookup.
Checking Foo
ancestors
at each step of creation Foo
gives us a big hint:
module One
def test; puts 'Test One'; end
end
module Two
def test; puts 'Test Two'; end
end
class Foo
include One
p ancestors
include Two
p ancestors
include One
p ancestors
end
Output:
[Foo, One, Object, Kernel] [Foo, Two, One, Object, Kernel] [Foo, Two, One, Object, Kernel]
If the module is already in the class pedigree, Ruby does not allow you to re-enable it again. Thus, when Two
included after One
, Two
it appears in the lookup table Foo
before it One
has a chance, no matter how many times you re-include One
.
a source to share
You can customize the include behavior:
module One
def test; puts 'Test One'; end
end
module Two
def test; puts 'Test Two'; end
end
class Foo
@mods = []
def self.include(mod)
@mods.delet mod
@mods << mod
end
def self.new(*args)
super.tap { |o| @mods.each { |m| o.extend m } }
end
include One
include Two
include One
end
a source to share
@banister, thanks a lot for this answer, since I can't comment, I'll add some code here to make it work with blocks as well as arguments:
class Module
def include_again(mod)
mod.instance_methods.each{ |m|
self.send(:define_method, m) { |*args, &block|
mod.instance_method(m).bind(self).call(*args, &block)
}
}
end
end
a source to share