Active Record in Ruby on Rails
By: David Heinemeier Hansson in Ruby Tutorials on 2009-03-03
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("[email protected]", 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
Add Comment
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
- Your name, rating, website address, town, country, state and comment will be publicly displayed if entered.
- Aside from the data entered into these form fields, other stored data about your comment will include:
- Your IP address (not displayed)
- The time/date of your submission (displayed)
- Your email address will not be shared. It is collected for only two reasons:
- Administrative purposes, should a need to contact you arise.
- To inform you of new comments, should you subscribe to receive notifications.
- A cookie may be set on your computer. This is used to remember your inputs. It will expire by itself.
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
- Although the administrator will attempt to moderate comments, it is impossible for every comment to have been moderated at any given time.
- You acknowledge that all comments express the views and opinions of the original author and not those of the administrator.
- You agree not to post any material which is knowingly false, obscene, hateful, threatening, harassing or invasive of a person's privacy.
- The administrator has the right to edit, move or remove any comment for any reason and without notice.
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Comments