Ruby - Understanding method_missing
10 Dec 2015Everytime you call a method in a ruby object ruby tries to find this method definition in all hierarchy,
for example calling the to_s
method of Fixnum
makes ruby to search it in Fixnum
and Object
.
In case of ruby doesn’t find the method it will be caught by method_missing method declared in BasicObject
.
class A
end
a = A.new
a.testing # NoMethodError: undefined method `testing' for #<MyTest:0x007f90392568b0>
Considering that we can redefine the method_missing in our class we could change this error message like this.
class A
def method_missing(method_name, *args, &block)
puts "#{method_name} with #{args.length} arguments could not be found"
end
end
a = A.new
a.testing # testing with 0 arguments could not be found
a.testing(1, 2, 3) # testing with 3 arguments could not be found
Now lets create something more complex and useful, lets create a class to represent our enviroment, also we want to execute some code only in a specific enviroment. We could create a method that receives the enviroment and a block of code
class Enviroment
def initialize(env)
@env = env
end
def on_env(env)
if env == @env
yield
end
end
end
And use this way
enviroment = Enviroment.new "development"
# now we can call any on_<enviroment>
enviroment.on_env "production" do
puts "you are in production env"
end
enviroment.on_env "development" do
puts "you are in development env"
end
Or we can use the method_missing to create a more dynamic, DSLic implementation
class Enviroment
def initialize(env)
@env = env
end
def method_missing(method_name, *args, &block)
# lets check if the called methods starts with on_<env>
if match = /on_(\w+)/.match(method_name.to_s)
# if yes we can execute the block given
block.call if match[1] == @env
else
# otherwise we just delegate to super.method_missing
super
end
end
end
Now instead of pass the enviroment we need only call a method called on_env.
enviroment = Enviroment.new "development"
# now we can call any on_<enviroment>
enviroment.on_production do
puts "you are in production env"
end
enviroment.on_development do
puts "you are in development env"
end
References
- BasicObject: ruby-doc.org/core-2.1.0/BasicObject.html.
- Regexp: ruby-doc.org/core-2.1.0/Regexp.html