A Beginner Guide to Creating Class Macros
If you are an experienced rubyist or at least have taken time to play with Rails or many other ruby gems, chances are you have already seem code like this:
class Person < ActiveRecord::Base has_many :books # <---- that's what i'm talking about! end |
The first time i’ve stumbled upon one of these weird “things”, i’ve figured that somehow the gem had created a new language construct or something alike that scared the hell out of me!
That’s until i’ve finally came to know that when you are declaring a class, you’re actually executing code, not only declaring methods, constants and instance variables. So, after all, code like the one above is simply calling a method from within a class definition.
At first look, it seems that it would be pretty useless to execute code inside a class definition instead of within a method, but when the ruby flexibility and power come to play, you’re really empowered by this possibility as it allows you to create new methods, modify object behavior, redefine instance variables – and much more – dynamically! Let’s see an example:
# I assume that a 'buzzer' function simply puts "Bzzzzz" class Foo define_buzzer :lucas end Foo.new.buzz_lucas # -> Bzzzzz |
The example above will create a method that is named according to a symbol you give as an argument. If that doesn’t look straightforward to you, let’s try another example:
class Foo define_buzzer :lucas, :extra_buzz => "Hey! Hey! Hey!" end Foo.new.buzz_lucas # -> Bzzzzz Hey! Hey! Hey! |
Now that’s something you might want to use: a dynamically named class that generates dynamically modified code! So instead of writing this:
class Foo def send_welcome_mail subject = "Welcome E-Mail" to = customer.email from = "Welcomer <welcomer@foo.bar>" send_email(subject,to,from) end def send_purchase_mail subject = "Your purchase has been confirmed!" to = customer.email from = "Foo Sales <sales@foo.bar>" send_email(subject,to,from) end end |
You could simply write this and get the exact same functionality, without the messy repetition:
class Foo def self.define_mailer(*args) define_method "send_#{args[0]}_mail" do send_email(args[1][:subject], args[1][:to] , args[1][:from]) end end define_mailer :welcome, :subject => "Welcome E-mail", :from => "Welcomer <welcome@foo.bar>", :to => customer.email define_mailer :purchase, :subject => "Your purchase has been confirmed!", :from => "Foo Sales <sales@foo.com>", :to => customer.email end |
Actually there’s much more that you can do with this powerful possibilities ruby gives you – which are outside the scope of this little introduction – but let’s start with the basics: how can you start creating your own class macros and get that “how the heck did you do that?” look from your fellow non-rubyist friends (if you are like me, chances are there won’t be much that are outside the ruby club yet
.
Part One: Define class methods to define macros that can be used within a class
When you are inside a class definition, all method calls are directed to the class of the class you are defining (which is Class, and that’s a weird sentence, isn’t it?). When you define methods using the: def self.class_method declaration, you are inserting the method into an intermediary class that is between the class you are defining and it’s class (which is Class, remember?) – some folks call this intermediary classes singleton classes or eigenclasses – so this way whenever you call a method inside a class definition, the interpreter will look for that method firstly in this “intermediary class” and then in the class Class (oh dear..) and up to the parent-of-all-parents Object (or, on ruby 1.9, BasicObject) class.
This means that if you define class methods to a class, you can actually call these methods inside the class definition to get the functionality we’re looking for! Let’s try that:
class Foo def self.create_mother_call_for(name) define_method("call_#{name}_mother") do puts "Hey #{name}'s mother! Come here!" end end create_mother_call_for :john end Foo.new.call_john_mother # -> Hey john's mother! Come here! |
And that’s it, you’ve created your first macro!
Part Two: Let’s broaden the horizon defining those methods inside Modules!
Let’s say you got shared functionality between some classes (you have, don’t you?) and it’s pretty repetitive stuff but not enough to violate the Single Responsibility Principle and to create another class to handle that. It seems like a good time to use our recently-learned class macros to help us there. To use class macros that live outside the class methods, you can use a method called extend, which includes module methods into that “intermediary class” we talked about (and that’s the last time i’m calling them like that! From now on you’ll hear eigenclass!). Nothing better than an example to show how beautifully you can do that:
module MotherCaller def create_mother_call_for(name) define_method("call_#{name}_mother") do puts "Hey #{name}'s mother! Come here!" end end end class Baby extend MotherCaller create_mother_call_for(:john) end Baby.new.call_john_mother # -> Hey john's mother! Come here! |
And it works just as you wanted! Class macros can now be included into any class’ eigenclass!
Part Three: You’re getting excited aren’t you?
I really wanted to give just an introduction to those of you that never heard of this kind of ruby trick, so it’s totally out of the scope of this simple blog post to talk about some pretty serious stuff you can do with class macros and metaprogramming. So, to help those of you who want to learn more, here goes a bunch of resources you can use to learn more about this kind of trickery:
- Text: http://ruby-metaprogramming.rubylearning.com/ – A simple guide you can follow to learn a lot
- Text: http://viewsourcecode.org/why/hacking/seeingMetaclassesClearly.html – A great introduction by the genius Why the lucky stiff
- Text: http://practicalruby.blogspot.com/2007/02/ruby-metaprogramming-introduction.html – A Really nice Introduction
- Text: http://ola-bini.blogspot.com/2006/09/ruby-metaprogramming-techniques.html – An overview on metaprogramming
- Video: The Ruby Object Model by Dave Thomas
- Video: The Ruby Object Model and Metaprogramming by Dave Thomas – Demo from Pragmatic Programmers Screencasts
- Book: http://www.pragprog.com/titles/ppmetr/metaprogramming-ruby
- Screencast: http://www.pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming
I really recommend you dig deep into this topic since it allows you to fully understand some advanced code and create solutions in a very specific, creativity-enabling way!
Do you have any thoughts on this? Comments are more than welcome!
