Understanding ActiveRecord: A Gentle Introduction to the Heart of Rails (Part 1)

by Gregory Brown

Rails is advertised as a model-view-controller web framework. In this two-part article, we'll be focusing on the M in MVC. Specifically, we'll be talking about the object-relational mapping (ORM) software, ActiveRecord, that forms the core of any data-centric Rails application. ORM allows us to think in terms of objects and write in our programming language of choice (in this case, Ruby) while still getting the persistence and optimization benefits of SQL databases.

Though we could talk a lot about the theory behind data modeling and object relational mapping, perhaps the best way to learn Rails is by doing. The first part of this article will walk you through ORM fundamentals using ActiveRecord by example, and the second part will aim to build a small but functional application to show how ActiveRecord works within its setting and in concert with the other toolsets Rails provides.

Though you don't have to be a Ruby or Rails guru to understand this tutorial, having the most recent Ruby and Rails API documentation handy will probably make things easier to follow.

Tasty: The World's Best Social Bookmarking Half-Application

Social bookmarking is a simple set of concepts to model, which is why I've chosen it as the example for this article. We'll be modeling users, entries, and tags. Our implementation will be very simple, so you shouldn't consider it "production ready" by the end of this article. Also, note that in practice, you might consider using the very nice acts_as_taggable plugin for tagging support, but here we'll be rolling our own basic implementation. Besides, if Rails lets you build a blog in 15 minutes, I don't see why we can't build basic folksonomy in 5.

Let's jump right in and start building stuff. Since entries are probably the most straightforward to implement, we'll start there, just as soon as we get a project hooked up.

Setting Up Your Environment

First, let's create the project:

$ rails tasty -d sqlite3

I'll be using SQLite for simplicity here. Using SQLite with your Rails applications allows you to completely avoid configuring your database. Since it is file-based, you do not need to have a database service running to use your Rails app. I usually use the BeAlertWhenOnSqlite3 page from the Camping wiki to remind me how to get the adapter installed.

Entry: Our First Tasty Model

Now it's time to define the simplest model imaginable for entries for this little bookmarking app. We'll eventually need to hook in tagging and users, but right now, the most bare-bones set of attributes an Entry is likely to have is something like this:

  • url
  • short_description
  • long_description

So this would be enough to model a set of data such as:

short_description: chunky bacon
long_description: The best darn chunky bacon resource on the net

That should be enough to get us going. We might want to do some timestamping, but Rails can actually help us out there, which you'll see in just a moment.

You'll notice I'm not overly worried about making changes to this model later. One place Rails really shines is through its migrations, which essentially provide a way to develop your database schema iteratively in pure Ruby. So if we need more fields later, or we need to implement different relationships with this data, we can do so down the line without much pain.

So let's write a simple schema, hook up the model, and load the stuff into the database.

$ ./script/generate model entry

You'll see this created a file called db/migrate/001_create_entries.rb . This will be where we define what attributes our Entry object will have (at least initially). If you're already familiar with SQL schema definitions, this shouldn't be too surprising to you. My definition follows:

Entry Migration (db/migrate/001_create_entries.rb)

class CreateEntries < ActiveRecord::Migration
 def self.up
   create_table :entries do |t|
     t.column :url, :string
     t.column :short_description, :string
     t.column :long_description, :text
     t.column :created_at, :datetime
     t.column :updated_at, :datetime

 def self.down
   drop_table :entries

To load this into your database, just run 'rake db:migrate'. For now, that's all you'll need to do to begin populating this model with data!

It might be surprising that we've ignored implementing a unique identifier for our record. This is because Rails does this for us via an auto-incrementing integer attribute 'id', which is added by default in your migrations. You can change this behavior if necessary, but it's usually just one less thing to worry about when using Rails.

Basic CRUD Operations on Our Entry Model

CRUD stands for Create-Read-Update-Delete, and this is the core set of functionality that ActiveRecord provides.

Let's use the Rails console (a wrapper around Ruby's irb), to play with some of these basic features and add some data to our application:

$ script/console 
Loading development environment.

I'll be using Ruby's pp library to make these examples more readable; you can enable this and follow along at home like so:

>> require "pp"


We'll start by loading in our original example data, the hobix entry.

>> Entry.create( :url => "", 
?>                  :short_description => "chunky bacon",
?>                  :long_description => "The best darn chunky bacon resource on the net" )

Reading Records

If we ask for a listing of all the entries, you can see ours does indeed show up.

>> pp Entry.find(:all)
    {"updated_at"=>"2007-04-06 17:41:07",
     "short_description"=>"chunky bacon",
     "long_description"=>"The best darn chunky bacon resource on the net",
     "created_at"=>"2007-04-06 17:41:07"}>]

You'll see the data we asked it to create, as well as three automated fields, id, created_at, and updated_at. Right now the latter two are the same value, but you'll see this does the right thing in an upcoming example.

Since find(:all) actually returns an array of all of the records, we should try to make sure we can find the individual record as well. Here are a few ways to do that:

#by database id
>> Entry.find(1)

#by dynamic finder
>> Entry.find_by_url("")

The dynamic finder example may seem magical, but Rails will automatically enable these for all the attributes on your model. You can actually do much more complicated find operations, which we'll get into in the second part of this article.

Let's get back to the simple stuff. Reading attributes is quite easy as well.

>> entry = Entry.find_by_url("")
>> entry.short_description
=> "chunky bacon" 
>> entry.long_description 
=> "The best darn chunky bacon resource on the net"

Updating Records

This is where the ORM magic is starting to kick in. Updating is unsurprising as well:

>> entry.short_description = "Super Chunky Bacon" 
=> "Super Chunky Bacon" 
=> true

Note that we do need to tell the object that we want to save the results back to the database.

There are other ways to do updates, which we'll show later.

I mentioned before that we'd be able to show that updated_at does indeed do the right thing. Dumping the object, you can see that this is the case:

>> pp entry
  {"updated_at"=>Fri Apr 06 18:01:40 -0400 2007,
   "short_description"=>"Super Chunky Bacon",
   "long_description"=>"The best darn chunky bacon resource on the net",
   "created_at"=>"2007-04-06 17:41:07"},
   @base=#<Entry:0x2b37d8e0b178 ...>,

Deleting Records

Finally, completing the CRUD circuit, let's confirm that we can kill off this entry and get Entry back to a blank slate:

>> entry.destroy
>> Entry.find(:all)   
=> []

Pages: 1, 2

Next Pagearrow