Saturday, 13 November 2010

Continuing adventures in adding methods to Ruby instances

In the last post we looked at the hoops we had to go through to redefine a method on a single instance using a closure. In this post we'll look at ways to make this easier on ourselves.

I should stress that I'm a Ruby n00bie, so take this all with a grain of salt. Please let me know if I've got it wrong.

First attempt

We found out last time that to add an instance method to a single object using a closure we had to obtain a reference to that instance's metaclass (or eigenclass as it is also known) using the class << instance syntax. We could then use the metaclass's private define_method via the send method.

class Greeter
    def say_hello
        puts "Hello World!"
    end
end
greeters_metaclass = class << greeter; self; end
greeters_metaclass.send(:define_method, :say_hello) do
    puts "G'day #{name}"
end
greeter.say_hello
#=> G'day Anonymous Dave

We can make this a bit easier by extracting this into a method. If we put it in a Ruby module, we can open up any class or instance and import this code as required.

module Meta
    def define(name, &block)
        meta = class << self; self; end
        meta.send(:define_method, name, block)
    end
end

And this works the same way as the previous code, just a bit neater:

# Open up Greeter class and import the Meta module:
class Greeter
    include Meta
end

# Our greeter gains the new syntax:
name = "Anonymous Dave"
greeter.define(:say_hello) do
    puts "G'day #{name}"
end

greeter.say_hello
#=> G'day Anonymous Dave

We can do better.

Second attempt

It would be really nice to be able to just write greeter.define.say_hello { puts "blah" }. Let's make it work. We'll need to make a define method that returns some sort of object that responds to any method, whether it exists or not. Ruby has a handy method_missing method we can define which will be invoked in the event it cannot resolve a particular method. We can just move the method creation into there:

module Meta
    def define
        meta = class << self; self; end
        Creator.new(meta)
    end
    class Creator
        def initialize(meta)
            @meta = meta
        end
        def method_missing(symbol, *args, &block)
            @meta.send(:define_method, symbol, block)
        end
    end
end

This then gives us a nicer syntax whenever we import the module:

name = "Anonymous Dave"
greeter.define.say_hello do
    puts "G'day #{name}"
end
greeter.say_hello
#=> G'day Anonymous Dave


greeter.define.say_hello do
    puts "Howdy!"
end
greeter.say_hello
#=> Howdy!

Parting thoughts

Like I said, I'm new to this stuff and have absolutely no idea if this is completely terrible. I do know that I'm really impressed with how easily even a newbie like me could add the syntax I wanted to absolutely everywhere in Ruby I could possibly need it, in a fairly neat and reusable way.

Replacing a Ruby instance method with a closure

I was recently playing around with replacing methods on specific Ruby instances. Everything is open for change in Ruby so I thought it would be a piece of cake. And in a way it was. The catch was that I wanted the method definition to close over some local variables I had declared (the same way () => localVar; will close over localVar for .NET lambdas), and this proved to be a little trickier than I expected.

Basic instance method replacement

Say we have an incredibly exciting Greeter class that says hello:

class Greeter
    def say_hello
        puts "Hello World!"
    end
end

greeter = Greeter.new
greeter.say_hello

#=> Hello World!

We can easily re-define this method on a specific instance like this:

def greeter.say_hello
    puts "Howdy World!"
end
greeter.say_hello
#=> Howdy World!
Note: It is just as easy to redefine this method on all instances of Greeter by opening up the class as Derick points out in the comments. In this case I just want to replace the method on the specific greeter instance, not on all instances of the Greeter class.

Now say that rather than hard-coding a greeting in our re-defined method, we want to reference a local variable instead (useful for replacing methods for unit tests).

name = "Anonymous Dave"
def greeter.say_hello
    puts "Hello #{name}"
end

#This won't work:
#  greeter.say_hello
#It gives this exception:
# in `say_hello': undefined local variable or method `name' for #<Greeter:0x337e1e0> (NameError)

This doesn't work because when we get to def greeter.say_hello we've changed the current scope to the greeter instance, and greeter knows nothing about name; it does not exist in the scope say_hello is running in. What we really want is to capture the name variable via a closure when we define the method, and in Ruby, we can do this with blocks and Procs.

A detour into Ruby internals and other stuff I don't understand

