hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

The Best of method_missing #

by why in inspect

I never use method_missing. Maybe twice. And both times I didn’t use it, regretted it, forcefully ejected the code from a moving vehicle, shed nary a tear.

And yet, there’s so many great uses for method_missing out there. If I may, a few of my favorite. And if you will, please, a few of yours?

  • XMLRPC::Client::Proxy will pass its method calls directly onto the service. You can even specific a prefix, which makes the whole thing a little more psuedo-OO.
    >> require 'xmlrpc/client'
    >> system = XMLRPC::Client.
         new2( "http://www.oreillynet.com/meerkat/xml-rpc/server.php" ).
         proxy( "system" )
    >> system.listMethods
    => ["meerkat.getChannels", "meerkat.getCategories", ...]
    
    Naturally, DRb does similarly.
  • Builder::XmlBase translates method calls into XML tags.
     >> require 'rubygems'
     >> require_gem 'builder'
    
     >> builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
     >> builder.person { |b| b.name("Jim"); b.phone("555-1234") }
     <person>
       <name>Jim</name>
       <phone>555-1234</phone>
     </person>
    
  • Hash with Attrs (halfway down the page) is kind of neat. Make your Hashes act like JavaScript objects.
     class Hash
       def method_missing(meth,*args)
         if /=$/=~(meth=meth.id2name) then
           self[meth[0...-1]] = (args.length<2 ? args[0] : args)
         else
           self[meth]
         end
       end
     end
    

How do you use method_missing? Blow our minds post-haste.

said on 03 Feb 2005 at 16:34

I use it in my Rails app, Elite Journal to generate RSS and Atom feeds for users:


 def method_missing(method)
    @posts = User.find_first(['username = ?', method.to_s]).posts
    render_action 'index'    
    cache_page if ActionController::Base.perform_caching
  end

Such that /rss/scott gets the feed for user scott, and so on and so forth. Not mind blowing really, but nice.

said on 03 Feb 2005 at 16:35

hmm, does pre/code not work in comments? Sorry :(

said on 03 Feb 2005 at 16:41

The QtRuby/Korundum bindings for the Qt and KDE apis use method_missing to interface with the ‘Smoke’ library. There are over 1000 classes and 30000 methods accessible from ruby via method_missing and its friend const_missing.

method_missing is also used to divert method invocations to KDE DCOP calls – the KDE equivalent to Distributed Ruby DRb. When you invoke a method on a KDE ::DCOPRef the Korundum runtime first looks for it in the Smoke runtime, and if it can’t be found, it catches an exception and tries to call a remote method via DCOP . So here in 70 lines of ruby code we interface with both DCOP and Smoke with a nice combination of method_missing and exception handling. If you use method_missing you can transform method names, and so CamelCase or all lower case with underscores can be used. And a DCOP method call ‘setThing()’ can be invoked via ‘thing=’ in ruby:

 def method_missing(*k)
     # Enables DCOPRef calls to be made like this:
     #
     # dcopRef = DCOPRef.new("dcopslot", "MyWidget")
     # result = dcopRef.getPoint("Hello from dcopcall")
     begin
         # First look for a method in the Smoke runtime.
         # If not found, then throw an exception and try dcop.
         super(*k)
     rescue
         dcopArgs = k[1, k.length-1]
         dcopArgs <<  NoEventLoop << -1
         method = k[0].id2name
         # Make 'parrot.age = 7' a synonym for 'parrot.setAge(7)'
         method = 'set' + method[0,1].upcase + method[1,method.length].sub("=", "") if method =~ /.*[^-+%\/|]=$/

         # If the method name contains underscores, convert to camel case
         while method =~ /([^_]*)_(.)(.*)/ 
             method = $1 + $2.upcase + $3
         end

         # Get the functions() for this dcop ref and 
         # cache method_name => full_type_signature in a hash
         if @functions.nil?
             @functions = {}
             funcs = call("functions()")
             if funcs.nil?
                 return nil
             end
             funcs.each do |func|
                 if func =~ /^([\w,<>:]*)\s+(.*)(\(.*\))/
                     return_type = $1
                     name = $2
                     args = $3
                     if args =~ / /
                         # Remove any arg names
                         args.gsub!(/ \w*/, "")
                     end

                     # Make thing? a synonym for isThing() or hasThing()
                     if name =~ /^(is|has)(.)(.*)/
                         predicate = $2.downcase + $3 + '?'
                         if @functions[predicate].nil?
                             @functions[predicate] = return_type + " " + name + args
                         end
                     end

                     if @functions[name].nil?
                         @functions[name] = return_type + " " + name + args
                     else
                         # If a function name is overloaded, just keep a single name entry in
                         # the hash, not all the full type signatures. Then leave dcopTypeNames() 
                         # to try and resolve the ambiguous call from the ruby arg types passed.
                         @functions.delete(name)
                         @functions[name] = name
                     end
                 end
             end
         end

         method = @functions[method]
         if method.nil?
             qWarning( "DCOPRef: call #{k[0].id2name}() not found" )
             return
         end

         return callExt(method, *dcopArgs)
     end
 end
said on 03 Feb 2005 at 17:17

Whoa, man. I gotta get these code tags under CONTROL !! I may ditch the borders. It’s too bad the scrollbars on CSS overflow is so poorly contrived.

said on 03 Feb 2005 at 17:37

In RMagick you can call any Image method on an ImageList object. In ImageList, method_missing routes the method on to the current image in the list. Following the RubyStyleGuide I also overrode respond_to? to give consistent results.

said on 03 Feb 2005 at 19:14
Lafcadio uses method_missing to dynamically define getters and setters for domain objects. Also the main facade for the DB, ObjectStore, does lots of pseudo-English parsing to make sense of method calls like
ObjectStore#get_user( 1 )
ObjectStore#get_users( 'fname', /francis/ )
ObjectStore#get_users { |user|
  user.fname.like( /francis/ )
}

I’d imagine ActiveRecord does the same.

I was pretty impressed by Rich Kilmer’s Alph presentation at RubyConf 2004, which transparently dispatches ActionScript method calls from Ruby to a Flash movie over the network. Basically abstracting away the entire network, like it was some piddling implementation detail … sw33t.

said on 03 Feb 2005 at 19:22

the code tags are blinking at me, ... kind of freaking me out.

said on 03 Feb 2005 at 22:54

I’m drunk, but RedHanded still rocks!

said on 04 Feb 2005 at 00:16

Unit testing—I pass a class that records the methods called upon it (via method_missing) and assert that the right things are called in the right order.

said on 04 Feb 2005 at 08:37

Is that what that is Asenchi? I thought _why was trying to send subliminal ruby messages to the brain…

said on 04 Feb 2005 at 09:43

I think the code blinking might be related to the refreshing time that is used at the comment posting form. Perhaps the timeout could be set closer to a minute?

said on 04 Feb 2005 at 09:58

Hey, you don’t sound drunk. You sound sensible and very rational. In fact, I took your suggestion right away. Soon as I saw it.

said on 04 Feb 2005 at 12:45

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
said on 04 Feb 2005 at 16:14

I’m sorry my method_missing code went missing.

said on 05 Feb 2005 at 11:44

This is a rather cute idea I took from PickAxe 1. 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
said on 05 Feb 2005 at 12:44

_why, maybe remove the sidebar when comments are shown in order to get rid of some of these scroll bars and have a larger space?

Just an idea.

said on 10 Feb 2005 at 11:00

Needle’s container.rb.

Comments are closed for this entry.