CSC/ECE 517 Fall 2007/wiki1b 2 22: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
Line 56: Line 56:
[http://www.alef1.org/ruby/method_missing/index.html]
[http://www.alef1.org/ruby/method_missing/index.html]


== 3. Generic Handler ==
== 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
[http://www.ruby-forum.com/topic/112986]
 
== 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
[http://redhanded.hobix.com/inspect/theBestOfMethod_missing.html]
 
== 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
 
[http://cleanair.highgroove.com/articles/2006/07/04/ruby-moment-of-zen-method_missing]
 
== 7. Generic Handler ==
  class NoBar
  class NoBar
   def method_missing(methodname, *args)
   def method_missing(methodname, *args)

Revision as of 18:07, 6 October 2007

Introduction - method_missing

The method_missing is a method that called whenever someone tries to call a method in your object that doesn't exist. When you call a method, Ruby looks for a method to invoke with the same name as the method you call. First 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, and then in each of the included modules of that class. Then it looks in that class’s superclass, and 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. When a method call is not exist, Ruby use method_missing as the last resort. [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]

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]

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

[6]

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

  1. Allow you to catch problem at runtime.
  2. Allow you define a generic method_missing and handle any undefined method. This is a big advantage over Java. In Java, when you call method is not define, the program will not compile.
  3. The use of method_missing falls under the general technique of metaprogramming. You can employ metaprogramming in missing_function to write a another function to handle the call.

References

  1. 10 things you should know about method_missing.[7]
  2. More Ruby: method_missing [8]
  3. Hey Ruby, how much for the method_missing? [9]

Further reading

  1. Evaluation Options in Ruby. [10]
  2. IF YOU BUILD IT .. will they come [11]


External links

  1. The Best of method_missing [12]