CSC/ECE 517 Fall 2011/ch3 3i ws: Difference between revisions
No edit summary |
|||
Line 24: | Line 24: | ||
end | end | ||
end | end | ||
This usage is a pretty common and valid use of the functionality. The only thing you have to be careful about is to not introduce any recursive calls to method_missing. If you forget to require YAML in the above example, the error would be a stack overflow. | |||
===Other patterns of method_missing=== | ===Other patterns of method_missing=== |
Latest revision as of 03:51, 16 October 2011
Introduction
Method_missing is very powerful and widely used in Ruby on Rails. As one of the dynamic features of Ruby, it is a way of intercepting calls to methods that haven't been defined, which would otherwise raise a NoMethodError. The functionality of method_missing is one of the core ingredients of cleanly designed Ruby programs.<ref>Method Missing http://contextr.rubyforge.org/test/method_missing.html</ref> It is still possible to extend it with context-dependent behavior.
This feature is not unique to Ruby. It also exists in Smalltalk, Python, Groovy, some JavaScripts, and even most CLOS extensions have it.
Method_missing gives Ruby objects a way to respond not just to finite numbers of predefined methods, but to handle entire groups of calls of different types. It is therefore used in many ways, from customizing debug information to generating new domain specific languages.
Implementation
When we send a message to an object, the object executes the first method it finds on its method lookup path with the same name as the message. If it fails to find any such method, it raises a NoMethodError exception - unless you have provided the object with the method_missing.<ref>Ruby Method Missing http://rubylearning.com/satishtalim/ruby_method_missing.html</ref>
Method_missing is defined in the Object class. It is very simple to use, simply override it in the subclass where it is needed. The method takes one, two, or three parameters: the symbol of the non-existent method, an array of the arguments that were passed in the original call and any block passed to the original method.
Adding better debug information on failure
One of the most simple but still very powerful ways of using method_missing is to allow it to include more information in the error message. Here is a simple example:<ref>Patterns of method missing http://ruby.dzone.com/news/patterns-method-missing</ref>
class MyFoo def method_missing(method, *args, &block) raise NoMethodError, <<ERRORINFO method: #{method} args: #{args.inspect} on: #{self.to_yaml} ERRORINFO end end
This usage is a pretty common and valid use of the functionality. The only thing you have to be careful about is to not introduce any recursive calls to method_missing. If you forget to require YAML in the above example, the error would be a stack overflow.
Other patterns of method_missing
There are many other patterns of method_missing. For example, using Ruby’s blocks and method_missing to make it easy to create any kind of output structure, like XML, HTML, graphical UIs and other hierarchical data structures, to lend themselves very well to the builder pattern.<ref>http://olabini.com/blog/category/ruby/</ref> By using method_missing, all kinds of test helpers are able to be created. The test helpers can be used to implement factories, delegate and do all kinds of things.
Examples
Roman Numerals
The classic example for method_missing, given in the Ruby Docs:<ref>Ruby Docs: Object.missing_method http://ruby-doc.org/docs/ProgrammingRuby/html/ref_c_object.html#Object.method_missing </ref>
class Roman def romanToInt(str) # ... end def method_missing(methId) str = methId.id2name romanToInt(str) end end
Usage:
r = Roman.new r.iv » 4 r.xxiii » 23 r.mm » 2000
The Roman class has no .iv or .xxiii methods, but the method_missing method turns the message into a string with id2name (which takes symbols and returns them as strings), and passes it to the romanToInt. This example shows how method_missing can make the Ruby language very flexible.
method_missing in Rails
Rails, like many domain specific languages of Ruby, makes heavy use of the method_missing function. A prominent example is ActiveRecord, where dynamic finders are implemented using method_missing. When a call like find_by_username is called from an ActiveRecord object, the method does not explicitly exist, rather the message is picked apart in ActiveRecord's Base::method_missing method. The variable part of the message, in this case "username" is turned into an argument to the find method, which is defined. The code below is not exactly what is in the method itself, but outlines what actually happens after calls to helper other methods:<ref>How dynamic finders work http://blog.hasmanythrough.com/2006/8/13/how-dynamic-finders-work </ref>
def method_missing(method_id, *arguments) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) # find... elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s) # find_or_create... else super end end
Considerations
Drawbacks
All else being equal, method_missing is less efficient at runtime than normally defined methods<ref> method_missing: price to pay you http://www.alef1.org/ruby/method_missing/ </ref>. Ruby looks for it in the receiving object's class first. If it doesn't find the method there, it looks for it in the superclass, and then the superclass and the superclass, and so on, until it eventually reaches the top of the chain (Object in Ruby 1.8, BasicObject in Ruby 1.9).<ref>[3] method_missing in Ruby. Does it make execution of functions in a Rails app any faster. http://www.coderanch.com/t/489410/Ruby/Method-missing-Ruby-Does-it</ref> Only then, if it still didn't find the method, does Ruby get down in the receiver's class again to look for a method_missing. So, method_missing is generally slower than a regular method.Besides the fact that the method_missing will need additional checks to determine the next course of action, method_missing is always the last place the ruby interpreter looks.
Method_missing can make the code more difficult for humans to follow. It is also does not work well with autocomplete and other features of IDE's or editors.
All of these things should be considered when deciding whether to rely on method_missing.
Relationship to respond_to?
Ruby objects also have a method called respond_to? which checks to see that a given message corresponds to a method of the class. By default, it will only look for explicitly defined methods. If none are found, it has no mechanism to check whether it is handled by method_missing. This issue is important to consider if the class or its children ever need the respond_to? method. It is good practice to always override the respond_to? method when using method_missing in a class that might be subclassed. In the stub below, we add a respond_to to the Roman Numerals class.
class Roman def respond_to? if method_sym.to_s =~ ... // regex to see if it's a valid roman numeral true else super end end end
Similar functionality in other languages
The method_missing class is defined in the Object class, so it is inherited by all objects. It raises a NoMethodError by default.
There are no similar methods in statically typed languages like Java or C++. Python does not have an equivalent method either, though it can be simulated using the __getattr__ method <ref>Various imlementations in other languages http://langexplr.blogspot.com/2008/02/handling-call-to-missing-method-in_06.html </ref>. Firefox's JavaScript implementation now supports a similar __noSuchMethod__ object <ref>Javascript documentation https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/noSuchMethod</ref> method, but it is not a JavaScript standard.
References
<references/>