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
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:
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
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.
By using a few simple Ruby methods, we have replicated Ruby's
define_methoddefines a method on the caller.
instance_variable_getgets an instance variable.
extendwith 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
Next time we'll get into some more complex examplesblog comments powered by Disqus