Frage

I have a couple of modules that extend method missing:

module SaysHello
    def respond_to?(method)
        super.respond_to?(method) || !!(method.to_s =~ /^hello/)
    end
    def method_missing(method, *args, &block)
        if (method.to_s =~ /^hello/)
            puts "Hello, #{method}"
        else
            super.method_missing(method, *args, &block)
        end
    end
end

module SaysGoodbye
    def respond_to?(method)
        super.respond_to?(method) || !!(method.to_s =~ /^goodbye/)
    end
    def method_missing(method, *args, &block)
        if (method.to_s =~ /^goodbye/)
            puts "Goodbye, #{method}"
        else
            super.method_missing(method, *args, &block)
        end
    end
end

class ObjectA
    include SaysHello
end

class ObjectB
    include SaysGoodbye
end

This all works well, eg ObjectA.new.hello_there outputs "Hello, hello_there". Likewise, ObjectB.new.goodbye_xxx outputs "Goodbye, xxx". respond_to? also works, eg ObjectA.new.respond_to? :hello_there return true.

However, this doesn't work very well when you want to use both SaysHello and SaysGoodbye:

class ObjectC
    include SaysHello
    include SaysGoodbye
end

While ObjectC.new.goodbye_aaa works correctly, ObjectC.new.hello_a acts strange:

> ObjectC.new.hello_aaa
Hello, hello_aaa
NoMethodError: private method `method_missing' called for nil:NilClass
    from test.rb:22:in `method_missing' (line 22 was the super.method_missing line in the SaysGoodbye module)

It outputs correctly, then throws an error. Also respond_to? doesn't correctly, ObjectC.new.respond_to? :hello_a returns false.

Finally, adding this class:

class ObjectD
    include SaysHello
    include SaysGoodbye

    def respond_to?(method)
        super.respond_to?(method) || !!(method.to_s =~ /^lol/)
    end


    def method_missing(method, *args, &block)
        if (method.to_s =~ /^lol/)
            puts "Haha, #{method}"
        else
            super.method_missing(method, *args, &block)
        end
    end
end

Also acts strangely. ObjectD.new.lol_zzz works, however ObjectD.new.hello_aand ObjectD.new.goodbye_t both throw a name exception after outputting the correct string. respond_to? also fails for hello and goodbye methods.

Is there a way to get this all working correctly? An explanation of how method_missing, Modules and super are interacting would also be really useful.

EDIT: coreyward solved the problem, if I use super instead of super.<method-name>(args...) in all the methods I define, the program works correctly. I don't understand why this is though, so I asked another question about this at What does super.<method-name> do in ruby?

War es hilfreich?

Lösung

When you redefine a method, you redefine a method; period.

What you're doing when you include the second module with the method_missing method define is overriding the previously defined method_missing. You can keep it around by aliasing it before you redefine it, but you might want to watch out with that.

Also, I don't know why you're calling super.method_missing. Once your method_missing definition is out of tricks you should let Ruby know it can continue on up the chain looking for a way to handle the call, all just by calling super (no need to pass arguments or specify a method name).

About Super (update)

When you call super Ruby continues on up the inheritance chain looking for the next definition of the method invoked, and if it finds one it calls it and returns the response. When you call super.method_missing you call the method_missing method on the response to super().

Take this (somewhat silly) example:

class Sauce
  def flavor
    "Teriyaki"
  end
end

# yes, noodles inherit from sauce:
#   warmth, texture, flavor, and more! ;)
class Noodle < Sauce
  def flavor
    sauce_flavor = super
    "Noodles with #{sauce_flavor} sauce"
  end
end

dinner = Noodle.new
puts dinner.flavor     #=> "Noodles with Teriyaki sauce"

You can see that super is a method just like any other, it just does some magic behind the scenes. If you call super.class here you're going to see String, since "Teriyaki" is a string.

Make sense now?

Andere Tipps

http://www.perfectline.ee/blog/activerecord-method-missing-with-multiple-inheritance

This article explains exactly how it works: Each new module doesn't overwrite or replace methods - instead, its methods are inserted into the inheritance chain. That's why calling super from each method_missing eventually calls all the method_missing's.

The class remains lowest in the inheritance chain, and the last-added module is adjacent to the class.

So:

class Foo
  include A
  include B
end

results in Kernel -> A -> B -> Foo

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top