MetaRuby - An Ounce of Meta

Metaprogramming sounds hand wavy and bewildering to a lot of folks. I thought it would be fun to do a series that shows metaprogramming isn't some dark art, or esoteric skill, but simply a handy tool any Rubyist can master.

Metaprogramming is writing code that writes code. That's it.

You are almost certainly using Ruby code that writes code today, let's work through one very simple example to whet your appetite.

Your Own attr_reader

Ruby provides some really handy methods for us like attr_reader and attr_writer. When you call attr_reader :name, :phone, it essentially writes the following code for you:

class Person
  # attr_reader :name, :phone

  # attr_reader(:name) generates
  def name 

  # attr_reader(:phone) generates
  def phone

To make our own attr_reader, we need to define an instance method for each symbol passed in. That instance method should return its corresponding instance variable. Ruby provides two methods we can use to achieve this: define_method and instance_variable_get.

Putting those methods together we end up with the following code:

module MyAttr
  def my_reader(attr_name)
    # Defines the method named by `attr_name`
    define_method(attr_name) do
      # Get the instance variable named by `attr_name`

For now we will just stick this in a Module named MyAttr. Let's try it out by exposing my_reader as a class method:

class Person
  # Use extend instead of include so my_reader is a class method.
  extend MyAttr
  my_reader :name

  def initialize(name)
    @name = name

person ="Mortimer")
#=> "Mortimer"

We just implemented a method that writes methods, great huh? Let's take this one step further so that we can define multiple readers at once just like attr_reader:

module MyAttr
  def my_reader(*attr_names)
    attr_names.each do |name|
      define_method(name) do

*attr_names lets us pass in zero or more attribute names. For each of those, a corresponding method is defined.


By using a few simple Ruby methods, we have replicated Ruby's attr_reader.

This is just a tiny taste of what you can easily do in Ruby. Want to go further? Try implementing a reader method that coerces its input to a given type, or writing the corresponding attr_writer.

Next time we'll get into some more complex examples

blog comments powered by Disqus
Monkey Small Crow Small