Ruby’s instance_exec method

instance_exec is a method you can use to change the “scope” of a block. One of the great advantages of doing this is that it can make your code more readable, particularly when writing a Domain Specific Language (DSL). So how does instance_exec work and why would you ever want to write a DSL?

instance_exec is pretty easy to demonstrate by way of a silly example. Let’s suppose you are running a Cattery. For anyone who doesn’t like silly examples, you can think of the cats as some kind of unmanaged resource (for example, a database connection) and the cattery as a pool of these resources (e.g. a database connection pool). Any cats that manage to escape the cattery can be considered to therefore be a memory leak that will harm your application and ultimately lead to sleepless nights and long days.

It might actually be easier on stress levels to just think of them as cats.

First, we define a simple class to represent a “cat”:

class Cat
  def speak!
    puts "meow!"
  end
end

and then another class to define the cattery itself, which internally stores an array of the cats currently in the cattery as well as providing an interface to add a new cat. Additionally, the cat should express itself by “speaking” after it is added to the cattery:

class Cattery
  def initialize
    @cats = []
  end

  def add_cat(cat)
    @cats << cat
    cat.speak!
  end
end

Let’s try it out:

cattery = Cattery.new
cattery.add_cat(Cat.new)

=> "meow!"

Lovely stuff.

Now imagine you discover that your cats keep escaping and you decide that the easiest way to stop this happening is to install a “door” on the cattery (assuming that the resident cats have not yet figured out how to open doors). So, you change your Cattery class accordingly by providing an interface to allow a door to be open and closed. You further alter your “add cat” method such that it will not allow a cat to be added unless the door is open:

class Cattery
  def initialize()
    @cats = []
    @door_open = false
  end

  def open_door
    @door_open = true
  end

  def close_door
    @door_open = false
  end

  def add_cat(cat)
    if @door_open
      @cats << cat
      cat.speak!
    else
      raise "Can't add a cat when the door is not open!"
    end
  end
end

Sweet. Now when you try adding a cat when the door is closed you get an error:

cattery.add_cat(Cat.new)
=> :in `add_cat': Can't add a cat when the door is not open! (RuntimeError)

cattery.open_door
cattery.add_cat(Cat.new)
=> "meow!"

Great! Nice and secure. only one problem… the developer who wrote that last piece of code forgot to close the door afterwards (doh!). How do we ensure that the door always gets closed properly after a cat is added?

Maybe you could use the begin/ensure (try/finally for non rubyists) syntax within the add_cat method? Well, if you go down that road then you’re breaking the single responsibility principle for that method – it’s called “Add cat” that’s all it should really be doing… but now it’s responsible for both adding the cat AND opening and closing the door. The method would also become coupled to the door interface which may cause future issues if we ever decide to change that interface or extract the door out into a class of its own. You might think that you could just remember to do a begin/ensure every time you add a cat:

begin
  cattery.open_door
  cattery.add_cat(Cat.new)
ensure
  cattery.close_door
end
=> "meow!"

But then you’re pretty much back in the boat you were originally, where you were relying on programmers to remember to close the door after themselves… only this time you have twice as much code!

At this point rubyists will start considering blocks. You might find yourself thinking along these lines :

* We could create a method to safely open the door and close the door afterwards, yielding to a block.
* We could remove the open/close methods on the cattery so people need to use that method to add a cat.

Such a method might look like this:

class Cattery
  ...

  def safely_open_door(&block)
    begin
      @door_open = true
      yield self
    ensure
      @door_open = false
    end
  end
 
  ...
end

Purrrfect. This would allow you to do the following:

cattery.safely_open_door do |this_cattery|
  cattery.add_cat(Cat.new)
  this_cattery.add_cat(Cat.new)
end

And you could stop right there – the solution works and the cats are safe. However, the code is not beautiful. We have multiple versions of the same variable floating around, both inside and outside of the block, which is confusing. It looks messy. Wouldn’t it be nicer if we could, just within the scope of the block, consider ourselves to be in the “cattery domain” where we could exclusively talk to the cattery without worrying about what’s going on outside of the block?

This is where instance_exec comes into play.

instance_exec changes the scope of the code within the block itself, and subsequently will change the result of calling “self” within it. In its current form, the block is scoped to the “main” object, which is why we are able to access the “cattery” variable.

What would be really nice is if this block were to be scoped to the cattery itself – any code within it would therefore be specific to the domain of dealing with a cattery. We can actually achieve this with one simple change to the code we have already:

class Cattery
  ...

  def safely_open_door(&block)
    begin
      @door_open = true
      instance_exec(&block)
    ensure
      @door_open = false
    end
  end
 
  ...
end

All we have changed is “yield” to “instance_exec”. The block will still be called (you can even pass additional arguments to the instance_exec method if you want to and they’ll be yielded to the block too). Making this small change allows us to finally write the code we want to:

cattery.safely_open_door do
  add_cat(Cat.new)
  add_cat(Cat.new)  
end

Or in one line:

cattery.safely_open_door { add_cat(Cat.new) }

Gorgeous.

So why would you ever want to do this? Well, the easiest way of demonstrating why you might want to create a DSL is to look at one you have already been using, maybe without even realising it. Try to imagine life without this syntax:

Rails.application.routes.draw do
  resources :products do 
    resources :comments
  end
end

Yup. The config.rb routes file in a rails application uses exactly the techniques that have been described here to create a language for you to talk about routing within the routes.rb file. If you output the result of “self” within the routing block you’ll find that it’s one of these:

 #<ActionDispatch::Routing::Mapper:0x000000040a4548>

Anyone who’s been in the game long enough to remember how routes worked in rails 2 may recall that previously routing used to work like this:

ActionController::Routing::Routes.draw do |map|
  map.connect '/products', :controller => 'products', :action => 'index'
  map.connect '/products/:id', :controller => 'products', :action => 'show'
end

And you’ll be hard-pressed to find anyone who wants to go back to that syntax. In conclusion then, instance_exec can help you:

* Write neater code
* Write less code
* Create rich Domain Specific Languages for you and other developers to use

Advertisements

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