Are my metaprogramming underpants showing?


#1

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.


#2

Matthew S. 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
http://www.artima.com/rubycs/ - 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


#3

On Dec 6, 2005, at 0:03, James B. wrote:

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.

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.


#4

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.


#5

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.


#6

Matthew S. removed_email_address@domain.invalid
Institute for Communicating and Collaborative Systems
University of Edinburgh

On Dec 6, 2005, at 2:37, Trans wrote:

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.

Gah. Serves me right for doing this late at night. Here is the
fixed link:

http://flickr.com/services/api/

Do you mind if I ask how you find it odd? Or what you might do
otherwise? Here is, hopefully, a fuller explanation:

The reasoning was like this. I wanted the Ruby method calls to look
just like they’re defined in the api (modulus the parameters), so
like this:
flickr.test.echo({“api_key” => “…”})

The catch is that in Ruby, that’s a calling ‘echo’ on ‘flickr.test’,
and so the ‘echo’ there needs to somehow know what the name of the
entire method (flickr.test.echo) is. The point of the exercise for
me was to avoid defining each and every API method specifically, so I
needed a way encode the entire API method name into that ‘echo’, and
the class heirarchy seemed like a reasonable fit:

  • simpler than tracing callers

So when a the above call is made, this happens:

  • flickr object create a Flickr::Test class, and an instance of it
    in @test
  • flickr.test creates a Flickr::Test::Echo class, and an instance
    of it in @echo, and then calls Flickr::Test::Echo#request
  • #request extracts the flickr API method name (flickr.test.echo)
    by downcasing the first letter of each element in the class name (the
    case-mangling was necessary since Ruby classnames are constants).

The first and simplest thing I did was to use method_missing just to
dynamically construct the API method name by concatenating the
method_id’s in #method_missing and returning self. This worked, but
didn’t leave open much flexibility for adding, well, much of anything.

Another alternative I considered was instead of using instances of
the classes, was just to instead create a class method with analogous
behaviour (e.g. Flickr.test would create Flickr::Test, etc), but that
meant treating the Flickr class differently from the other classes,
since it would need to be instantiated to match the “flickr =
Flickr.new” intuition as well as the 'flickr.x.y" requirement.
Seemed better to do it all at once.

thanks once again,
matthew smillie.


#7

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”)


#8

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.

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.


#9

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.