#---method_missing---
When you call a method on an object, Ruby will search
for it in a bunch of places before
finally throwing in the towel and calling the aptly
named method: method_missing
.
method_missing is a private method defined in the top-level Basic_Object class. When ruby calls it, it simply raises a NoMethodError exception to let you, the user, know that the method you called does not exist.
class Dog
end
d = Dog.new
## raises NoMethodError without rescue nil
p d.do_you_speak_english? rescue nil
You can get the exact same effect by calling method_missing directly on the object:
p d.send(:method_missing, :do_you_speak_english?) \
rescue nil
(We have to use send because method_missing is a private method.)
What makes it special are 2 things:
class Dog
def method_missing(method_name, *args, &blk)
res = "You called method: #{method_name}\n"
res += "with args: #{args.join(',')}\n" if args.any?
res += "with a block whose result is: #{yield}" \
if block_given?
res
end
end
d = Dog.new
puts d.do_you_speak_english?('yes', 'no') { 'maybe' }
## => "You called method: do_you_speak_english?"
## "with args: yes,no"
## "with a block whose result is: maybe"
p d.say_something!
## => "You called method: say_something!"
This is how Rails’ ActiveRecord models work under the hood for methods such as find_by_name or find_by_name_and_age where name and age are database columns. Rails just overrides method_missing to handle these method calls and return the appropriate results.
Without super:
class Dog
def method_missing(method_name, *args, &blk)
if method_name.end_with?('?')
true
end
end
end
d = Dog.new
p d.bark? # => true
But what happens if you call a method that doesn’t end with ’?’ that’s not defined in the class elsewhere? You’d expect it to raise a NoMethodError, right? See:
p d.speak # => nil
That’s not right. This gives the impression that the dog can understand the speak command but it just returns nil for some reason. Instead, it should really be blowing up with a NoMethodError because it doesn’t understand the speak command at all.
This is because our method_missing method above,
only handles methods that end with ’?’. And for all
others, it just doesn’t do anything. That’s why nil is
returned.
Instead, we should be raising a NoMethodError in those
cases.
But a better way is to just call super
which
makes this the responsibility of the original
method_missing method defined in BasicObject
:
class Dog
def method_missing(method_name, *args, &blk)
if method_name.end_with?('?')
true
else
super # Add this
end
end
end
p d.bark? # => true
## => raises NoMethodError without rescue, as expected
p d.speak rescue nil
While all dog objects can now respond to any question-mark methods, do they say so?
p d.respond_to?(:bark?) # => false
Oops. But it does, doesn’t it?
p d.bark? # => true
respond_to?(method_name, include_private = false)
is
a method in Object
class. So it’s available in all the
objects of classes you define. It returns true if the
object can respond to the method. This is useful to
write code without lots of exception handling.
In order for respond_to? to return true for the methods you handle in method_missing, you need to override respond_to_missing? method as well, usually with the same logic as in method_missing.
So, for our example above:
class Dog
def respond_to_missing?(method_name, \
include_private = false)
method_name.end_with?('?') || super
end
end
Now the dog knows how to respond to bark?
p d.respond_to?(:bark?) # => true