Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect the end of a method chain in Ruby

I have a Flickr interface that I wrote a while ago and part of it bothers me and I'd like to make it nicer. The way it works is I use method missing to construct the url parameters for the flickr call from the methods called on the flickr object, eg.

@flickr.groups.pools.getPhotos(:user_id => "12656878@N06", :group_id => "99404851@N00")

These 'method calls' construct an api call that looks like this

http://api.flickr.com/services/rest/?method=groups.pools.getPhotos&user_id=1848466274& group_id= 99404851@N00

(I have left off the api key bit) It does this by remembering each 'method' until a method is called with arguments at which time it constructs the url and and makes the call to Flickr.

The reason I took this approach is so the ruby code matches the documentation on the Flickr site, you can copy and paste it and it will mostly work, and hopefully it will make it a bit more resilient to api changes because I have no hard coded methods.

What irritates about this though is that in this example the group id is being passed to the getPhotos method rather than the groups method. I would much rather it looked like this.

@flickr.groups(:group_id => "99404851@N00").pools.getPhotos(:user_id => "12656878@N06")

So my question. Is there any way in Ruby that I can detect that the last method has been called so that I can trigger the call to Flickr?

like image 520
Henry Avatar asked Aug 26 '09 04:08

Henry


1 Answers

Since I can't think of any way to detect the last method call, I'm going to suggest a different approach. It is similar to what ActiveRecord does; a proxy class that builds the options and doesn't fetch the data until you call a method that operates on the data.

class Flickr
  def initialize
    @result = FlickrResult.new
  end

  def method_missing(method, *args, &block)
    if @result.data.respond_to?(method)
      @result.run(method, args, block)
    else
      @result.append(method, args[0])
      return self
    end
  end

  class FlickrResult
    attr_reader :data

    def initialize
      @data = []
      @keys = []
      @options = {}
    end

    def append(key, options)
      @keys << key
      @options.merge!(options) if options
    end

    def run(method, args, block)
      if !@did_run
        fetch_data
      end

      @data.send(method, *args, &block)
    end

    def fetch_data
      puts "Only runs once!"
      @url = @keys.join(".") + "?" + @options.map {|k, v| "#{k}=#{v}" }.join("&")
      # use @url and fetch data..
      @data = ["foo", "bar"]

      @did_run = true
    end
  end
end

@flickr = Flickr.new
@flickr.groups(:group_id => "123").pools.thing.users(:user_id => "456")

@flickr.each {|f| p f }
# => "Only runs once!"
# => "foo"
# => "bar"
p @flickr.map {|f| f.upcase }
# => ["FOO", "BAR"]

It only fetches the data when you each or map it or whatever (any array method).

like image 198
August Lilleaas Avatar answered Sep 20 '22 09:09

August Lilleaas