Home > Programming > Really really simple Ruby metaprogramming

Really really simple Ruby metaprogramming

Metaprogramming in Ruby has the reputation of being something only the true zen Ruby masters can even hope to understand. They say the only true way to learn metaprogramming is to train with Dave Thomas in his mountain retreat for five years – the Ruby equivalent of this XKCD:

But it’s not that bad. In fact, I’m going to go so far to say that it’s possible to learn to metaprogram in Ruby without even meeting Dave Thomas. Yeah, I know, it doesn’t sound possible. But just you wait…

What is metaprogramming?

Metaprogramming is what makes Ruby awesome. It’s writing code to write code. It’s dynamic code generation. It’s the move from imperative to declarative. It’s a rejection of Java’s endless repetition of boilerplate code. It’s the living embodiment of DRY. Here’s an example:

class Monkey
  def name
    @name
  end

  def name= n
    @name = n
  end
end

I can see you there, in the middle of the classroom, with one arm straining up, the other one holding it up because it’s so damn hard to hold up. OK, Jimmy, what is it? Oh, you don’t need to write all that code in Ruby? You can just use attr_accessor?

Jimmy’s right. That code snippet above could be written like so:

class Monkey
  attr_accessor :name
end

So, attr_accessor‘s magic right? Well, actually, it’s not. Just like in Scooby-Doo where what looked like magic to start off with turned out to be an elaborate hoax, attr_accessor is just a class method of Module. It defines the name and name= methods on Monkey, as if you’d manually defined them.

And that’s all metaprogramming is. Just a way of adding arbitrary code to your application without having to write (or copy-paste) that code.

An (extended) example

The source code for this example is available in my StringifyStuff plugin on github, which in turn was adapted from Ryan Bates’ Railscast Making a Plugin. Incidentally, if you feel you can improve the plugin, fork me on github!

I’m assuming basic knowledge of Ruby, and some familiarity with Rails would be helpful as well, but not essential.

Have a quick peek at the github repo now – the important files to look at for now are init.rb and lib/stringify_time.

First, stringify_time. This file defines a module called StringifyTime, which defines a method called stringify_time:

module StringifyTime
  def stringify_time *names
    #crazy stuff going on in here
  end
end

The other two files, stringify_stuff and stringify_money are similar.

Now, init.rb:

class ActiveRecord::Base
  extend StringifyStuff
  extend StringifyTime
  extend StringifyMoney
end

This extends ActiveRecord::Base with the three modules listed. This now means that any class that inherits from ActiveRecord::Base (e.g. your models) now has the methods defined in each of those modules as class methods.

The StringifyStuff plugin is used like so:

class Project < ActiveRecord::Base
  stringify_stuff
  stringify_time :start_date, :end_date
end

stringify_time is passed a list of symbols representing the relevant model attributes. It will provide useful accessors for those attributes. Let’s have a look at a simplified version of stringify_time:

module StringifyTime
  def stringify_time *names
    names.each do |name|

      define_method "#{name}_string" do
        read_attribute(name) &&
          read_attribute(name).strftime("%e %b %Y").strip()
      end

    end
  end
end

stringify_time is passed a list of symbols. It iterates over these symbols, and for each one calls define_method. define_method is the first clever Ruby metaprogramming trick we’re going to look at. It takes a method name and a block representing the method arguments and body, and magically adds an instance method with that name, arguments and body to the class in which it was called. In this case, it was called from Project, so this will gain a new method with the name "#{name}_string". In this example, we passed in :start_date and :end_date, so two methods will be added to Project: start_date_string and end_date_string.

So far so good. Now though, it gets a little bit hairy, so hold onto your horses (or equivalent equid).

In a normal model instance method, you’d access an attribute using a method of the same name. So if you wanted to get the start_date converted to a string, you’d write:

def my_method
  start_date.to_s
end

The problem with doing this in define_method is that we don’t have start_date as an identifier – it’s held as a symbol. There are two ways to accomplish the above if start_date was passed in as a symbol:

def my_method attr_sym
  read_attribute(attr_sym).to_s #This is a Rails method
end

or:

def my_method attr_sym
  send(attr_sym).to_s #Ruby built-in method
end

For simplicity, I’m using write_attribute, but send is useful to know about too.

So, back to define_method:

      define_method "#{name}_string" do
        read_attribute(name) &&
          read_attribute(name).strftime("%e %b %Y").strip()
      end

