CSC/ECE 517 Fall 2012/ch1 1w22 an
Introduction
When we define a method in a class and decide to call that method, how do we do it?
We simply create an object of the class and pass the method name to the object as a message. The object then looks up into its method lookup path and tries to match the called method (passed as a message to the object) with the defined methods in the class. When there is a match, the method is executed along with the parameters passed and the result is returned.
What is a Method Lookup Path?
When the object receives a method name that is to be executed, these are the steps carried out for finding out that method called:
- It looks in the current self object’s own instance methods.
- Then it looks in the list of instance methods that all objects of that class share.
- Then in each of the included modules of that class, in reverse order of inclusion.
- Then it looks in that class’s superclass.
- Then in the superclass’s included modules, all the way up until it reaches the class Object.
- If it still can’t find a method, the very last place it looks is in the Kernel module, included in the class Object.
- Finally, it calls method_missing (if defined in the class), else throws up the NOMethodError exception.
This entire tracing that the object does is the method lookup path.
What is method_missing?<ref>http://rubylearning.com/satishtalim/ruby_method_missing.html</ref>
Now suppose that the object does not find a matching method in its method lookup path i.e there is no such method defined in the class. Then what?
In normal circumstances the NoMethodError Exception is raised .
Here is where the method_missing comes into picture. The name “method_missing” should be self explanatory that it is invoked when a method is not found. It is a method of last resort. This method accepts the name of the non-existing method, the array of arguments passed and also any associated block to the method.
The format for defining method_missing
=> def method_missing(m,*args,&block)
There are 3 arguments: (i) m-> Takes the symbol/name of the undefined method (ii) *args-> Takes the array of arguments passed in the method call (iii) &block-> Takes any block passes to the method
Examples
simple illustration
class A // creating a class A def say // defining a method say puts " say Hi " // body of method say end end
Now, creating the object of the class
a=A.new // object of the class => #<A:0x2a082e0> //object id
Calling the defined method
a.say // defined method => say Hi // returned result
Calling the undefined method
a.sayhi // undefined method sayhi NoMethodError: undefined method `sayhi' for #<A:0x2a082e0> // the NoMethodError is raised
method_missing implementation
class A def say puts " say hi " end def method_missing(m,*args,&block) // defining method_missing puts " This method does not exist" // body of method_missing end end
Calling a method that is not defined
a=A.new a.sayhi // calling the undefined method sayhi with no arguments => This method does not exist // this result returned when method_missing is executed
Explanation: When the object 'a' traces its method lookup path for a matching method as 'sayhi', after a failure it resorts to method_missing and the body of method_missing is executed.
Note: There is something interesting that programmers do. Sometimes when a class has many methods that do generally the same kinds of things, and the programmer is not sure in advance which methods will the user call since there are so many of them, and they are all so similar, implementing all of them by hand seems futile. In these situations method_missing makes a new method that was previously not defined and adds it to the class ; or it just does what needs to be done, this is in the hands of the programmer. You can look at the Generic Handler example for this.
Now, let us look into a few more examples to get the concept right.
passing parameters in an undefined method call
class A def add(a,b) a+b end def method_missing(name,*args,&block) // the method_missing is defined and the *args parameter accepts all the parameters passed during the method call puts “You have typed the method name wrong and these were the parameters passed ; #{args[0]}, #{args[1]}” end end
the passed parameters are stored in the array 'args' and can be accessed like a normal array
Calling the defined method
a.add(1,2) // calling the defined method add and passing the parameters (1,2) => 3 // result
Calling the undefined method
a.adds(4,2) // calling the undefined method adds and passing the parameter (4,2) => You have typed the method name wrong and these were the parameters passed; 4, 2
Explanation: There is a genuine mistake that the user instead of add has typed in adds and this method is not defined. Here when the adds method with parameters is called, the object 'a' tries to look up the method in the method lookup path. When upon failure it invokes method_missing then the args passed in the 'adds' method are stored in the array “args”. It then executes the body of method_missing making use of the parameters.
converting numbers from roman representation to integer representation
class Roman @@Roman_to_Numeric = {'i' => 1, 'v' => 5, 'x' => 10, 'l' => 50, 'c' => 100, 'd' => 500, 'm' => 1000} def method_missing(method_var,*args,&block) numeric_value = 0 roman_string = method_var.to_s.downcase for i in 0...roman_string.length-1 if (@@Roman_to_Numeric[roman_string[i]]-@@Roman_to_Numeric[roman_string[i+1]] == 0 || @@Roman_to_Numeric[roman_string[i]]- @@Roman_to_Numeric[roman_string[i+1]] == -9 || @@Roman_to_Numeric[roman_string[i]]-@@Roman_to_Numeric[roman_string[i+1]] == -4 || @@Roman_to_Numeric[roman_string[i]]-@@Roman_to_Numeric[roman_string[i+1]] >= 4) && (/.v.x/ =~ roman_string) == nil else puts "Roman string is invalid" return end end while roman_string != "" if roman_string[roman_string.length - 1] == 'x' && roman_string[roman_string.length - 2] == 'i' numeric_value += 9 roman_string.chop! roman_string.chop! elsif roman_string[roman_string.length - 1] == 'v' && roman_string[roman_string.length - 2] == 'i' numeric_value += 4 roman_string.chop! roman_string.chop! else numeric_value += @@Roman_to_Numeric[roman_string[(roman_string.length)-1]] roman_string.chop! end end puts "Numeric Value for #{method_var} is: #{numeric_value}" end end
calling the undefined methods
r= Roman.new r.vii r.iClx r.xxix r.xxxi r.xxiv r.xxvi r.vx
The Output:
Numeric Value for vii is: 7 Roman string is invalid Numeric Value for xxix is: 29 Numeric Value for xxxi is: 31 Numeric Value for xxiv is: 24 Numeric Value for xxvi is: 26 Roman string is invalid
method_missing to log method calls
Another application that makes use of method_missing could be a simple logger used for debugging purposes. Many times, it may be required to log the trace of called methods and provide information such as: called method-name, arguments, return type. It can be tedious to repeat this part of code in every method. So, a simple solution to this problem can be obtained using method_missing as:
class SimpleCallLogger def initialize(o) @obj = o end def method_missing(methodname, *args) puts "called: #{methodname}(#{args})" a = @obj.send(methodname, *args) puts "\t-> returned: #{a}" return a end end
EXPLANATION: This program makes use of method_missing in a way that it wraps around called method to output the logging information on entry and on exit, it logs the return type. Further, method_missing intercepts the method call and forward it to internal object with ‘send’ method of ruby. Hence, this use of method_missing acts as wrapper.
Generic Handler
class NoBar def method_missing(methodname, *args) define_method(:bar) if "bar" == methodname.to_s define_method(:nobar) if "nobar" == methodname.to_s end end
This is an example of using method_missing as a generic handler to handle when a calling method is not exist. You can use missing_method to dynamically create a method at a runtime.
Advantages of method_missing:
- In addition to specifying the error messages for the methods that are not defined, method_missing provides a more dynamic behavior in the programming environment.
- If we are unfamiliar with the usage of the object we created but it must support unexpected method calls, then using method_missing is a good option.
- Allows to catch problem at runtime.
- Allows to define a generic method_missing and handle any undefined method. This is a big advantage over Java. In Java, when you call an undefined method, the program will not compile.
- The use of method_missing falls under the general technique of meta-programming. You can employ meta-programming in missing_function to write a another function to handle the call.
Disadvantages of method_missing:
- It is slower than conventional method lookup. Simple tests indicate that method dispatch with method_missing is at least two to three times as expensive in time as conventional dispatch.
- Since the methods being called never actually exist—they are just intercepted at the last step of the method lookup process—they cannot be documented or introspected as conventional methods can.
- Because all dynamic methods must go through the method_missing method, the body of that method can become quite large if there are many different aspects of the code that need to add methods dynamically.
- Using method_missing restricts compatibility with future versions of an API. Once you rely on method_missing to do something interesting with undefined methods, introducing new methods in a future API version can break your users' expectations.
Things to remember:
- Ruby knows that method_missing( ) is there, because it's a private instance method of BasicObject that every object inherits. The BasicObject#method_missing( ) responds by raising a NoMethodError. Overriding this method_missing( ) allows you to call methods that don't really exist. If your method_missing method is only looking for certain method names, don't forget to call super if you haven't found what you're looking for, so that the other superclass method_missing can handle it.
- obj.respond_to? function returns true if the obj responds to the given method. So if you want to know whether your class will respond to a function you can use respond_to? to know the answer. But if method_missing() is used the output may not be what you expected.
- Take the case of the below program. Class A defines only method method_missing() and no other method. Now when a new object is created for class A and when that tries to access a method say, foo, the respond_to? will return the value false. But when you try to actually implement it using “a.foo”, the code will get executed courtesy of method_missing(). So even though the respond_to? says that you cannot access foo function using class A object, if you have method_missing() defined you can access the method.
class A def method_missing(method_id) puts "In method_missing" end end
a = A.new puts a.respond_to?(:foo) a.foo
Output:
false In method_missing
- What if within the method_missing() we define an undefined method? Then what is the result expected? Try to run this code and see what is the result:
class A @@i = 0 def method_missing(method_id) puts "In Method Missing #{@@i}" @@i += 1 self.fun end end
a = A.new puts a.respond_to?(:foo) // just checking whether the receiver 'a' responds to the method 'foo'
even though the result is false the execution will proceed normally
a.foo
output: The expected result would be an "stack level too deep" error.
Explanation: when the “foo” method is called upon the object, the method_missing is run and in this block you have a method “self.fun” that is undefined (its a random name that we gave). Here when the program execution encounters “self.fun” it again calls upon method_missing and this goes on in an endless loop till the stack is full.
References
- http://expertiza.csc.ncsu.edu/wiki/index.php/CSC/ECE_517_Fall_2007/wiki1_2_p2
- http://blog.jayfields.com/2008/02/ruby-replace-methodmissing-with-dynamic.html
- http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/
- http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/
- http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html
- http://expertiza.csc.ncsu.edu/wiki/index.php/CSC/ECE_517_Fall_2007/wiki1b_2_Method_Missing