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?
Entries (RSS)
Huh, zzland??
What does it mean?
I’ll let you guess