ActiveRecord

On this page
 - Minimal ActiveRecord
 - Attributes
 - Intercept Attributes
 - Alias Attributes
 - Delegate Attributes
 - Add Attributes
 - Update Attributes
 - Reflection on Attributes
 - Accessing attributes before they have been typecast
 - Protecting Attributes
 - Manipulate Counters
 - Toggle Booleans
 - new versus create 
 - save vs save! and create vs. create! 
 - Understanding save cascades in associations
 - Find
 - Dynamic Attribute-Based Find
 - Callbacks
 - Associations
   - One-to-One: belongs_to and has_one
   - One-to-Many: has_many
   - Many-to-Many: has_many :through and habtm
   - has_many :through, :source
   - has_many :through, :uniq
 - Association Callbacks
 - Polymorphic Associations
 - Aggregations
 - Validations
 - Acts as List
 - Acts as Tree
 - Acts as Nested Set
 - Single Table Inheritance
 - Scoping :with_scope
 
See also: 
 - the Wikipedia Active record pattern entry
 - the Official ActiveRecord Overview 
 - the ActiveRecord::Base API docs
 - the Caboose Edge ActiveRecord API docs

Minimal ActiveRecord

The simplest possible ActiveRecord model has virtually no application specific code, yet has a great deal of functionality. Here's a minimal ActiveRecord model:

And let's say that the monkeys table (see Database Conventions) in our database has the following structure:

+-----------------------------------------------+
| TABLE monkeys                                 |
+--------------+---------------+----------------+
| Field        | Type          | Other          |
+--------------+---------------+----------------+
| id           | integer       | primary key    |
| name         | varchar(50)   |                |
| age          | integer       |                |
| bionic_arm   | boolean       |                |
| species      | varchar(50)   |                |
+--------------+---------------+----------------+

With this simple configuration we have read and write access to all our model's attributes and we can search our existing records in a variety of ways:

Attributes

ActiveRecord infers model attributes from the database. Each attribute gets a getter, setter, and query method.

There are several magic attribute names that you should not inadvertently use - here is a list, more details can be found on the Database Conventions page.

created_at              updated_on               lock_version
created_on              updated_at               type
id                      #{table_name}_count      position
parent_id               lft                      rgt

Intercept Default Attribute Accessors

If you wish to manipulate an attribute before or after it is saved in the model object (and perhaps ultimately the database), you may override the default accessors and use read_attribute and write_attribute to actually change the stored attribute value. For example:

Alias Attributes

See also:
- Ruby alias method alternative

New in Rails 1.2! Now you may use the alias_attribute method to alias an attribute name and automagically acquire getter, setter, and query methods just like a real attribute.

Delegate Attributes

See Also:
 - Mailing list post on this topic

The delegate method allows you to add contained objects methods to your current objects methods. So for example (taken from the mailing list post linked above), the Mephisto blogging software pulls the year, month, and day methods up from the :published_at DateTime object into the Content object:

Add Attributes

You may also add attributes to your model. Perhaps you want a synonym for an existing attribute or alternate access to an existing attribute:

Update Attributes

You may update attributes individually or in mass as a hash using either the attributes=, update_attribute, or update_attributes methods. Both of the update_* methods instantly save the record (assuming validations pass). You should be aware that calling update_* on an unsaved model will save it! New values assigned via the attributes= method must be explicitly saved.

Reflection on Attributes

The attribute_names method returns all of the attribute names for an ActiveRecord object and the attribute_present? method indicates if an attribute has a non-nil value or (if a String) is not empty. You may also use has_attribute? to query if a model has a particular attribute.

Accessing attributes before they have been typecast

Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first. That can be done by using the {attribute}_before_type_cast accessors that all attributes have.

This is especially useful in validation situations where the user might supply a string for an integer field and you want to display the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn’t what you want.

Protecting attributes

See also:
 - Securing your Rails application
 - a strong case for attr_accessible

