Ruby External CRON manager on Heroku with Sinatra, Resque and Redis


In todays post we’ll have look at task scheduling using Heroku and the famous Resque gem. I have presented it at last London Skillsmatter Salesforce Dev Meetup. Watch the video:

Delayed tasks on Salesforce have some well known weaknesses. For example: “Use extreme care if you are planning to schedule a class from a trigger. You must be able to guarantee that the trigger will not add more scheduled classes than the 25 that are allowed”.

With the help of Heroku, most of your background tasks might be externalized and delegated to a dedicated app on Heroku. To build that solution we’ll be using 4 different gems. The objective is to create a simple web app that is able to create, handle and monitor tasks build by registered users. To be really functional, that web app should be reachable through an API (coming in a future post).

  1. Sinatra to build a basic web app
  2. Databasedotcom: to get the secure connection to Salesforce API
  3. Omniauth to get the authorization token from your ORG
  4. Resque to handle Job Queues and monitor them

Sinatra

Sinatra is the trendy DSL to build Web App in Ruby maintained by the brilliant RKH aka the cool kid. If you are bored by the heaviness of Rails, just pick Sinatra and as they say: “Put this in your pipe and smoke it”. In few words, Sinatra is a simple framework looking like rails, usually written in single file. It looks like Rails because you’ll find some MVC sprinkles and multiple typical Rails features all around Sinatra (redirect, helper, before filter, routing, asset management etc.).

In our app, we’ll use the basic Sinatra abilities to define the initial URL, the callback and the URL to create a new task. As everything is handled in a single file, you’ll see at the end all the code packed into a sinatra app.rb file.

Just for your pleasure, that’s how to build the simplest web server in Sinatra in 4 lines.

require 'sinatra'

get '/hi' do
  "Hello World!"
end

Databasedotcom

Database.com has been presented a couple of times on the web. The most recent and appealing posts and apps have been written by Fractastical, Metadaddy and Dburkes. They explain in a really readable way how to install, configure and use that magic library. In a few words, databasedotcom is a Ruby wrapper on top of SFDC REST API. Said differently – after having opened a secure connection to your ORG, you can CRUD any standard or custom object in an ActiveRecord-like way.

Here is how to install and use it in our app

require 'databasedotcom'

config = YAML.load_file("config/salesforce.yml") rescue {}
@client_id = config["client_id"]
@client_secret = config["client_secret"]
@username = config["username"]
@password = config["password"]

dbdc = Databasedotcom::Client.new(:client_id => @client_id, :client_secret => @client_secret)
dbdc.authenticate :username => @username, :password => @password.
dbdc.materialize('Lead')
leads = Lead.all
leads.each{ |lead| lead.update_attribute('LeadSource',leadsource) }

Omniauth

To get the authorization token, we’ll use omniauth. Many people keep using the standard OAuth and OAuth2 gems. However, Intridea built a really practical gem to simplify your OAuth handshake. After 19 months of dev V1.0 was released. Omniauth comes with a lot of provider strategy. A strategy is a basically an end-point description. The list of existing providers is increasing regularly and you can easily add a new strategy and bundle it as a new gem in few lines.

To use omniauth you need to store the session. We’ll use Rack::Session::Cookie. We require the Salesforce strategy usage in these lines:

require 'omniauth'
require 'omniauth-salesforce'

  use Rack::Session::Cookie
  use OmniAuth::Builder do
    provider :salesforce, config["client_id"], config["client_secret"]
  end

Don’t forget to add a callback URL matching the one described on the “Remote Access” record created on your ORG. Also please note that as your callback must be over HTTPS, you must enforce SSL on your app. This is automatically done on Heroku as of a few weeks ago, but must be handled on your local machine (check Rack::SSL).

Resque

We’ve kept the best for the end. Resque was written a few years ago by Defunkt from Github after multiple failures using background.rb and Delayed job. Read the interesting history of Resque on Github Blog. In Resque, jobs queues are usually stored on a Redis DB. Any other storage solution might nevertheless be used. Resque workers (you can use multiple and dedicate to specific tasks/queues) handle them. On Heroku, a worker is a resource just like Dynos are and need to be added to your app using either a Procfile or the heroku CLI

heroku scale worker=1.

Other worker providers like IronWorker are also available. Locally, workers are running rake tasks.

Additional to its proved robustness and overall quality, one of the strength of Resque is to come with a monitoring web UI. You’ll see there you’re running workers, working and failed jobs in real time.

To load Resque Monitoring app with our sinatra app, here’s the trick:

require './song'
require 'resque/server'

run Rack::URLMap.new \
  "/"       => Sinatra::Application,
  "/resque" => Resque::Server.new

A job is defined as a module/class which declare the associated queue and define a perform method.

 module JobName
    @queue = :queue_name

    def self.perform(params)
      puts "the job does this and that"
      # YOUR LOGIC WILL GO HERE
    end
  end

As we want to trigger actions on Salesforce.com to bulk update lead source, our job code is as follows:

 module Updatelead
    @queue = :lead
    config = YAML.load_file("config/salesforce.yml") rescue {}
    @client_id = config["client_id"]
    @client_secret = config["client_secret"]
    @username = config["username"]
    @password = config["password"]

    def self.perform(leadsource)
      puts "update all Leads"
      dbdc = Databasedotcom::Client.new(:client_id => @client_id, :client_secret => @client_secret)
      dbdc.authenticate :username => @username, :password => @password
      dbdc.materialize('Lead')
      leads = Lead.all
      leads.each{ |lead| lead.update_attribute('LeadSource',leadsource) }
      puts "updated all Leads"
    end
  end

Conclusion

Ok, we now have all the parts of our app. Lets glue them together and deploy it on Heroku.

You can find the final source code on GITHUB, fork it and deploy it with your credentials on Heroku.

Tagged , , , , ,

One thought on “Ruby External CRON manager on Heroku with Sinatra, Resque and Redis

  1. Thanks for sharing code mate. Really useful. Merci beaucoup.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: