CSC/ECE 517 Fall 2012/ch1 1w22 an: Difference between revisions
No edit summary |
No edit summary |
||
Line 203: | Line 203: | ||
When the 'foo' method is called, after no method match the method_missing is run and this block has a method “self.fun” that is undefined. Here when the program execution encounters 'self.fun' it once again calls method_missing. This goes on in an endless loop till the stack memory is full. | When the 'foo' method is called, after no method match the method_missing is run and this block has a method “self.fun” that is undefined. Here when the program execution encounters 'self.fun' it once again calls method_missing. This goes on in an endless loop till the stack memory is full. | ||
*Ruby knows method_missing( ) exists, because it's a private instance method of 'BasicObject' that every object inherits. The BasicObject#method_missing( ) responds by raising the NoMethodError. Overriding this method_missing( ) allows you to call methods that don't really exist. | *Ruby knows method_missing( ) exists, because it's a private instance method of 'BasicObject' that every object inherits. The BasicObject#method_missing( ) responds by raising the NoMethodError. Overriding this method_missing( ) allows you to call methods that don't really exist. |
Revision as of 02:44, 20 September 2012
Introduction<ref>http://rubylearning.com/satishtalim/ruby_method_missing.html</ref>
A method that belongs to a class is called by creating an object of the class and passing the method name to the object as a message. The object then looks up the method lookup path and tries to match the called method with the defined methods in the class. On success, the method is executed and the result is returned.
If the object does not find a match in its method lookup, in normal circumstances the NoMethodError Exception is raised .
In cases where the user wants to handle the methods which are not defined but are still called, “method_missing” can be defined and the user can handle the methods as he/she sees fit.
Format for Defining method_missing
=> def method_missing(m,*args,&block)
(i) m-> accepts the symbol/name of the undefined method (ii) *args-> accepts the array of arguments passed in the method call (iii) &block->accepts a block passed to the method
Ruby Method Lookup Flow
When the object of a class receives a method name to be executed, the following steps are carried out for matching and executing the method:
- First, the object looks in its own instance methods.
- Second, it looks in the list of instance methods that all objects of that class share.
- Third, in each of the included modules of that class, in reverse order of inclusion.
- Fourth, it looks in that class’s superclass.
- Fifth, in the superclass’s included modules, all the way up until it reaches the class Object.
- Sixth, 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.
Examples
Calling Defined and Undefined Methods
class A // creating a class 'A' def say // defining a method 'say' puts " say Hi " // body of method say end end
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<ref>http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/</ref>
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
When the object 'a' traces its method lookup path for a matching method 'sayhi', upon failure it resorts to method_missing and the body of method_missing is executed.
Sometimes when a class has many methods that do generally the same kinds of functionality, and the programmer is not sure in advance which methods the user will call since there are so many of them, and all of them are similar, writing code for all of the methods seems futile. In these situations method_missing can be defined to take care of these cases. The below 'Generic Handler' example implements this.
Passing Parameters to 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
The user made a genuine mistake by typing 'adds', but this method is not defined. When the 'adds' method with parameters is called, the object 'a' tries to match the method in the method lookup path. Upon failure it invokes method_missing, the args are passed, stored in the array 'args' and the body of method_missing is executed.
Converting Numbers from Roman Representation to Integer Representation<ref>http://courses.ncsu.edu/csc517/common/lectures/notes/wk2.pdf</ref>
class Roman DIGITS = {'I'=>1,'V'=>5,'X'=>10,'L'=>50,'C'=>100,'D'=>500,'M'=>1000,} def roman_to_integer(roman_string) last = nil roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit| if digit_value = DIGITS[digit] if last && last > digit_value memo -= digit_value else memo += digit_value end last = digit_value end memo end end
def method_missing(method) str=method.id2name roman_to_integer(str) end end
Calling the undefined methods
r= Roman.new r.vii r.xxix r.xxiv r.xxvi
The Output
=> 7 => 29 => 24
method_missing to Log Method Calls<ref>http://expertiza.csc.ncsu.edu/wiki/index.php/CSC/ECE_517_Fall_2007/wiki1b_2_22</ref>
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. 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
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 undefined methods, method_missing provides a more dynamic behavior in the programming environment.
- If we are unfamiliar with the usage of the objects we created, then using method_missing is a good technique.
- Handles problems at runtime.
- Define's a generic method_missing and handle's any undefined method, a big advantage over Java. In Java, when you call an undefined method, the program will not compile.
- method_missing falls under the general technique of meta-programming. Employ meta-programming in missing_function to write an another function to handle the call.
Disadvantages of method_missing
- 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.
- method_missing restricts compatibility with future versions of an API. Introducing new methods in a future API version can break users' expectations.
Key Points
- In the following example, if within method_missing() we define an undefined method.
class A @@i = 0 def method_missing(method_id) puts "In Method Missing #{@@i}" @@i += 1 self.fun end end
a = A.new a.foo
Output
The result is a 'stack level too deep' error.
When the 'foo' method is called, after no method match the method_missing is run and this block has a method “self.fun” that is undefined. Here when the program execution encounters 'self.fun' it once again calls method_missing. This goes on in an endless loop till the stack memory is full.
- Ruby knows method_missing( ) exists, because it's a private instance method of 'BasicObject' that every object inherits. The BasicObject#method_missing( ) responds by raising the NoMethodError. Overriding this method_missing( ) allows you to call methods that don't really exist.
- If method_missing is only looking for certain method names, don't forget to call the super keyword 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 expect.
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
References
<references/>
Further Suggested Reading
- http://www.rubyinside.com/
- https://www.youtube.com/watch?v=SAEiCkixrdE&feature=relmfu
- http://blog.jayfields.com/2008/02/ruby-replace-methodmissing-with-dynamic.html
- http://liquiddevelopment.blogspot.com/2006/04/twisting-and-shaping-dsls-using-ruby.html
- http://blog.rubybestpractices.com/posts/gregory/031-issue-2-method-lookup.html
- http://phrogz.net/RubyLibs/RubyMethodLookupFlow.pdf(Diagram)