my face
About Me

Published Posts

All Posts

New Post


View by Tag:

interviewing, code, testing, philosophy, blog, wantmyjob, virtualization, railsmud, heroku, ruby, published, neoarchaeology, railsgame, rails, juggernaut, astrino, cheaptoad, shannaspizza, mongodb, refactorit, devise, rvm, passenger, jruby, programming, vagrant, chef, railsframe, business, codefolio


Online Portfolio

Resume

Profile on LinkedIn

Recommend me on WorkingWithRails: Recommend Me

Some Simple Ruby Metaprogramming

Posted: 4 years ago (2007-11-09 19:06:29 UTC ) / Updated: 2 years ago (2009-06-01 22:28:14 UTC )

Imported from WordPress

Originally posted on 2007-11-09 19:06:29

Ruby Metaprogramming is a neat subject. There's a lot going on with it. But the easiest-to-find explanation isn't very clear, no matter how neat it is. Neither is the one most people recommend. There are some excellent, if brain-bending, examples out there if you look, and even one reasonable example, even if it's very involved. There are several frameworks for Ruby that use it extensively.

What doesn't seem to exist is a simple, step-by-step buildup of what 'metaprogramming' really means in Ruby and how you do it. Metaprogramming is a little bit difficult, like anything where you're keeping track of two levels at once. But it's a lot more straightforward than it looks... So this article will try to make that clear, in ways that others don't. I think it's important to explore this topic -- it's probably the most useful set of language idioms that Ruby currently has.

In Ruby, a class definition is really a lot like a loop, an if/else, or other similar statement. Ruby runs through each line in turn and does what it says to do. For instance,

class MyClass

puts "Here I am!\n"
end

If you run this, it prints "Here I am!". It's really just going through and running your code. Most other languages aren't like this. If you put a printf() in the middle of a structure definition, your C compiler will become very grumpy and won't compile it at all. But Ruby has done a very interesting thing in letting you run random code in there. You can also use 'if':
class MyClass

if(true)
def my_func
puts "My_func is defined!\n"
end
end
end

You can also use loops and other normal Ruby code. So far, not so useful. But interesting! But what if you used a loop?
class MyClass

(0..10).each do |idx|
argname = "myfunc_#{idx}" # make a string like "myfunc_7" from the index
define_method argname do
puts "You called method number #{idx}!\n"
end
end
end

myc = MyClass.new
myc.myfunc_3


If you've ever had a bunch of irritating functions to write that all did similar things then you can probably see how that's useful. Imagine writing a set of accessors that added debugging print statements, or a set of type-checked methods. Since Ruby allows you to get a list of all the methods a class supports by calling "instance_methods" on it, you could even add a debug version of every function, or replace the old function name with one that does a debugging print and then calls the old one. Check below this code for an explanation of how it works:
class MyClass

def test(arg)
print "Test method received #{arg}.\n"
end

instance_methods.each do |method_name|
aliased_method_name = method_name + "_alias"

alias_method(aliased_method_name, method_name)
method_proc = proc do |*args|
puts "You called #{method_name}!\n"
send_alias(aliased_method_name, *args)
end
define_method(method_name, method_proc)
end
end

myc = MyClass.new
myc.test("blah")


This is a little complicated, so let's look at what it's doing. We define a class called MyClass, and a simple function called "test" that prints a message and prints out its argument. At the very end of the code we create a new object of type MyClass, and we call "test" with the string "blah". So far, so good. If we called just that, minus the weird metaprogramming stuff, it would print "Test method received blah."

But in the middle, we get a list of every instance method that MyClass supports (remember, MyClass is automatically a Class, which is a Module, so it supports a bunch of stuff automatically). For every method, we add a new alias for it -- "define_method" gets aliased as "define_method_alias", "test" gets aliased as "test_alias", and so on. Then, we replace the old method with one that prints out "You called <method>!", for whatever method we just called. And then, we call the old method.

So we'll still see "Test method received blah," but first we'll see "You called test!". Neat, no?

Our aliasing will actually handle any number of arguments for the method -- our new method takes "*args", which means "take any number of arguments and combine them into a list called 'args'." Then when calling send_alias, we add "*args" to the end of the call. In a function call it means the opposite -- take the list "args" and treat it as if it were a bunch of arguments, not a single argument of type Array.

So why do we call send_alias, rather than just send?. That's because we're using the original version of send, the one we stuffed into an alias. Otherwise we'd see "You called send!", but we'd see it an infinite number of times because it would just keep calling send, over and over, forever. Normally we'd just call send, but it's different because we're messing with send. We could also fix this problem by not aliasing send, perhaps with a line like "next if method_name == send" right after the "each".

That's where we'll leave it for now. Check back for more later.

Previous: And then... / Next: The 'Self' Variable and Eval in Ruby

Edit | Destroy | See All Posts

blog comments powered by Disqus