Skip to content

Instantly share code, notes, and snippets.

@balramkhichar
Created December 15, 2014 18:14
Show Gist options
  • Save balramkhichar/5ffcf49a20851eb1d4f3 to your computer and use it in GitHub Desktop.
Save balramkhichar/5ffcf49a20851eb1d4f3 to your computer and use it in GitHub Desktop.
method_missing blog post
What is method_missing ?
When we send a message to an object, it first method it finds on its method lookup path with the same name as message. If it fails to find any method like this then it will raise NoMethodError exception.
method_missing is a known tool in the Ruby meta-programming toolbox. It’s a callback method that gets called when an object tries to call a method that is missing. An example of this is dynamic finders in ActiveRecord. Let's say Member has an email attribute, you can do Member.find_by_email('[email protected]') and the interesting fact is that User and ActiveRecord::Base have not defined it.
example:
class MyClass
def do_ten(x)
10
end
def do_something(task, times)
puts "Did Something"
end
end
class DemoClass
def initialize(obj)
@obj = obj
end
def method_missing(method, *arguments, &block)
puts "Called method #{method}"
@obj.send(method,*args)
end
end
#now let's test this
myclass = MyClass.new
wrapped_class = DemoClass.new(myclass)
wrapped_class.do_something("Something", 5)
wrapped_class.do_ten(100)
wrapped_class.respond_to?(:do_something)
output:
"Called method do_something"
"Did Something"
"Called method do_ten"
10
false
The last result is strange. Isn't it?
Proper way to Implement Method missing
method_missing can be used to do some cool things in Ruby, but it can also problems when done improperly. We should define respond_to? method. It is used to check that an object knows about existance of a method before actually calling it to avoid an error about the missing the method.
example:
require 'ostruct'
class Demo
def user
@_user ||= OpenStruct.new(name: 'Balram', age: 22, occupation: 'developer')
end
def method_missing(method, *arguments, &block)
# the first argument is a Symbol, so we need to_s it if you want to pattern match
if method.to_s =~ /user_(.*)/
user.send($1, *arguments, &block)
else
super
end
end
def respond_to?(method, include_private = false)
method.to_s.start_with?('user_') || super
end
end
demo = Demo.new
demo.user_name
demo.respond_to?(:user_name)
demo.method(:user_name)
Output:
#<Demo:0x00000001e40948>
"Balram"
true
NameError: undefined method `user_name' for class `Demo'
from (irb):23:in `method'
from (irb):23
from /home/balram/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
Oops! what's wrong with the last result?. We can solve "#method" problem using respond_to_missing method provided in Latest Ruby 1.9. When overriding method_missing, We should always define respond_to_missing?.
example:
require 'ostruct'
class Demo
def user
@_user ||= OpenStruct.new(name: 'Balram', age: 22, job: 'developer')
end
def method_missing(method, *arguments, &block)
# the first argument is a Symbol, so we need to_s it to match pattern
if method.to_s =~ /user_(.*)/
user.send($1, *arguments, &block)
else
super
end
end
def respond_to_missing?(method, include_private = false)
method.to_s.start_with?('user_') || super
end
end
demo = Demo.new
demo.user_name
demo.respond_to?(:user_name)
demo.method(:user_name)
Output:
#<Demo:0x00000001e40948>
"Balram"
true
#<Method: Demo#user_name>
Cool. It works. Isn't it cool enough to fall in love with ruby?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment