jose.costa

Method missing to simplify queries to an external service 5

Posted by jose.costa
on Wednesday, August 05

I know there are several discussions on the usage of method_missing in Ruby. In this post i’ll present a pretty simple, yet useful solution that uses method_missing to interact with the Brightcove Media Read API (you don’t need to be familiar with this service, i’ll explain a little bit in the next few lines).

Brightcove Media Read API accepts calls of the form:

http://api.brightcove.com/services/library?command=find_all_videos
&token=0Z2dtxTdJAxtbZ-d0U7Bhio2V1Rhr5Iafl5FFtDPY8E.

http://api.brightcove.com/services/library?command=find_related_videos
&video_id=123123&token=0Z2dtxTdJAxtbZ-d0U7Bhio2V1Rhr5Iafl5FFtDPY8E.

http://api.brightcove.com/services/library?command=find_videos_by_text
&text=sometextsample&pageSize=100&token=0Z2dtxTdJAxtbZ-d0U7Bhio2V1Rhr5Iafl5FFtDPY8E.

A token must be passed on each call, and you could also add more parameters like you would do in a regular GET request. What comes back is a JSON string that can be easily picked up.

The key thing here is to notice that there are several commands you could execute from the API, naturally each with its own name that must be specified in the request right after “command=”. Since the API also provides a set of error codes to address all wrong requests or non-existent commands requests, we simply wanted to forward all the calls to the API and reply back with its answer. So, in order to avoid defining all API methods in our Ruby module, we just used the method_missing and forwarded all calls to it, using the name of the method as the API command.

The idea was that a call like:

http://api.brightcove.com/services/library?command=find_videos_by_user_id
&user_id=34876423&token=0Z2dtxTdJAxtbZ-d0U7Bhio2V1Rhr5Iafl5FFtDPY8E.

became just:

Brightcove::ReadProxy.find_videos_by_user_id :user_id => 34876423

We then implemented what we called the Brightcove::ReadProxy in a few lines like shown below:

module Brightcove
  module ReadProxy

    TOKEN = '0Z2dtxTdJAxtbZ-d0U7Bhio2V1Rhr5Iafl5FFtDPY8E.'
    SITE = 'http://api.brightcove.com/services/library'

    def self.method_missing(method_id, *args)
      args[0] ||= { }
      args[0].merge!({ :command => method_id, :token => TOKEN })
      get SITE, args[0]
    end

    def self.get(url, params)
      http_client = HTTPClient.new
      result = http_client.get(url, params)
      content = nil
      begin
        content = JSON.parse(result.content)
      rescue Exception => e
        Rails.logger.error e.message
      end
      return content
    end

 end

Basically all the magic relies in the method_missing that would convert any call to the Brightcove::ReadProxy module in the format accepted by the API, without having to define every API method and maintaining the Rails like finders syntax. We also used the httpclient gem to simplify the GET request calls and the json gem to parse the result of the call.

I’m not saying that this is the best usage, but i think in this particular situation it suits pretty well.

What do you think?

|

If you have found this material to be useful, you might
consider recommending me on Working With Rails.

Comments

Leave a response

  1. Vishnu Gopal Vishnu GopalAugust 05, 2009 @ 10:34 PM

    You could on a 404 or 500 response from the GET, raise the method_missing again. (Makes sense—it’ll let people know there’s no API call like that). A @@cache of methods is also usually good for stuff like this.

    Cheers, Vishnu

  2. David Balatero David BalateroAugust 06, 2009 @ 01:41 AM

    Once you hit the method_missing call, and you have determined that it is a valid API call (e.g. via response code), you should really use define_method (http://www.ruby-doc.org/core/classes/Module.html#M001654) to dynamically create and cache that method at that point. Otherwise, every time you make an API call, it will have to go down the entire inheritance tree looking for a valid method, then down the method_missing tree.

  3. David Balatero David BalateroAugust 06, 2009 @ 01:44 AM

    Also, it’s a good idea to update respond_to? as well whenever you mess with method_missing (for reasons here: http://www.dcmanges.com/blog/30). Since you seem to allow any arbitrary method to be sent, one quick way would be this:

    def respond_to?(method) true end

  4. José Costa José CostaAugust 06, 2009 @ 06:25 PM

    Thanks so far for your comments. David, you’re right. Your suggestions are a must.

  5. Josh Illian Josh IllianAugust 06, 2009 @ 10:00 PM

    Great write-up, Jose, much appreciated. Seems like a natural fit for and natural example of method_missing.

Comment