I'm completely out of my depth here, but here is my Ruby n00bie interpretation of some Ruby mumbo jumbo. First, everything in Ruby is an object, and every object has a class (as near as I can tell). Even nil (Ruby's null):

puts nil.class
#=> NilClass
puts nil.methods.count
#=> 71

If you're from a .NET background, we've just called null.GetType().GetMethods().Length and lived to tell the tale (and been told 71 apparently :)).

Anyway, the point is that everything is an object and they all have classes. Our greeter object is an instance of the Greeter class we defined earlier. Ruby has all kinds of interesting ways of interacting with instances and classes, including instance_eval and class_eval which let us run code within the context, or scope, of both instances and classes. Unfortunately none of these seem to work the scope in a way that will let us add an instance method to a specific instance using a closure (please let me know if I'm wrong).

There is another option: Ruby metaclasses (a.k.a. eigenclasses). It turns out that when I said that greeter's class is Greeter, I sort of lied. In fact, Ruby itself will dissemble slightly when you run greeter.class and it says Greeter. Every object has a class, but also a metaclass. And a metaclass' class has Class, as well as it's own metaclass, which I guess is a meta-metaclass. And so on into madness.

In a valiant attempt to get back on topic, the idea is that you can add a method to a metaclass that will only be available on the instance. If you modify a method on the class then it will appear on all instances.

The next thing we need to know is that everything that inherits from Class has a very useful method called define_method which defines a method from a block. Unfortunately this method is private. But luckily you can invoke private methods in Ruby using the send method. The send method is also cursed and contains potassium benzoate... kidding.

Believe it or not, we can now solve the original problem.

Instance method replacement with closures

So what we need to do is get access to the greeter instance metaclass, and send a define_method call with a block that closes over our local variable. We can access an instance's metaclass by using the class << instance syntax, which will change the current scope to instance's metaclass (so self will refer to the metaclass. This is confusing; see Yehuda's article for a great explanation). And the rest is fairly straight forward:

greeters_metaclass = class << greeter; self; end
greeters_metaclass.send(:define_method, :say_hello) do
    puts "G'day #{name}"
end
greeter.say_hello
#=> G'day Anonymous Dave

name = "closure"
greeter.say_hello
#=> G'day closure

We now have all the pieces put together. We've got our paws on a reference to the instance's metaclass, and we've defined a class using a block (do..end) which will close over our local variable using the metaclass' private define_method via send. Piece of cake, right? :)

Parting thoughts

This tripped me up for a while, and this post is my attempt to try and decipher what is happening. It's not quite as bad as I make it sound, it just requires some understanding about Ruby's class system and closures. Along the way I found these absolutely essential references that will hopefully clear things up a bit for you:

Next post we'll look at a hack to make this a bit easier.

Wednesday, 10 November 2010

Git pre-commit hook to help with multiple identities

I use git both at work and for personal projects. Unfortunately I always forget to properly set my user.email and user.name for new work repositories, and so I end up committing under my personal email address. No big deal, but not exactly brilliant either.

When this happened again recently I decided it would be the last time. Enter Git hooks. By using a pre-commit hook I now make sure I never commit to a repo with a mycompany.com remote unless the configured user email address is a mycompany.com address.

#!/usr/bin/env ruby

# Make sure that the user has a MyCompany email address before committing
# to a repository that contains a MyCompany remote.

useremail=`git config user.email`
remotes=`git remote -v`

if remotes.match(/mycompany\.com/) and not useremail.match(/mycompany\.com/) then
    puts "Pre-commit error: #{useremail.strip} is not a MyCompany email address "
    puts "but this repository has MyCompany remotes."
    puts
    exit 1
end

Obviously there's much more we could do with this (ensure non-company repos don't get commits under company email addresses for one), but this simple version solves the immediate problem.

This code goes in a pre-commit file in your repo's .git/hooks directory, or in your Git templates directory to apply to all future repos (C:\Program Files (x86)\Git\share\git-core\templates\hooks on my machine. You can apply it to existing repos by re-calling git init which will re-copy the templates). The file needs to be executable (which it is if you're running Windows :)), and you'll obviously need Ruby for this specific example.

For more information and some much more impressive examples of Git hooks, have a look at Glenn Gillen's post on Slaying dragons with git, bash and ruby.