Sometimes you just don't want model attributes to be easily modifiable (think passwords, account balances, and bionic arms!). You can protect your critical attributes with attr_protected or explicitly note only those attributes that are accessible with attr_accessible.

Increment and Decrement Counters

 - for documentation on the counter_cache see the Cached Counters 
   on the Database Conventions page

There are several methods available to simplify the management of counter attributes

There are also the increment_counter and decrement_counter class methods - both of these methods instantly update the value in the database.

Toggle Boolean Attributes

Boolean attributes may be flipped with the toggle and toggle! methods (the "!" method saves to the database).

Model.new versus Model.create

Both new and create will return a new model object but create also saves the record to the database (assuming it passes validations), so:

save vs save! and create vs create!

the ! methods throw an exception on failure, the non-! methods return false on failure.

Understanding save cascades in associations

See also:
 - the "Unsaved objects and associations" section 
   on the ActiveRecord::Associations::ClassMethods API page

The save cascades resulting from association creation or assignment are not always immediately obvious! Let's see what happens in various circumstances. (You may also want to refer to the Associations section below.)

1. Assigning an object to a one-to-one association

So, if a head has_one body and a body belongs_to a head, what happens when I:

  1. Assign a new head to an existing body
  2. Assign a new body to an existing head

From this example we learn that in a one-to-one relationship that:

  1. adding the has_one object to the belongs_to object does not save the has_one object
  2. adding the belongs_to object to the has_one object instantly saves the belongs_to object

Or, another way of putting it is that only the object with the foreign key in the database is saved when added to other objects.

2. Using the build method to assign to a one-to-one association without saving

We know (from just above) that adding the has_one object to the belongs_to object does not save the has_one object, so that situation is not in question here. But, is it possible to assign a new belongs_to object to a has_one object without saving? (Using the same models and tables as just above...)

3. Assigning an object to a has_many association

has_many and has_one associations work in pretty much the save way. So, like has_one, assigning the has_many object to the belongs_to object will not instantly save it, while the reverse will. The build method may be used here as well to stave off saving the belongs_to object.

Why would we want to defer saving? Suppose you have an object that belongs_to more than one other object and you want to ensure all the associations are properly created when saving. For example, let's say we have a (probably poorly designed) robot-building application and we want to make sure that when we create our robot we get a body and two arms at one go.

4. Assigning an object to a has_and_belongs_to_many association

Whether new associations are saved or not in a habtm relationship depends on the save status of the collecting object.

  1. If the collecting object is unsaved, new associations will not be saved
  2. If the collecting object is saved, new associations will be instantly saved

5. Assigning an object to a has_many :through association

Here are the save cascade rules for has_many :through.

  1. The join model will not automatically save new associations
  2. has_many :through objects will not automatically save pushed associations. In fact, these will be lost unless the associated object is saved!
  3. In Rails 1.2, the << operator should work as for habtm (above)

Find

See also:
- Under the hood: ActiveRecord::Base.find, Part 1 by Jamis Buck
- Under the hood: ActiveRecord::Base.find, Part 2 by Jamis Buck
- Under the hood: ActiveRecord::Base.find, Part 3 by Jamis Buck

ActiveRecord gives you an incredible range of options for selecting records from your database with its basic find method, and you may even drop into explicit SQL with find_by_sql if you must (although this is generally discouraged).

The most basic find selects records by primary key (the autoincremented integer field 'id' by default). If you attempt to access only one record.

then you will receive a single Monkey object (or ActiveRecord::RecordNotFound error if there is no Monkey with id = 1 to be found). Thus, in some cases you may want to ensure that a record exists for a given id before trying to retrieve it from the database,

If you pass in more than one id or an array of ids then you will receive an array in return

You may also retrieve the first record or all records with the standard find syntax. This syntax replaces the deprecated find_first and find_all methods

find has many useful options, listed here and described below.

find options
- :conditions
- :order
- :group
- :limit
- :offset
- :joins
- :include
- :select
- :readonly

