Rails-style Ruby meta-programming
I am giving a Ruby lecture tomorrow (some students happen to be quite lucky in fact). I wanted to present an example of a Ruby On Rails style meta programming technique. For example you can have a find_by_name or find_by_email method on your ActiveRecord objects. Those methods are in fact generated on the fly using a technique which looks like what follows. This has probably been explained a million times somewhere else on the web, but I don't care
So let's start with a very basic contacts book class which looks like:
class ContactBook def initialize @contacts = Hash.new end def add_contact(name, email) @contacts[name] = email end def contact_named(name) @contacts[name] end end book = ContactBook.new book.add_contact "Pierre", "pierre@zzland.fr" book.add_contact "Julien", "julien@zzland.fr" book.add_contact "Yoan", "yoan@zzland.fr" book.add_contact "Mr Bean", "info@mrbean.com"
Nothing impressive here, so now let's get all the contacts whose email contain a given substring:
class ContactBook def email_containing(str) @contacts.select { |key, value| value.include? str } end end def display_contact(contact) puts "#{contact[0]} <#{contact[1]}>" end zzland = book.email_containing "zzland" zzland.each { |contact| display_contact(contact) }
But what if we could have email_thestringtolookfor methods instead of this? Let's do it by evaluating some code on the fly:
class ContactBook def method_missing(id, *args) method_name = id.to_s if method_name[0..5] == 'email_' str = method_name[6..method_name.length] to_eval = <<-END_FUNC def email_#{str} @contacts.select { |key, value| value.include? "#{str}" } end END_FUNC instance_eval to_eval return eval("self.email_#{str}") end end end zzland = book.email_zzland bean = book.email_bean zzland.each { |contact| display_contact(contact) } puts "-----" bean.each { |contact| display_contact(contact) }
method_missingis invoked whenever a message is sent to the class instance, but no matching method name can be found.- If the name starts with
email_, then we are in luck! (note that I should throw an exception if the name doesn't start with this to comply with the Ruby semantics, but I was too lazy for that) - We generate a new method for the instance through evaluation.
- We do not forget to invoke the new method through evaluation, as the original call would return
nilsince the method had not been found.
Nice isn't it?
Related posts:
- Ruby fun with Rinda
- Rails vs .Net
- Script de compilation LaTeX en Ruby
- Revisiting Guice and AOP with AspectJ
- Switched back to 2.x style

October 15th, 2007 - 22:42
Huh, zzland??
What does it mean?
October 16th, 2007 - 13:46
I’ll let you guess