July 16, 2007
ActiveRecord Inference
If you know anything about Rails, you'll know that ActiveRecord allows the attributes of objects to be inferred from their associated table column names. You may be curious about how this happens. As I'll show you, it's due to dynamic features of the Ruby language.
Suppose we have a model class.
class Programmer < ActiveRecord::Base has_many :skills def full_name first_name + " " + last_name end end
This class is based on the following table:
create table programmers ( id integer not null, first_name varchar(255) not null, last_name varchar(255), favourite_language varchar(255), primary key (id) );
Now we can code:
joe = Programmer.new joe.first_name = "Joe" joe.last_name = "Bloggs" joe.favourite_language = "Ruby" joe.save
So, how does Ruby enable Rails to infer the attribute names?
The answer can be found in a couple of classes within ActiveRecord::Base.
def initialize(attributes = nil) @attributes = attributes_from_column_definition @new_record = true ensure_proper_type self.attributes = attributes unless attributes.nil? yield self if block_given? end
def method_missing(method_id, *args, &block) method_name = method_id.to_s if @attributes.include?(method_name) or (md = /\?$/.match(method_name) and @attributes.include?(query_method_name = md.pre_match) and method_name = query_method_name) define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods md ? query_attribute(method_name) : read_attribute(method_name) elsif self.class.primary_key.to_s == method_name id elsif md = self.class.match_attribute_method?(method_name) attribute_name, method_type = md.pre_match, md.to_s if @attributes.include?(attribute_name) __send__("attribute#{method_type}", attribute_name, *args, &block) else super end else super end end
Looking at these methods we can see that method_missing dynamically generates the setter methods for the Programmer object
from the @attributes loaded in initializer.
The method_missing method is one of the fundamental tools within the Ruby metaprogramming kit bag. It allows the programmer to enable classes to respond to arbitrary method calls. It is the key to how ActiveRecord inference works.
I think this is a good example of how the power of Ruby's dynamic features underpins the Rails framework. There are many more waiting to be explored in the source code.
Posted to Rails by Keith Pitty
