Nino55
Nino55

Reputation: 61

Re-Include Module

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!

Upvotes: 2

Views: 627

Answers (4)

Lumean
Lumean

Reputation: 1

@banister, many thanks for that answer, since I can't comment I'll add here the code 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

Upvotes: 0

horseyguy
horseyguy

Reputation: 29905

 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"

Upvotes: 2

Konstantin Haase
Konstantin Haase

Reputation: 25964

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

Upvotes: 0

Mark Rushakoff
Mark Rushakoff

Reputation: 258208

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.

Upvotes: 0

Related Questions