Introduction
According to The 2022 Airbrake Error Data Report, the most prevalent error within projects is the undefined method, often known as the NoMethodError exception. When a receiver (an object) receives a method that does not exist, an error occurs.
Instead of raising a NoMethodError when someone calls an undefined method on an instance of your class, you want to cut off the method call and do something else with it.
Alternatively, you may be required to explicitly specify a huge (potentially endless) number of methods for a class. You'd rather construct a single method that can respond to an endless number of different method names.
Responding to Calls to Undefined Methods
To overcome the above-discussed problem, we have a solution. Start with defining a missing_method for our class. For your class, define a method missing method. When somebody calls a method that would otherwise throw a NoMethodError, the missing_method method is invoked instead. It receives the symbol of the nonexistent method as well as any arguments passed in.
Here's a class that alters the default error handling when a method is missing.
Code
​​class MyClass
def defined_method
puts"This is a defined method."
end
def missing_method(method, *args)
puts "Sorry, I don't know any #{method} method."
end
end
obj = MyClass.new
obj.defined_method
obj.undefined_method
Output
This is a defined method.
Sorry, I don't know any undefined_method method.
In the next example, we'll provide Fixnum, a missing_method implementation to define an infinite number of additional methods. Fixnum will now respond to any method that looks like "plus_#" and takes no arguments:
Code
class Fixnum
def missing_method(m, *args)
if args.size > 0
raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)")
end
match = /^plus_([0-9]+)$/.match(m.to_s)
if match
puts self + match.captures[0].to_i
else
raise NoMethodError.
new("undefined method '#{m}' for #{inspect}:#{self.class}")
end
end
end
4.plus_5 # => 9
-30.plus_20 # => -10
10.plus_100 # => 110
20.send(:plus_50) # => 70
125.divide_25
# NoMethodError: undefined method 'divide_25' for 125:Fixnum
150.plus_7(10)
# ArgumentError: wrong number of arguments (1 for 0)
The missing_method approach is widely used in delegation circumstances when one object must implement all of another object's methods. Rather than specifying each method, a class implements missing_method as a catch-all and sends "missing" method calls to other objects. The built-in delegate library makes this simple, but here's an example class that delegated practically all of its functions to a string. It should be noted that this class does not subclass String:
Code
class BackwardsString
def initialize(s)
@s = s
end
def missing_method(m, *args, &block)
result = @s.send(m, *args, &block)
result.respond_to?(:to_str) ? BackwardsString.new(result) : result
end
def to_s
@s.reverse
puts s
end
def inspect
to_s
puts s
end
end
The call to Object#send is the interesting part here. This method accepts the name of another method and calls it with the arguments provided. Without even glancing at the method name, we may delegate any missing method call to the underlying string:
s = BackwardsString.new("I'm ahead!") # => !daeha m'I
s.size # => 10
s.upcase # => !DAEHA M'I
s.reverse # => I'm ahead!
s.undefined_method # NoMethodError: undefined method 'no_such_method' for "I'm ahead!":String
The missing_method method can also be used to add syntactic sugar to a class. If one of your class's methods is frequently invoked with a string argument, you can create an object.string a simple way object.method ("string").
A missing_method method can also be defined on a class. This can be used to add syntactic sugar to factory classes. Here's a basic factory class that makes it simple to construct strings (as if it wasn't already straightforward):
Code
class StringFactory
def StringFactory.missing_method(m, *args)
return String.new(m.to_s, *args)
end
end
puts StringFactory.first_string # => "first_string"
puts StringFactory.second_string # => "second_string"
puts StringFactory.superclass # => Object
If we try to call an explicitly defined method, then the missing_method will not be called, as we can see when we call the String.Factory.superclass method we get return value as "object".
The missing_method method intercepts all calls to undefined methods, including calls to "genuine" methods with incorrectly entered names. This is a frequent source of bugs. If you have problems while using your class, the first thing you should do is add debug statements to missing_method or comment it out entirely.
If you’re using missing_method to implicitly define methods, you should also be aware that Object.respond_to? returns false when called with the names of those methods. After all, they’re not defined: