views:

109

answers:

3
+1  Q: 

Re-Include Module

Hello,

I need some 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 need as a result 'Test One' but obviously it returns 'Test Two'. I need a clean simple way for re-include my module.

Any suggestion?

Thanks!

+1  A: 
 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"
banister
What's happen if my method test has arguments ?
Nino55
updated answer for arguments to `test` methods
banister
A: 

I'm not precisely sure of a workaround other than remove_method, but the reason why what you've shown doesn't work is the way that Ruby performs method lookup.

Inspecting Foo's ancestors during each step of Foo's creation 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 a module is already in a class's ancestry, Ruby doesn't allow you to re-include it again. Thus, when Two is included after One, Two occurs in Foo's lookup table before One has a chance, no matter how many times you re-include One.

Mark Rushakoff
remove_method won't work because the method isn't defined on `Foo` but in a module included by `Foo` (and i assume you dont wnat to remove the method from the module ;) )
banister
A: 

You could adjust the behavior of include:

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
Konstantin Haase
i like the approach, but seems like an abuse of `tap` hehe.`o = super``@mods.each { |m| o.extend m }`
banister
Yeah, that's to write it in one line. it's like smalltalk's 'yourself'. What is the alternative (non-abusive) use case of tap?
Konstantin Haase
@Konstantin, i haven't used #tap much myself, but the guys i've spoken to say its only real use case is for debugging (they said they would not use it in production code).
banister