Saturday, April 20, 2013

Using Sidekiq with Faye to notify client web pages and applications of updates

Recently I was given a small coding challenge of how I would update sites & client applicaitons with notifications of content being released. My mind immediately flew to the faye gem. However, this would be a serious block if my app had to halt everything to send out the notification to a bunch of browsers. So, I added in Sidekiq. In the Content Model I put this to call out to Sidekiq:
  def self.release_next
    ContentWorker.perform_async
  end
The ContentWorker then looks like this:
class ContentWorker
  include Sidekiq::Worker
  sidekiq_options queue: "content"

  def perform
    next_release = Content.next_release(Content.last_release)
    # puts the data into JSON format; for actual content it could encode a url that is then loaded
    vars = ["title" => next_release.title,
             "site" => next_release.site,
             "released_at" => next_release.released_at.strftime("%l:%M:%S %p %m-%d-%Y")].to_json
    message = {:channel => "/releases", :data => vars, :ext => {:auth_token => FAYE_TOKEN}}

    uri = URI.parse(FAYE_SUBSCRIPTION_PATH)
    Net::HTTP.post_form(uri, :message => message.to_json)
    next_release.update_attribute(:released_at, DateTime.now)
  end
end
I created a designated queue in Sidekiq for the messages and then made the Faye channel creation configurable via an environment variable from an initializer file (/config/initializers/faye_configuration.rb):
FAYE_SUBSCRIPTION_PATH = "http://localhost:9292/faye"
The client then just listens in on the Faye channel and acts upon a message coming through. Here is the Javascript I wrote for listening to the Channel and adding the new release to the top of the view table just below the headers:
$(function() {
   var faye = new Faye.Client("http://192.168.0.12:9292/faye");
   faye.subscribe("/releases", function(data) {
      add_to_table(data);
   });
});

function add_to_table(data){
    var json_obj = jQuery.parseJSON(data);
    var $content_table = $('#content_table');
    if ($content_table.find('tr').length >= 10 ) {
        $content_table.find("tr:last").remove();
    }
    $("#header").after("<tr><td>" + json_obj[0].title+ "</td><td>" + json_obj[0].site + "</td><td>" + json_obj[0].released_at + "</td></tr>");
}
All in all a nice little challenge. I have not had a chance to try it out beyond my home network yet so I am not sure how performant the solution would be.

Railscasts used for this:
Faye
Sidekiq

1 comment:

  1. just came here to say your blog name is fantastic

    ReplyDelete