Finding with :conditions allows you to supply a SQL WHERE fragment to limit the result.

The conditions array format is not necessary, but will sanitize user input and should always be used whenever you do not have complete control over the input

New in Rails 1.2 - Conditions takes a hash! The comparison is limited to equality and AND.

New in Rails 1.2 - Conditions take arrays!

The :order option takes a valid SQL fragment

The :group option uses the GROUP BY SQL syntax to group the result.

The :limit option sets the maximum size of the result set

The :offset option sets where the select should begin.

Discouraged: The :joins option takes a SQL JOIN fragment. Whenever possible use :include's eager loading (see Associations below).

The :include option performs eager loading on associations using LEFT OUTER JOINs. See the "Eager loading of associations" section in the official ActiveRecord::Association documentation as well as Associations below for more information. Primarily you will use the :include option to reduce the number of database hits for records associated with your primary record. In my example here, if I am planning to count the offspring of every monkey in my database I may load all these offspring in one database query rather than going back and hitting the database for each individual monkey's offspring. You should be aware that using :include may cause introduce ambiguity in :conditions and :order options (see the "Eager loading of associations" section in the official ActiveRecord::Associations documentation).

The :select option is by default the "*" in "SELECT * FROM" but can be replaced by any valid SQL fragment

The :readonly option locks the returned records disallowing any changes.

Dynamic Attribute-Based Find

The dynamic find methods are syntactic sugar aimed at making your queries easier to write and easier to read. It is easiest to show them in use

Callbacks

From the API documentation, "Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic before or after an alteration of the object state." There are callbacks for: validate, create, save, update, destroy, find, new/initialize, and associations (Association Callbacks are discussed in the Associations section below).

You may configure most of these callbacks (excluding find, initialize, and association callbacks) in two different ways: (1) overwrite the default method or (2) register with the macros. The macros are more powerful and more commonly used, especially as the macros add their behavior into a callback hierarchy that is maintained through a class inheritance hierarchy. That is, if you register callbacks for the Animal class (which inherits from ActiveRecord::Base) and subsequently define a Monkey class (which inherits from Animal), then Monkey will also inherit the Animal callbacks.

The callback macros can be used in four different ways (see the very clear discussion in the "Types of callbacks" section on the ActiveRecord::Callbacks API page)

  1. symbol reference to a protected or private method in the model
  2. an object to be messaged with the callback method and record
  3. a method string
  4. a method Proc

The validation callback hierarchy

These series are triggered by calls to:
 object.valid?

New Record                      Existing Record
-----------------------------       -----------------------------
 before_validation                   before_validation
 before_validation_on_create         before_validation_on_update
 << object validated here >>         << object validated here >>
 after_validation                    after_validation
 after_validation_on_create          after_validation_on_update
This series is triggered by calls to:
 Model.create
 object.save (if not previously saved)

before_validation
before_validation_on_create
<< object validated here >>  
after_validation 
after_validation_on_create 
before_save    
before_create 
<< db record created here >> 
after_create
after_save
This series is triggered by calls to:
 object.save (if previously saved)
 object.update_attributes

before_validation
before_validation_on_update
<< object validated here >>
after_validation
after_validation_on_update
before_save
before_update
<< db record saved/updated here >>
after_update
after_save

A save! call triggers the validation series twice. If save! is called on a new record, the new record validation series with *_on_create methods is called twice.

This series is triggered by calls to:
 object.save!

before_validation
before_validation_on_update
<< object validated here >>
after_validation
after_validation_on_update
before_validation
before_validation_on_update
<< object validated here >>
after_validation
after_validation_on_update
before_save
before_update
<< db record saved/updated here >>
after_update
after_save

Several of the save/update methods appear to bypass validation (or at least the validation callbacks) and only call the save callbacks.

This series is triggered by calls to:
 object.increment!
 object.decrement!
 object.toggle!
 object.update_attribute

before_save
before_update
<< db record saved/updated here >>
after_update
after_save

