Grokking Idiomatic Dynamic Ruby
I want to use Ruby to implement something very much like Active Record,
but I'm a Ruby newbie. What I most need to understand is dynamic
Ruby, from which it's said the language derives much of its power.
So I read and reread the relevant chapters of the Pickaxe book,
which are clear enough on the 'how,' but not so much on the 'why.'
That is, the book adequately explains how to use certain dynamic
features, but not why and when I would want to use them. I've
also been lurking on why the lucky stiff's blog, which is chock full of clever, dynamic stuff. I could mostly follow the dynamic bit of why's (poignant) guide to Ruby, but I wasn't truly internalizing it; I was not groking it.
Thinking I'll understand things if I just try it, I've been both
studying the Active Record code while simultaneously writing my own.
My hopes were dashed, though, when I discovered this fundamental
structure of ActiveRecord::Base.
class Base
# class methods and variables
class << self
# more methods
end
# Instance methods and variables
end
My question was, what the hell is this class << self stuff doing in the middle of the Base class? No worries, a little Googling turned up this very nice explanation on RubyGarden: the Singleton Tutorial.
This is apparently a Ruby idiom for adding class methods to a
class without having to type "ClassName." or "self." before
each method name like so: Base.whatever or self.whatever.
Things began to gel, but I hadn'tt yet reached full understanding.
Why? Because my mental picture of the Active Record class
hierarchy kept getting fuzzy as I perused the source code. For
instance, inside what I now know to be the singleton class
(virtual class, metaclass, eigenclass, people can't seem to decide [I like the name "shadow" class]) of the Base class, I found these four class methods:
def table_name
reset_table_name
end
def reset_table_name
name =
"#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
set_table_name name
name
end
def set_table_name( value=nil, &block )
define_attr_method :table_name, value, &block
end
alias :table_name= :set_table_name
private
def define_attr_method(name, value=nil, &block)
sing = class << self; self; end
sing.send :alias_method, "original_#{name}", name
if value
#
use eval instead of a block to work around a memory leak in dev
# mode in fcgi
sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
else
sing.send :define_method, name, &block
end
end
If the Active Record developer uses the default class-naming style of
Active Record (table names are plural, class names are singular, e.g.,
table name "orders" gives class name "Order"), then the table_name
method above will return the name of the associated database table the
first time it's called. But if you trace the call all the way
through, it seems as if the define_attr_method is overwriting the
table_name method to return the just determined database table name for
each subsequent call. "Ick!" says I, thinking back to programmer
school, "self modifying code!" That can't be right. I have
obviously not yet grokked the idioms of dynamic Ruby. The
question now is, what is this line of code referring to: sing = class << self; self; end ?
There was just one thing to do, trace through the object creation
process myself. So I whipped up the following code that echoes
using Active Record, ran it, and now - now - I grok it. The code
was cleaned up and the numerical ordering was added after the scales
fell from my eyes.
class Test
puts "1. Inside base class, but outside any method, object ID is #{self.object_id}"
def meth
puts "9. Inside instance method of base class object ID is #{self.object_id}"
end
class << self
puts "2. Inside singleton class of base class, object ID is #{self.object_id}"
def meth2
puts "4. Inside class method of base class, object ID is #{self.object_id}"
sing = class << self; self; end;
puts "5. Referencing singleton object, object ID is #{sing.object_id}"
end
end
end
class Atest < Test
puts "3. Inside subclass of base class, object ID is #{self.object_id}"
end
puts "Calling base class method directly"
Atest.meth2
puts "6. Outside subclass of base class, Object ID of subclass is #{Atest.object_id}"
class << Atest
puts "7. Inside singleton class of subclass, Object ID is #{self.object_id}"
end
puts "Creating instance of subclass"
a = Atest.new
puts "8. Object ID of instance object is #{a.object_id}"
puts "Calling instance methods"
a.meth
class << a
puts "10. Inside singleton class of instance object, Object ID is #{self.object_id}"
end
If you haven't clicked through any of the links above (and you should),
the gist of dynamic ruby concerns what are best known as singleton
classess. The essence being that every object in Ruby can have a
singleton class dynamically associated with it; a class that inserts
itself between the object and its parent class; essentially making
itself a superclass of the object and a subclass of the original class.
And it's important to remember that classes are themselves
objects in Ruby. That's an inadequate explanation, so, really,
click through those links.
In any event, if you run the above code, it will produce output similar to this (your object IDs will be different):
1. Inside base class, but outside any method, object ID is 441462
2. Inside singleton class of base class, object ID is 441452
3. Inside subclass of base class, object ID is 441432
Calling base class method directly
4. Inside class method of base class, object ID is 441432
5. Referencing singleton object, object ID is 441372
6. Outside subclass of base class, Object ID of subclass is 441432
7. Inside singleton class of subclass, Object ID is 441372
Creating instance of subclass
8. Object ID of instance object is 441152
Calling instance methods
9. Inside instance method of base class object ID is 441152
10. Inside singleton class of instance object, Object ID is 441112
To help crystalize this, study the following illustration. Singleton classes are blue-green, ordinary classes are gray.
When
Ruby processes this program it encounters the class Test definition
first and creates an object for it in memory (step 1). It goes on
to process the inards of class Base and finds the next bit of runnable
code (i.e., not a method definition) to be class << self.
Since no class methods have been defined yet, this idiomatic code
causes Ruby to create a singleton (shadow) class for the Test class
object in which to store its class methods. While inisde this
singleton object we print out it's object ID and add a class method.
The processor then encounters the class Atest < Test
line. This is standard issue subclassing, and causes Ruby to
create a new Atest object (step 3). The Atest.meth2 line is how
class methods are called. When meth2 runs, you'll note that it
runs under the auspices of the Atest object, so that when this line is
run: sing = class << self; self; end; it creates a
singleton object for the Atest class (step 5). This is a critical
component of Active Record, it's what allows, say, a Person object to
have a last_name attribute or a Car object to have a model attribute
even though both descend from ActiveRecord::Base. It also means
that the earlier Active Record code wasn't overwriting the table_name method, it was overriding it, and that's cool.
Next the code asks the Atest object to tell us about itself (step 6).
And then we create a singleton class for it with this bit of code
class << Atest (step 7). Finally, we create an
actual instance of Atest by calling its new method, and ask it to tell
us what it is (step 8). When we call an instance method of object
'a', we can see that we're running as object 'a' (step 9). And we
complete the chain, by creating a singleton class for object 'a' with
the line class << a (step 10).
When we're all done, we have a class/object hierrarchy 6 levels deep.
There's the original Test class and its shadow, where all the
class methods are stuffed. A subclass, Atest, that inherits from
Test and has its own class methods stuffed in its own shadow class.
And an instance of this subclass that has a shadow class
containing methods that are paritcular to it.
Grok it?