Monday 23 August 2010

Using retry to run exception block again

One cool thing that I've not personally seen in another language is the retry keyword. If you use it in a rescue block, it'll rerun the block of code the exception occurred in.. Take a look at this code:

retry_count = 0
begin
  ShoddyWebService.new.call
rescue Timeout::Error => ex
  retry_count += 1
  raise ex if retry_count > 5
  puts "Shoddy web service timed out..retry #{retry_count}/5"
  retry
end

Now, if your web service call fails 5 times, the code will fail. This makes your applications more resilient to external problems, as services timing out does happen once in a while.. I'd rather my application handled that nicely instead of just bombing out.

Wednesday 18 August 2010

Slick code with simple DSLs thanks to Ruby

One of the things I love about Ruby is the massive advantages gained by the way it's built - things like blocks make it so easy for Ruby to have amazing looking, clean code. The great thing is, this also extends to writing your code in Ruby!

Want the people consuming your code to have easy to understand, learn and read code? Write a nice DSL for it. (I'm going to take a second here - DSL stands for Domain Specific Language.. kind of like another programming language, that is structured in the best possible way to interact a specific domain)

Take for example an email sending class..

Email.new("clocKwize@gmail.com", "spammaster@yahoo.com", "I love you", "Not really!").send

Now, it's pretty straightforward what this does, but it'll be easy to forget which email it'll send to and which email it'll appear to come from.. Lets write a DSL to make this way more readable, and more extensible, should we need to add more parameters and options later

Email.send do
  to "clocKwize@gmail.com"
  from "spammaster@yahoo.com"
  subject "I love you"
  body "Not really!"
end

Now, that code may be longer, but no developer will ever find it hard to see exactly what the code is doing, they'll never have to go and look at the definition of Email to find out.

Now, there is no harm in providing both ways to use your code, you don't want to restrict people to use a DSL, it's usually a layer built on top of an API. Here is how I wrote the code initially

class Email
  def initialize(to = nil, from = nil, subject = nil, body = nil)
    @to, @from, @subject, @body = to, from, subject, body
  end
  
  def self.send(&block)
    email = Email.new
    email.instance_eval &block
    email.send
  end
  
  def to(value)
    @to = value
  end
  
  def from(value)
    @from = value
  end
  
  def subject(value)
    @subject = value
  end
  
  def body(value)
    @body = value
  end

  def send
    puts "Sending an email!"
  end
end

Now this works nicely by using a cool method called instance_eval. This basically takes a block and runs it but with a 'self' of whatever you are calling instance_eval on. In this case, when the block is run, and it comes across to "clocKwize@gmail.com" - this is calling self.to("clocKwize@gmail.com") on the Email object created in send.

The only draw back to this is, once self is our email object, we can no longer access instance stuff in the class we are using the DSL in!

For example

class TestClass
  def initialize(magic_number)
    @magic_number = magic_number
  end
  
  def go
    Email.send do
      ...
      body "Magic number is: #{@magic_number}"
    end
  end
end

Now, this looks like it should work, but it wont because Ruby will be looking for @magic_number on the Email object, which isn't what the user of your DSL intended at all.

The best way around this to pass the instance of the Email to the block as a parameter. Not quite as slick but the block keeps its original scope and works fine:

Email.send do |email|
  ...
  email.body "Magic number is: #{@magic_number}"
end

This is accomplished by changing the self.send method as follows

def self.send(&block)
  email = Email.new
  block[email]
  email.send
end

But you know, I kind of like the first way, and if the user doesn't need any instance stuff from the class they are in, we shouldn't stop them using the prettier DSL. A very simple way to do this is to ask the block how many parameters it accepts..If it takes one, pass the Email instance, if it doesn't use self as the Email instance.

def self.send(&block)
  email = Email.new
  block.arity == 1 ? block[email] : email.instance_eval(&block)
  email.send
end

There we go.. the best of both worlds! Consumers of your DSL can use whatever way they are comfortable with and your code will work either way. Hopefully this will help someone on their path of discovery.