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
@name
end
# attr_reader(:phone) generates
def phone
@phone
end
end
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`
instance_variable_get("@"+attr_name.to_s)
end
end
end
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
end
end
person = Person.new("Mortimer")
person.name
#=> "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
instance_variable_get("@"+name.to_s)
end
end
end
end
*attr_names
lets us pass in zero or more attribute names. For each of those, a corresponding method is defined.
Recap
By using a few simple Ruby methods, we have replicated Ruby’s attr_reader
.
define_method
defines a method on the caller.instance_variable_get
gets an instance variable.- Calling
extend
with a Module, exposes the module’s methods as class methods.
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