Created
December 15, 2014 18:14
-
-
Save balramkhichar/5ffcf49a20851eb1d4f3 to your computer and use it in GitHub Desktop.
method_missing blog post
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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