Pushr refactored with external notifiers 30|01|2009 0 comments Comments for article feed

Getting deploy notices as tweets is certainly cool. But maybe you are on the other end of cool and use Jabber, or maybe you're an old pro and use IRC. Well, you can get Pushr deploy notices through these channels, too, and have infrastructure to implement more. You can also see some dynamic programming goin' on.

Refactoring

After couple of weeks in use, Pushr really needed some refactoring. There were ugly things, there was forgotten stuff. This had to go away. Beyond that, the Twitter notifier was soldered into the code and there was no way to easily add and configure more. And that really did suck, because some friends over at XNet are much more used to plain old IRC.

You can download notifiers from their repository, currently there are ones for IRC and Jabber. As will be evident, they are modeled after Integrity notifiers, so it should be trivial to adapt these.

External notifiers

So. All the notifying logic had to be ripped out and a foundation in form of Pushr::Notifier::Base had to be laid. This class provides shared logic for separate notifiers, which use plain old inheritance to get it:

# Inherit from this class in your notifiers
# See eg. http://github.com/karmi/pushr_notifiers/blob/master/irc.rb
class Base
  attr_reader :config

  def initialize(config={})
    @config = config
    log.fatal("#{self.class.name}") { "Notifier not configured!" } unless configured?
  end

  # Implement this method for your particular notification method
  def deliver!
    raise NoMethodError, "You need to implement 'deliver!' method in your notifier"
  end

  private

  # Over-ride this method to send diferent message
  def message(notification)
    if notification.success
      "Deployed #{notification.application} with revision ... etc"
    else
      "FAIL! Deploying #{notification.application} failed. Check deploy.log for details."
    end
  end

  # Implement this method to check for notifier configuration
  def configured?
    raise NoMethodError, "You need to implement 'configured?' method in your notifier"
  end

end

The architecture for this setup was heavily inspired by solution in Nicolás Sanguinetti's & Simon Rozet's Integrity application, which you should check out.

You can setup notifiers in Pushr's config.yml like this:

notifiers:
  # You'll probably want to protect the updates for this Twitter account :)
  - twitter:
     username: hello
     password: kitty
  # For IRC see http://github.com/karmi/pushr_notifiers/blob/master/irc.rb
  - irc:
       uri: 'irc://irc.freenode.net:6667/test'
  # For XMPP see http://github.com/karmi/pushr_notifiers/blob/master/jabber.rb
  - jabber:
       username: he110
       password: g33k

These notifiers are then loaded like this:

def load_notifiers
  @notifiers_path = CONFIG['notifiers_path'] || '../pushr_notifiers'
  @notifiers = []
  CONFIG['notifiers'].each do |notifier|
    notifier_name, notifier_config = notifier.to_a.flatten
    unless Pushr::Notifier::const_defined?(notifier_name.to_s.camelize)
      begin
        require File.join( File.dirname(__FILE__), @notifiers_path, notifier_name  ) 
      rescue Exception => e
        raise "Notifier #{notifier_name} not found! (#{e.message})"
      end
    end
    @notifiers << Pushr::Notifier::const_get(notifier_name.to_s.camelize).new(notifier_config)
  end
end

Several things are goin' on in here. First, using the something || something_else Ruby convention, we don't force anybody to configure the obvious in config.yml. You'll most probably put notifier classes one level above Pushr's directory in a pushr_notifiers directory. So, why set this.

We then traverse the notifiers array from config.yml and look if we have something like this already declared in our app –– using ported ActiveSupport's camelize method. As is the case with Twitter notifier, which is the default notifier.

If not, we'll try to load a file with the same name as the notifier, and put an informative Exception if it couldn't be found.

After we put all notifiers into the @notifiers array, it's super-easy to call them all in one swoop:

def send_notifications
  @notifiers.each { |n| n.deliver!(self) }
end

And thanks to shared logic in Pushr::Notifier::Base it's again super easy to implement (let's say) an IRC notifier, using Simon Rozet's wonderful shout-bot gem:

require 'rubygems'
require 'shout-bot' # $ sudo gem install sr-shout-bot --source=http://gems.github.com

class Pushr::Notifier::Irc < Pushr::Notifier::Base
  def deliver!(notification)
    return unless configured?
    ShoutBot.shout(config['uri'], :as => "Pushr") do |channel| 
      channel.say message(notification)
    end
  end

  private
  def configured?
    !config['uri'].nil?
  end
end

If you're interested in this kind of programming, you should definitely check Russ Olsen's book Design Patterns in Ruby. It's last chapter, Convention over Configuration, will guide you through this magic Rails-like stuff. When you think you know Ruby rather good, this is the book which will bring your skillset to another level.

Join the discussion
 

(required)
(required, won't be displayed)

(Use Markdown syntax)