1

I'm using Mash.to_module (from Hashie) to put settings on classes. This works fine, for unit testing my config system I would like to be able to reset this class method. After struggling for 5 hours, I finally found a way to remove the class method settings, but no I can't put it back on... Is there life after undef, or another way to remove a class method? The solution of this question doesn't seem to work. I'm using ruby 2.1.5.

Here is some test code:

class Mash < Hash
    def to_module(mash_method_name = :settings)
      mash = self
      Module.new do |m|
        m.send :define_method, mash_method_name.to_sym do
          mash
        end
      end
    end
end

class B

    class << self

        def reset

            # singleton_methods.include? :settings               # true
            # methods.include? :settings                         # true
            # remove_method :settings                            # method `settings' not defined in #<Class:B>
            # send :remove_method, :settings                     # method `settings' not defined in #<Class:B>
            # singleton_class.remove_method, :settings           # method `settings' not defined in #<Class:B>

            # B.singleton_methods.include? :settings             # true
            # B.methods.include? :settings                       # true
            # B.send :remove_method, :settings                   # method `settings' not defined in #<Class:B>
            # B.singleton_class.send :remove_method, :settings   # method `settings' not defined in #<Class:B>

            # methods.include?( :settings ) and undef :settings  # unexpected keyword undef
            undef :settings if methods.include?( :settings )     # works

        end
    end

end

B.extend Mash.new.to_module
b = B.new

B.settings # {}
# b.settings # undefined method `settings' <- intented behaviour

# B.instance_eval { remove_method :settings } # `remove_method': method `settings' not defined in B
# B.instance_eval { undef :settings } # WORKS !
B.reset

# B.settings # # undefined method `settings' <- GOOD!

B.extend Mash.new.to_module
B.settings # undefined method `settings' => OOPS, is there life after undef? 

1 Answers1

2

Your difficulty is not down to the method being a class method but because the method is defined in a module. Firstly you need to be clear on the difference between remove_method and undef_method.

remove_method removes a method from the class/module that defined it (ie the one that contains the corresponding def or on which define_method was called). If you try and call that method, ruby will still try to search superclasses and included modules. Here remove_method isn't working for you because the receiver is B's singleton class but the method wasn't defined there (it was defined on the anonymous module), hence the error about the method not being defined on the class.

undef_method prevents a class from responding to a method, irrespective of where that method was defined. That's why extending a new module after calling undef_method doesn't work: you've told ruby not to search the ancestors for that method.

What will work however is calling remove_method on the module that you've extended your class with. This will stop that inplementation of settings being used bur won't interfere if the class is extended with another module defines the method.

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174
  • Thank you! That helps me further. But if I understand well, doing what you suggest just affects everything that would use this module, not just the class I try to affect. –  Jul 17 '16 at 09:55
  • Oh, I see, I could change the module to use `extended (base)` to have the method defined directly on the singleton class of the receiver. –  Jul 17 '16 at 09:57
  • It would affect everyone that uses that module, but as you've presented things the module is never reused. – Frederick Cheung Jul 17 '16 at 10:18