Rails style meta programming in Ruby – part 2
December 18, 2007
This is part two of this blog post – if you landed on this page, then please checkout part 1 before reading this part.
Last time, we learned that all methods are executed by sending a message to self, where self is set to the appropriate object automatically. We also learned that class definitions are code which is executed at run time, and that a class is nothing but an object of type Class.
What is a metaclass?
Here is the next important point – Classes are a place to store instance methods, objects are a place to store instance variables. An object cannot store a method, only a class can.
But what about class methods? We know that a class is just an object, and when you say something like
class Foo
def self.hello
puts "hello"
end
end
Foo.hello
You are invoking the hello method on the instance of the object stored in Foo, which happens to be an object of type Class (ie your class definition). So where are these class methods stored?
When you invoke a method on an object, Ruby looks at the object’s class pointer, which contains a reference to the ‘class object’ that defines the class. The class object contains the list of methods defined by the class definition, and a pointer to its super class, which contains another list of methods and a pointer to its super class and so on. Ruby searches along this chain until it finds the method it requires, or calls method_missing if it cannot.
To be consistent, a ‘class object’ should behave in the same way. It has a class pointer that points to a class. Ruby magically creates this virtual class when you define a class method, and uses it to store all class methods for the class. This magically created virtual class is what is known as the classes metaclass.
Note that metaclasses follow the same inheritance chain as classes, inheriting from metaclasses up the chain, which is why this works:
class Foo
def self.hello
puts "parent says hello"
end
end
class Bar < Foo
end
Bar.hello
Classes can have instance variables
So if a class is just an object, and an object is a place to store instance variables, then can a class have instance variables too? Yes it can, but think carefully about how to access them.
Normally, we create a class, say Foo, and in that class defintion use attr_reader to provide methods to access the instance variables. So where do we put our attr_reader defintion to allow access to a class instance variable? We have to put it in the objects class defintion, which in this case is the classes metaclass:
class Foo
@class_instance_var = '1234'
class<<self
attr_reader :class_instance_var
end
def hello
puts "hello"
end
end
puts Foo.class_instance_var
To complete the picture, we just need to know what the following syntax means:
class<<self
...
end
'class<<obj' allows us to extend an object by "building a new class just for object obj". In the context above, it basically changes self to be the metaclass instead of the defining class.
Another way of thinking of it is simply that 'class<<obj' gives you access to obj's metaclass.
When we are in the metaclass, we can define the attr_readers required to read the class instance variables.
Finally some Rails magic
Now we know every we need to know make this work:
class User < ActiveRecord::Base
set_table_name :silly_named_users_table
end
We know that when 'set_table_name' is execute, it is executed against self, which happens to be the object defining the User class, so its just like saying:
class User < ActiveRecord::Base
User.set_table_name :silly_name_users_table
end
This means that the method 'set_table_name' must be in User's metaclass, or in the inheritance chain for it, which includes ActiveRecord::Base. We know its not in User's metaclass, as we didn't put it there, so it must be in ActiveRecord:
module ActiveRecord
class Base
def self.set_table_name (table)
puts "setting the table name to #{table}"
end
end
end
class User < ActiveRecord::Base
set_table_name :silly_named_users_table
end
In the Users class, we want to store this alternative table name - it needs to be stored in a class instance variable in the User class, which is simple (I will also add an attr_reader method to the meta class so we can test the code does the correct thing):
module ActiveRecord
class Base
def self.set_table_name (table)
puts "setting the table name to in AR #{table}"
@tablename = table
end
end
end
class User < ActiveRecord::Base
set_table_name :silly_named_users_table
class<<self
attr_reader :tablename
end
end
puts User.tablename
The above code does as expected - why?
When set_table_name is encountered in the User class definition, self is the class object stored in the constant User. It cannot find the set_table_name method method in User's metaclass, so it looks in the parent class, and finds it, but self is not changed to the parent class, its still the class object stored in the constant User that is how inheritance works.
When we set the instance variable in the parent method, self is still User so we are setting an instance variable in the User class object. Anytime we call a method against an instance of a User object, the method can access that tablename variable and use it to generate the correct SQL. How to do that is left as an exercise for the reader
Entry Filed under: metaprogramming, ruby. Tags: metaprogramming, rails, ruby.
3 Comments Add your own
Leave a Comment
Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
Trackback this post | Subscribe to the comments via RSS Feed
1.
Nick | December 20, 2007 at 3:49 am
I simply love rails—great post!
2.
George I. Joe | December 21, 2007 at 10:52 pm
thanks for your effort, but this is confusing.
3. Ruby, Rails, Rails Plugins, JavaScript, SEO « exceptionz | January 4, 2008 at 3:53 am
[...] Rails style meta programming in Ruby – Part 2 [...]