The object.update callback hierarchy only triggers the update callbacks.

This series is triggered by calls to:
 object.update

before_update
<< db record updated here >>
after_update

However, the model update class method triggers a full series of callbacks.

This series is triggered by calls to:
 Model.update

after_find             (if defined)
after_initialize       (if defined)
before_validation
before_validation_on_update
<< object validated here >>
after_validation
after_validation_on_update
before_save
before_update
<< db record saved/updated here >>
after_update
after_save

increment_counter and decrement_counter bypass all callbacks

These methods bypass all callbacks:
 Model.decrement_counter
 Model.increment_counter
This series is triggered by calls to:
 object.destroy

before_destroy
<< record is deleted here >> 
after_destroy

This series is triggered by calls to:
 Model.destroy
 Model.destroy_all

after_find             (if defined)
after_initialize       (if defined)
before_destroy
<< record is deleted here >> 
after_destroy

These don't trigger any callbacks:
 Model.delete
 Model.delete_all

The after_find and after_initialize callbacks may will only be called if the default method is overwritten (no macros here).

Triggered by a call to:
 Model.new

after_initialize       (if defined)

--------------------------------------------

Triggered by a call to:
 object.reload
 Model.find

after_find             (if defined)
after_initialize       (if defined)

Associations

See also:
 - Amy Hoy's Associations Cheat Sheet [pdf]
 - Josh Susser's many informative posts on associations
 - Advanced Topic: Extending ActiveRecord associations by Jamis Buck
 - Advanced Topic: Self-Referential Joins by Leon

There are four associations: belongs_to, has_one, has_many, and has_and_belongs_to_many (often abbreviated habtm - but not in code!). The API documentation for the association methods is very good.

One-to-One: belongs_to and has_one

belongs_to and has_one both express a one-to-one relationship with the main difference being which table has the foreign key, so let's try some variants. First, a one-to-one association that is only accessible from one of the objects.

If we add the has_one association to the non-foreign-key-containing-model, we achieve a one-to-one association with access from both objects.

