Are my metaprogramming underpants showing?

Hello all,

I've done a quick little project to see if I can wrap my head around Ruby metaprogramming, and I'd like to run it by some more-experienced minds.

I picked the quick little task of coding up the web API for flickr (http://flickr.com/services/API). Not necessarily for practical usage (though I may use it myself), but more because it struck me as a good example since there are a lot of methods with extremely similar behaviour on the client side (send parameters to this URL). In any case, here's the code:

require 'pp'
class Flickr
   def method_missing(method_id, *params)
     # Find the desired class name
     class_name = method_id.to_s.capitalize
     # Find the corresponding instance variable
     ivar = :"@#{method_id}"

     # have we already made this particular class before?
     unless self.class.const_defined?(class_name)
       # new class which inherits from this one.
       new_class = self.class.const_set(class_name, Class.new(self.class))
       # new instance variable
       instance_variable_set(ivar, new_class.new)
     end

     # if we have parameters, execute the appropriate method (returning
     # what, though?) otherwise return the instance we just made so
     # that the next thing can be called correctly.
     return instance_variable_get(ivar) unless params.length > 0

     the_method = instance_variable_get(ivar).class.name.downcase.gsub(/::/, ‘.’)
     # abstract out the actual API call for the moment.
     puts “call: http://flickr.com/services/rest/?method=’#{the_method}’
     puts "with these other params: "
     pp *params
   end
end

# envisaged usage
flickr = Flickr.new
result = flickr.test.echo({"api_key" => "something long", "foo" => "bar"})

The idea is that the usage should mirror how the methods are defined in the flickr docs.

In general I'm asking (like the subject suggests) if my metaprogramming underpants are showing? Have I defied any particular conventions? Does this seem like a sensible approach, and if so, a sensible implementation? Have I set myself up for some rather spectacular failures?

Specifically, though, there are three aspects of the code that I'm particularly curious whether anyone has any alternate approaches:

1. encoding the method name as a class heirarchy. e.g. 'flickr.test.echo' is implicit in the class definition that results from that call (Flickr::Test::Echo), then the method name gets reconstructed from that when the eventual call is made. Any other ways to do this?

2. relying on params.length to determine the 'end' of the call seems a little funny. On the other hand, every method call takes at least one parameter. One idea I had was to inherit from Proc, and define #call, which would let flickr methods get passed around as, well, methods, though this seems to have its own dangers. (If I were to implement this as a practical library, I think I'd use flickr's reflection methods to sort this out, but is there a way to do it that doesn't require that sort of external oracle?)

3. using method_missing strikes me as a potential pitfall, but I can re-raise this if/when the flickr API returns its own 'method not found' error. Are there any other 'gotchas' I should watch out for?

Thanks in advance for any feedback.

matthew smillie.

Matthew Smillie wrote:

Hello all,

...

In general I'm asking (like the subject suggests) if my metaprogramming underpants are showing? Have I defied any particular conventions? Does this seem like a sensible approach, and if so, a sensible implementation? Have I set myself up for some rather spectacular failures?

Specifically, though, there are three aspects of the code that I'm particularly curious whether anyone has any alternate approaches:

A suggestion: move the code that munges method_id and *params into separate methods. Much easier to test and make sure it does what you want and that it doesn't barf on weird input.

James

···

--

http://www.ruby-doc.org - Ruby Help & Documentation
Ruby Code & Style - Ruby Code & Style: Writers wanted
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
http://www.30secondrule.com - Building Better Tools

Done, with a due sense of embarrassment since I as soon as I started thinking about that aspect of the code, I immediately found a bug involving using capitalize/downcase and flickr's camelCase method names. Whoops & thanks for the reminder (I'll flatter myself that I would have caught that during refactoring and testing anyway).

While I appreciate the input, I'm not particularly worried about weird input and barfing at this stage, rather that my overall approach with the metaprogramming is relatively sane. I've barfed over lots of weird input in my time (mixing wine and spirits, for instance), but the metaprogramming is relatively new.

thanks again,
matthew smillie.

···

On Dec 6, 2005, at 0:03, James Britt wrote:

Matthew Smillie wrote:

Hello all,

...

In general I'm asking (like the subject suggests) if my metaprogramming underpants are showing? Have I defied any particular conventions? Does this seem like a sensible approach, and if so, a sensible implementation? Have I set myself up for some rather spectacular failures?
Specifically, though, there are three aspects of the code that I'm particularly curious whether anyone has any alternate approaches:

A suggestion: move the code that munges method_id and *params into separate methods. Much easier to test and make sure it does what you want and that it doesn't barf on weird input.

The whole class as method thing seems very odd.n I suspect there's a
better way. But I'm not sure what you're trying to do exactly (the link
to the Fliker API didn't work btw)

T.

Here is, hopefully, a fuller explanation

Ah, I see. Okay. I'm too tired to go into tonight, but I get back to
you in the morning. In the mean time, iyou have some time, you might
want to have a look at the Functor class --that may give you some ideas
(see http://rubyforge.org/frs/?group_id=483\)

T.

Sorry I didn't get to this until this evening. Hope it's helpful. -T.

  require 'calibre/functor'

  module Flickr
    extend self

    @@api = {}

    def method_missing( sym , *args )
        @@api[sym] ||= Functor.new { |op, *args|
            api_call("#{sym}.#{op}", *args )
        }
    end

    def api_call( method, *args )
      puts “call: http://flickr.com/services/rest/?method=’#{method}’
      puts “with parameters:”
      p args
    end

  end

  Flickr.test.echo("api_key" => "something long", "foo" => "bar")

Sorry I didn't get to this until this evening. Hope it's helpful. -T.

Noone's in a hurry over here, so no worries.

  module Flickr
    extend self

    @@api = {}

    def method_missing( sym , *args )
        @@api[sym] ||= Functor.new { |op, *args|
            api_call("#{sym}.#{op}", *args )
        }
    end
  end

Well, it helps (and that calibre library is quite cool), but: it fails when there's more than three terms in the method, e.g.:
Flickr.photos.licenses.getInfo({"something" => "blah"})
undefined method `getInfo' for nil:NilClass (NoMethodError)

The general case (flickr.a.b...n), doesn't seem possible to me without the object returned from #method_missing having the same behaviour (i.e. implementation of #method_missing) as the initial object, but with the previous calls as part of the object's state.

matt.

Hmm.... that does make it trickier b/c when will the chain end? The
only thing I can think of the top of my head is to use an '!' method to
indicate it.

  Flickr.test.echo!

Then you can just return the same Functor-like object collecting the
parts along the way until the '!' is hit.

T.