开发者

Re-Include Module

开发者 https://www.devze.com 2022-12-27 02:48 出处:网络
I need some like this: module One def test; puts \'Test One\'; end end module Two def test; puts \'Test Two\'; end

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开发者_如何学JAVA 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!


 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"


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.


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


@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
0

精彩评论

暂无评论...
验证码 换一张
取 消