sebastian.martinez

Scheduling in Ruby

Posted by sebastian.martinez
on August 10, 2009

Scheduling tasks is something we all need to know to do, for it's quite common in applications. Fetching feeds, indexing some data, processing files at a periodical time, that happens a lot. You are probably quite familiar then with the linux cron, if you had to deal with scheduling stuff in the past, but there is something you may not. Let me introduce you the Whenever(Whenever) gem. What is it? A simple gem to schedule tasks writing them in nice ruby syntax...just let the gem work it's magic and deal with the cron.

In order to install it, you have to add first the github source, only if you never done it :

$ gem sources -a http://gems.github.com
$ sudo gem install javan-whenever

To get started, just place yourself in the app path and type

$ wheneverize . 

This will create an initial config/schedule.rb for you.

There you can nicely write tasks to run.

Some examples are:

every 3.hours do
    runner "MyModel.some_process"
    rake "my:rake:task"
    command "/usr/bin/my_great_command"
  end

  every 1.day, :at => '4:30 am' do
    runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
  end

  every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
    runner "SomeModel.ladeeda"
  end

  every :sunday, :at => '12pm' do # Use any day of the week or :weekend, :weekday
    runner "Task.do_something_great"
  end

Another nice thing to do is integrate it with Capistrano.


 after "deploy:symlink", "deploy:update_crontab"

  namespace :deploy do
    desc "Update the crontab file"
    task :update_crontab, :roles => :db do
      run "cd #{release_path} && whenever --update-crontab #{application}"
    end
  end

The official documentation provides this way to integrate it, but we found some little details, and the solution is here for you.

If you integrate it by the regular way, when making multiple deploys, it will configure several tasks to run in your cron file. Why? Because the path of the current application changed, and the gem uses the path (by adding a comment line) when updating the cron. What should we do then? Simple, change the run line with this:

run "cd #{current_path}; whenever -i #{current_path}/config/schedule.rb"

What have we done? We used the -i option, which makes an update, but passing the comment we want, and make it not to change in every deploy.

Feel free to try it and leave your comments !!

| |
jose.costa

Method missing to simplify queries to an external service

Posted by jose.costa
on August 05, 2009

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(Getting Started with the Media 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(Getting Started with the Media 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?

| |