28

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?

Glenjamin
  • 7,150
  • 6
  • 25
  • 26
  • 1
    The reason for wanting this is because a gem we're writing for merb will be replacing the existing form helpers, and we need to remove those in some way. Thus I cant really alter how the first module is included, only what can be done afterwards. – Glenjamin Mar 09 '10 at 10:12
  • 1
    Possible duplicate of [Is it possible to reverse the included module in a class?](http://stackoverflow.com/questions/1019477/is-it-possible-to-reverse-the-included-module-in-a-class) – lulalala Mar 04 '16 at 01:37

4 Answers4

5

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
Roman Gonzalez
  • 1,901
  • 13
  • 18
  • is there a way to do this without removing the methods called by the same name all the way up the inheritance chain? changing undef_method to remove_method doesn't work for me – Sv1 Dec 22 '17 at 06:03
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

ghoppe
  • 21,452
  • 3
  • 30
  • 21
3
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 #&lt C:0x11b0d90>

klochner
  • 8,077
  • 1
  • 33
  • 45
  • 3
    Wouldn't there be a problem if a method with the same name already existed in class `C` before including module `Mod`? – Mladen Jablanović Mar 08 '10 at 17:40
  • sure. Assuming Mod uses alias_method to overwrite the former methods, you could revert the overwritten methods, but I think that level of detail was overkill for the question. – klochner Mar 08 '10 at 17:49
  • 1
    After further testing, neither undef_method or remove_method actually does what I need. – Glenjamin Mar 09 '10 at 12:27
  • 6
    I vote against it. It's a bad technique and should not be used. You don't even remove the module from `#included_modules` array and there's the risk of losing methods. There's probably no good answer for this question. – farnoy Jan 12 '13 at 16:30
  • @farnoy what do you think is the better solution to this question? – Iván Cortés Aug 13 '19 at 19:13
0

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).

j.p.
  • 1,031
  • 1
  • 18
  • 25