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!

+2


a source to share


4 answers


 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"

      



+2


a source


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

.

0


a source


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

      

0


a source


@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

      

0


a source







All Articles