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
Comments
Post a comment









Remember personal info?