So, each time round the loop, name will be one of the symbols passed into stringify_time. Let’s go with start_date to see what happens. define_method will define a new instance method on the Project class called start_date_string. This method will check to see if there is a non-nil attribute called start_date, and if so, call strftime on it. This formatted date will be the return value of the method.

Wrap up

That’s more than enough for one day (I’ve been told off for writing kilo-word posts before). I’ll explain the workings of the rest of the plugin in a future post. If you want to learn more, I highly recommend David Flanagan’s The Ruby Programming Language.

Metaprogramming is writing code that gives you more code. It’s a bit like the Sorcerer’s Apprentice; in fact, I think that should be recommended watching for this subject.

Be the Sorcerer. Don’t be Mickey.

Categories: Programming Tags: , ,
  1. February 7th, 2011 at 16:06 | #1

    Hello, nice post!

    I would like to suggest a disclaimer at the top saying that you just add methods to classes. That would save some time reading for people who don’t know Ruby, but grasp concepts of metaprogramming. :)

    Out of curiosity, can you build classes in Ruby dynamically?

  2. Code Marshal
    February 9th, 2011 at 05:47 | #2

    Ruby is a programming language for donkeys.

  3. February 9th, 2011 at 09:18 | #3

    @Micha? Moroz Thanks!

    I see where you’re coming from, but the post is more targeted at those who haven’t done any metaprogramming before, and I didn’t want to muddy the waters by introducing certain things too early.

    Yes, you can build classes dynamically, here is an example.

  4. February 9th, 2011 at 09:19 | #4

    @Code Marshal Thanks for your insightful comment Code Marshal. I was going to delete it, but I realised that it perfectly sums up the views of the anti-Ruby brigade. Thanks again!

  5. February 9th, 2011 at 18:27 | #5

    Ah, sorry that I didn’t make my point clear.

    I just don’t accept the way of talking about things like arcane powers. Mainly because the people who are the target of this article would think that there is not much beyond that. Or worse, that the thing is really hard, effectively turning them down. A few even would be fooled by the 5 years mentioned before. :) For me, learning is about de-mystifying the magic, not about applying to become a sorcerer.

    Of course it’s only my opinion.

  6. February 9th, 2011 at 18:40 | #6

    @Micha? Moroz I was trying to poke fun at the people who do talk about these things like arcane powers – I completely agree with you.

    My point about the sorcerer was directly related to Fantasia – Mickey tries to be clever and ends up nearly ruining everything, whereas the sorcerer is restrained with his power. My point wasn’t that these things are magic – just that they’re powerful and can create big problems if you’re not careful.

  7. February 10th, 2011 at 02:55 | #7

    Ah, I understand now. Maybe my brain was a bit off during reading that I didn’t catch it the first time.

    I agree that metaprogramming is like a sharp knife, but on the other hand, the whole programming also should fall into this category. Of course there are languages like Java that try to lower incompetence of coders by it’s structure, but it won’t solve the real problem.

    In this case, dynamic method creation/modification can be irritating unless being documented in the first place you’d start looking for the unexpected behavior (i.e. the definition of the manipulated object). Other than the ability to conceal the code that affects the particular chunk of code anywhere in the codebase, I think that the programming and metaprogramming aren’t much different. And, of course, may come in handy at times. :)

  8. February 10th, 2011 at 09:15 | #8

    @Micha? Moroz Yes, metaprogramming can conceal code, and that can be annoying. But that has to be balanced by the increase in expressivity and DRYness it can bring. As with all things in programming, it relies on the programmer to find the correct balance between the opposing forces.

    Incidentally, I’m sorry about the way my comments system chews up your name! Damn wordpress…

  9. February 10th, 2011 at 15:59 | #9

    Is your database encoded in UTF-8? Because WordPress itself (well, the vanilla one does) can handle pretty much every character in the world. :)

    (The character in question is http://en.wikipedia.org/wiki/L_with_stroke )

  10. February 10th, 2011 at 16:03 | #10

    Ah. The link meant to end with L (underscore) with (underscore) stroke. Accidentally it got emphasized by Markdown.

  11. February 10th, 2011 at 16:19 | #11

    @Micha? Moroz Yes it is, I don’t get why I have this problem.

    I fixed your comment.

    I had a lecturer called Slawek, so I know the character in question. Very embarassing!

  1. February 5th, 2011 at 20:36 | #1
  2. February 7th, 2011 at 12:37 | #2
  3. April 6th, 2013 at 03:09 | #3

Comments parsed as Markdown.