CSC/ECE 517 Fall 2007/wiki1b 2 22: Difference between revisions
Line 3: | Line 3: | ||
= Introduction - method_missing = | = Introduction - method_missing = | ||
The method_missing is a method that called | The method - method_missing - is invoked by Ruby, when an object sent a message that it cannot handle. symbol is the symbol for the method called, and args are any arguments that were passed to it. By default, the interpreter raises an error when this method is called. However, it is possible to override the method to provide more dynamic behavior. | ||
obj.method_missing(symbol [, *args] ) => result | |||
When a method is called, Ruby looks for a same name method. It searches the method in the following sequence - self object’s methods, instance methods of all objects of this class, included modules in this class, superclass, superclass’s included modules, all the way till 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. When a method does not exist, Ruby invokes method_missing as the last resort. The default implementation of method_missing method - throws an | |||
[http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/] | [http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/] | ||
Revision as of 18:53, 6 October 2007
Introduction - method_missing
The method - method_missing - is invoked by Ruby, when an object sent a message that it cannot handle. symbol is the symbol for the method called, and args are any arguments that were passed to it. By default, the interpreter raises an error when this method is called. However, it is possible to override the method to provide more dynamic behavior.
obj.method_missing(symbol [, *args] ) => result
When a method is called, Ruby looks for a same name method. It searches the method in the following sequence - self object’s methods, instance methods of all objects of this class, included modules in this class, superclass, superclass’s included modules, all the way till 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. When a method does not exist, Ruby invokes method_missing as the last resort. The default implementation of method_missing method - throws an [1]
Examples
1. Object composition
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 object "intercepts" all method calls made to it, prints out a message and forwards on the method call to an internal object using the 'send' method, without knowing anything about the object passed to it. It can be used to debug some code without littering it with print statements. [2]
2. Factorial
Let's create a Computer class that contains a factorial method (you know the famous n! thing).
class Computer def factorial n raise ArgumentError if n < 0 f = 1 n.downto(1) do |i| f = f * i end f end end computer = Computer.new puts computer.factorial(4)
We would like to use some notation close to the usual notation:
computer = Computer.new puts computer._4!
Obviously, we cannot create methods for every integer, to do it we use the method_missing.
def method_missing(meth, *args) meth.to_s =~ /_([0-9]*)!/ return super if ! $1 factorial($1.to_i) end
If we use the special notation (_<digits>!) the method_missing implementation extracts the number, from the method name, and calls the factorial method to get the result. Each time and for any method the same processing happens. [3]
3. Enumerator
class Enumerable::Enumerator def method_missing(sym,*args,&blk) each{ |x| x.send(sym,*args,&blk) } end end
The benefit is a number of nice conveniences, eg.
[1,2,3].map + 3 => [4,5,6] [1,2,3].select > 2 => [3] [1,2,3].reject > 1 => [1] [1,2,3].find > 1 => 2
4. Roman numbers
For example, Roman [10] returns "X" and Roman.CLIX returns 159(through method_missing).
module Roman Sorted = [ ["M", 1000], ["CM", 900], ["D", 500], ["CD", 400], ["C", 100], ["XC", 90], ["L", 50], ["XL", 40], ["X", 10], ["IX", 9], ["V", 5], ["IV", 4], ["I", 1] ] Values = Hash[*Sorted.flatten] Values.default = 0 # Needed to cope with bad input and boundary conditions.
# Convert an integer to a Roman numeral. def self.[](dec) raise ArgumentError, dec.to_s if dec.class != Fixnum or dec < 1 Sorted.map { |str, num| str * (x, dec = dec.divmod num)[0] }.join end
# Convert a Roman numeral to an integer. def self.method_missing(id) id = id.id2name dec = (0...id.length).inject(0) { |sum, ix| (n = Values[id[ix, 1]]) < Values[id[ix+1, 1]] ? sum - n : sum + n } raise ArgumentError, "#{id}? #{dec}=#{self[dec]}" if id != self[dec] dec end end
5. Ruby Torrent
RubyTorrent uses method_missing to make all the bencoded fields in the .torrent file easy to access. (bencoding being the BitTorrent encoding scheme.) This is basically identical to the Javascript attributes thing above, but with some type-checking. For example:
class MetaInfoInfoFile def initialize(dict=nil) @s = TypedStruct.new do |s| s.field :length => Integer, :md5sum => String, :sha1 => String, :path => String s.array :path s.required :length, :path end end def method_missing(meth, *args) @s.send(meth, *args) end end
6. Ruby moment of zen
For example, let’s say you have a User model, and that user has a ton of profile data. You want to abstract that data, so you make a Profile class.
class User < ActiveRecord::Base has_one :profile end
class Profile < ActiveRecord::Base belongs_to :user end
However, you don’t want to call user.profile.street_address every time you need to access an attribute in a user’s profile. You also don’t want to define a bunch of reader methods like this:
def street_address profile.street_address end
Here’s all you have to do:
class User < ActiveRecord::Base ... # Attempts to pass any missing methods to # the associated +profile+. def method_missing(meth, *args, &block) if profile.respond_to?(meth) profile.send(meth) else super end end end
So, we now have this:
@user.street_address == @user.profile.street_address
7. 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
- 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.
References
- 10 things you should know about method_missing.http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/
- More Ruby: method_missing http://blog.mauricecodik.com/2005/12/more-ruby-methodmissing.html
- Hey Ruby, how much for the method_missing? http://www.alef1.org/ruby/method_missing/index.html
Further reading
- Evaluation Options in Ruby. http://www.infoq.com/articles/eval-options-in-ruby
- IF YOU BUILD IT .. will they come. http://www.ddj.com/blog/architectblog/archives/2007/08/dependency_inje.html
External links
- http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/
- http://blog.mauricecodik.com/2005/12/more-ruby-methodmissing.html
- http://www.alef1.org/ruby/method_missing/index.html
- http://www.ruby-forum.com/topic/112986
- http://redhanded.hobix.com/inspect/theBestOfMethod_missing.html
- http://cleanair.highgroove.com/articles/2006/07/04/ruby-moment-of-zen-method_missing