Active Record in Ruby on Rails

By: David Heinemeier Hansson Viewed: 153163 times    

Active Record connects business objects and database tables to create a persistable domain model where logic and data are presented in one wrapping. It‘s an implementation of the object-relational mapping (ORM) pattern by the same name as described by Martin Fowler:

  "An object that wraps a row in a database table or view, encapsulates
       the database access, and adds domain logic on that data."

Active Record‘s main contribution to the pattern is to relieve the original of two stunting problems: lack of associations and inheritance. By adding a simple domain language-like set of macros to describe the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the gap of functionality between the data mapper and active record approach.

A short rundown of the major features:

  • Automated mapping between classes and tables, attributes and columns.
     class Product < ActiveRecord::Base; end
    
     ...is automatically mapped to the table named "products", such as:
    
     CREATE TABLE products (
       id int(11) NOT NULL auto_increment,
       name varchar(255),
       PRIMARY KEY  (id)
     );
    
     ...which again gives Product#name and Product#name=(new_name)
  • Associations between objects controlled by simple meta-programming macros.
     class Firm < ActiveRecord::Base
       has_many   :clients
       has_one    :account
       belongs_to :conglomorate
     end
  • Aggregations of value objects controlled by simple meta-programming macros.
     class Account < ActiveRecord::Base
       composed_of :balance, :class_name => "Money",
                   :mapping => %w(balance amount)
       composed_of :address,
                   :mapping => [%w(address_street street), %w(address_city city)]
     end
  • Validation rules that can differ for new or existing objects.
      class Account < ActiveRecord::Base
        validates_presence_of     :subdomain, :name, :email_address, :password
        validates_uniqueness_of   :subdomain
        validates_acceptance_of   :terms_of_service, :on => :create
        validates_confirmation_of :password, :email_address, :on => :create
      end
  • Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
     class Person < ActiveRecord::Base
       def before_destroy # is called just before Person#destroy
         CreditCard.find(credit_card_id).destroy
       end
     end
    
     class Account < ActiveRecord::Base
       after_find :eager_load, 'self.class.announce(#{id})'
     end
  • Observers for the entire lifecycle
     class CommentObserver < ActiveRecord::Observer
       def after_create(comment) # is called just after Comment#save
         Notifications.deliver_new_comment("david@loudthinking.com", comment)
       end
     end
  • Inheritance hierarchies
     class Company < ActiveRecord::Base; end
     class Firm < Company; end
     class Client < Company; end
     class PriorityClient < Client; end
  • Transactions
      # Database transaction
      Account.transaction do
        david.withdrawal(100)
        mary.deposit(100)
      end
  • Reflections on columns, associations, and aggregations
      reflection = Firm.reflect_on_association(:clients)
      reflection.klass # => Client (class)
      Firm.columns # Returns an array of column descriptors for the firms table
  • Direct manipulation (instead of service invocation):
       long pkId = 1234;
       DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
       // something interesting involving a cat...
       sess.save(cat);
       sess.flush(); // force the SQL INSERT
    

    Active Record lets you:

       pkId = 1234
       cat = Cat.find(pkId)
       # something even more interesting involving the same cat...
       cat.save
  • Database abstraction through simple adapters (~100 lines) with a shared connector
     ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
    
     ActiveRecord::Base.establish_connection(
       :adapter  => "mysql",
       :host     => "localhost",
       :username => "me",
       :password => "secret",
       :database => "activerecord"
     )
  • Logging support for Log4r and Logger
      ActiveRecord::Base.logger = Logger.new(STDOUT)
      ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
  • Database agnostic schema management with Migrations
      class AddSystemSettings < ActiveRecord::Migration
        def self.up
          create_table :system_settings do |t|
            t.string :name
            t.string :label
            t.text :value
            t.string :type
            t.integer  :position
          end
    
          SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
        end
    
        def self.down
          drop_table :system_settings
        end
      end
    

Most Viewed Articles (in Ruby )

Latest Articles (in Ruby)

Comment on this tutorial