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?
method_missing is a method that ruby gives you access inside of your objects a way to handle situations when you call a method that doesn't exist. It's sort of like a Begin/Rescue, but for method calls. It gives you one last chance to deal with that method call before an exception is raised.
Creating Modules in Ruby To define a module, use the module keyword, give it a name and then finish with an end . The module name follows the same rules as class names. The name is a constant and should start with a capital letter. If the module is two words it should be camel case (e.g MyModule).
As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons.
The module methods & instance methods can be accessed by both extend & include keyword. Regardless of which keyword you use extend or include : the only way to access the module methods is by Module_name::method_name.
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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With