Alternatively, you can set up models (and matching) tables with dueling belongs_to associations. This "works" (doesn't throw errors) but the behavior is probably not desirable in most situations (I say most because I imagine there are some setups where you may want this behavior). Observe.

The final combination of dueling has_one models (with appropriate tables) will also be accepted by rails (won't immediately throw an error) but doesn't behave in a way that is at all useful (and it is obvious why: there is no place for rails to store the foreign key!)

Rails will let you shoot yourself in the foot! Here we use dueling has_one models and (inappropriately) put a foreign key in one of the models. This "works" and it is possible you won't immediately run into the situation where it breaks - but when you do, it's going to be very very difficult to track down!

One-to-Many: has_many

A Company has_many Employees. A Dog has_many Fleas. A Blog has_many Articles. You get the idea. has_many works pretty much the same way as has_one, and like has_one is typically paired with a belongs_to in the matching model. Here's a simple example.

Many-to-Many: has_many :through and habtm

See also:
 - Josh Susser's post on magic-join-model-creation
 - Josh Susser's post on join-models-not-proxy-collections
 - Josh Susser's post on many-to-many-dance-off
 - this mailing list post from DHH just prior to the introduction of has_many :through in Rails 1.1

I would suggest using has_many :through over habtm unless you can clearly articulate your reasons for preferring habtm (slightly easier model configuration is not a good reason).

Let's first look at has_and_belongs_to_many

Now let's look at the same idea implemented using has_many :through.

has_many :through, :source

There are many circumstances where you might have multiple references to the same table, the :source option lets you do it with style! See the example:

has_many :through, :uniq

The :uniq (new in 1.2) option to has_many :through will eliminate duplicates from the :through collection

Association Callbacks

There are four callbacks you can apply to your association definition

 before_add
 after_add
 before_remove
 after_remove

Typically you will supply these with the name of an instance method, but you can also pass them a Proc. You may pass multiple methods in an array. If an exception is thrown in a before_add or before_remove callback, the object will not be added or removed respectively.

Polymorphic Associations

See also:
 - the Understanding Polymorphic Associations page on the rails wiki

Use a polymorphic association when you want one type of object to be able to belong_to more than one other type of object. In the example I have Items that may be owned by either an Individual or a Company. (Tags as taggable are often implemented as polymorphic associations.)

Aggregations

See also:
 - the ActiveRecord::Aggregations Class Methods page

Aggregations allow you to compose models of ruby classes in part or entirety. In the Globalize plugin, aggregations are used to provide currency localization. Here is a (highly simplified) illustration of how they've used aggregations: (You should check out the full implementation in the globalize projects svn repository. Their Currency class actually takes care of the details needed to localize currency; my Money class here is only fleshed out enough to be usable - although probably not useful!!).

Thus, aggregations:

  1. are constructed using the composed_of macro.
  2. can feed model values through a ruby class
  3. can be told the proper class name with the :class_name option if it cannot be inferred from the aggregation
  4. can be told which db values should be handled by the aggregate in the :mapping option
  5. can be told what db values and in which order should be fed to the aggregate's initialize method in the :mapping option

Validations

See also:
 - the Validations API page
 - the Validations Class Methods API page

Validations help you maintain your data by ensuring that no improper values make it to the database through ActiveRecord. The stock API documentation for validations is very good (each method name below is linked to the API docs.) The validation methods available to you are:

 - validates_acceptance_of
 - validates_associated
 - validates_confirmation_of
 - validates_each
 - validates_exclusion_of
 - validates_format_of
 - validates_inclusion_of
 - validates_length_of
 - validates_numericality_of
 - validates_presence_of
 - validates_size_of
 - validates_uniqueness_of

Acts as List

The acts_as_list macro allows for the easy creation of list structures in your rails application. We'll start with a basic one model, one table list structure and build from there.

See also:
 - the Acts as List section on the Database Conventions page.
 - the class method API docs for acts_as_list
 - the instance method API docs for acts_as_list 

Before we look at some examples, here are the methods available to acts_as_list models.

 - in_list?
 - first?
 - last?

 - insert_at
 - remove_from_list

 - increment_position
 - decrement_position

 - higher_item
 - lower_item

 - move_higher
 - move_lower
 - move_to_top
 - move_to_bottom

First a simple one model, one table example

Now a two model, two table example

Acts as Tree

ActiveRecord has built-in support for a tree structure.

See also:
 - the Acts as Tree section on the Database Conventions page.
 - the class method API docs for acts_as_tree
 - the instance method API docs for acts_as_tree 

Before we look at some examples, here are the methods available to acts_as_tree models.

 - root
 - parent
 - ancestors
 - children
 - self_and_siblings
 - siblings

Acts as Nested Set

ActiveRecord has built-in support for a nested set structure.

See also:
 - the Acts as Nested Set section on the Database Conventions page.
 - the class method API docs for acts_as_nested_set
 - the instance method API docs for acts_as_nested_set 

Before we look at some examples, here are the methods available to acts_as_nested_set models.

 - root?
 - child?
 - unknown?

 - add_child

 - all_children
 - direct_children
 - full_set

 - children_count
 - before_destroy  (implements this callback such that pruning 
                    a branch doesn't mess up the counts)

Single Table Inheritance

See also:
 - the Single Table Inheritance section on the Database Conventions page.
 - the "Single table inheritance" section in the ActiveRecord::Base API docs.
 - Single Table Inheritance in Rails S5 presentation by Skip Baney

ActiveRecord includes support for the Single Table Inheritance pattern. Skip Baney has a very good online slideshow presenting Single Table Inheritance In Rails.

Scoping :with_scope

See:
 - maiha's caboose posting on nested with_scope
 - the API with_scope docs
 - with_scope with scope by Chris Wanstrath

More coming soon! But until then, maiha'a post and the api docs ably explain this concept.