How can I reverse ruby's include function

2019-01-23 13:00发布

问题:

I'll explain what i'm looking for in code as thats probably the most succinct:

module Mixin
  def method
    puts "Foo"
  end
end

class Whatever
  include Mixin
end

w = Whatever.new
w.method
=> "Foo"

# some magic here
w2 = Whatever.new
w.method
=> NoMethodError

I had tried just undefining the Mixin module using remove_const, but this doesn't seem to make any difference to Whatever. I had assumed that #include just added a reference to the module into the class's method resolution chain - but this behaviour doesn't agree with that.

Can anyone tell me what include actually does behind the scenes, and how to reverse this?

回答1:

module Mod
  def foo
    puts "fooing"
  end
end

class C
  include Mod
  def self.remove_module(m)
    m.instance_methods.each{|m| undef_method(m)}
  end
end

>> c = C.new
>> c.foo
fooing
>> C.remove_module(Mod)
=> ["foo"]
>> c.foo
NoMethodError: undefined method `foo' for #< C:0x11b0d90>



回答2:

As it seems probably you want to accomplish these on instances instead of the whole class, so I would change klochner's code a bit to handle just one instance instead of all the instances of a class.

module ModuleRemover

    def remove_module(mod, options = {})
      metaclass = class << self; self end
      mod.instance_methods.each {|method_name| metaclass.class_eval { undef_method(method_name.to_sym) }}
    end

end

As Mladen pointed out, it would be cool to avoid removing methods that are overwritten on the host class, so an [only, exclude] options for this method would be ideal.

>> c1 = C.new
>> c1.foo
=> fooing
>> c1.extend(ModuleRemover)
>> c1.remove_module(Mod)
>> c1.foo
=> NoMethodError: undefined method `foo' for #< C:0x11b0d90>
>> c2 = C.new
>> c2.foo
=> fooing


回答3:

I'm not sure what you're trying to accomplish, but perhaps instead of using include to add instance methods, what you want to do is use extend to add methods just to particular instances of the class, then you wouldn't need to remove them.

More information on the difference between include and extend



回答4:

Some years ago I used the gem evil for un-including modules etc., but apparently it is no longer maintained. So I just tried un instead (only on my old ruby 1.8.7). Worked fine as advertised:

DESCRIPTION:

un provides unextend and uninclude to allow for a better prototype-oriented programming experience.

If you replace your "# some magic here" (after installing un) by

require 'un'
Whatever.uninclude Mixin

you get the behavior as described by you - almost. Object has already a method called method, so you get a "wrong number of arguments" error instead.

It would be nice if someone tries it on ruby 1.9 or on jruby and reports the results (I make the answer community wiki for this).