Modeling a Database with Active Record

Problem

You have a relational database, and you want to create a model representation of it with Active Record. (We'll be using the cookbook_dev database from .")

Solution

First, create a Rails project called cookbook with:

$ rails cookbook

From the root directory of the cookbook application created, use the model generator to create model scaffolding for each table in the cookbook_dev database (except for the join tables):

~/cookbook$ ruby script/generate model chapter
 create app/models/
 exists test/unit/
 exists test/fixtures/
 create app/models/chapter.rb
 identical test/unit/chapter_test.rb
 identical test/fixtures/chapters.yml
~/cookbook$ ruby script/generate model recipe 
 exists app/models/
 exists test/unit/
 exists test/fixtures/
 create app/models/recipe.rb
 identical test/unit/recipe_test.rb
 identical test/fixtures/recipes.yml
~/cookbook$ ruby script/generate model tag 
 exists app/models/
 exists test/unit/
 exists test/fixtures/
 create app/models/tag.rb
 identical test/unit/tag_test.rb
 identical test/fixtures/tags.yml

Next, add the following declarations to the files in the app/models directory:

~/cookbook/app/models/chapter.rb:

class Chapter < ActiveRecord::Base
 has_many :recipes end

~/cookbook/app/models/recipe.rb:

class Recipe < ActiveRecord::Base
 belongs_to :chapter
 has_and_belongs_to_many :tags end

~/cookbook/app/models/tag.rb:

class Tag < ActiveRecord::Base
 has_and_belongs_to_many :recipes end

Discussion

Active Record creates an ORM layer on top of our cookbook database. This layer allows Rails to communicate with the database via an object-oriented interface defined by Active Record classes. Within this mapping, classes represent tables and objects correspond to rows in those tables.

Our databasebeing relationalcontains one-to-many and many-to-many relationships. We need to supply Active Record with some information about what these relationships are. To do this, we add relationship declarations to the Active Record class definition of each model.

For the one-to-many relationship between chapters and recipes, we've added has_many :recipes to chapters.rb and belongs_to :chapter to recipes.rb. Notice that these declarations double as plain English descriptions of the relationship (for example, "Chapters have many recipes."). This language helps us to conceptualize and communicate complex data models by verbalizing their real-world representations.

The many-to-many relationship between recipes and tags also needs the help of Active Record declarations. We've added has_and_belongs_to_many :tags to recipes.rb and has_and_belongs_to_many :recipes to tags.rb. There's no sign of the intermediate join table, recipes_tags; this is by design. Active Record handles the complexities of maintaining many-to-many relationships and provides an intuitive interface for accessing them from within Rails.

You can verify the existence of the model and its relationships by running an instance of the Rails console. Running script/console from your application's root drops you into an irb session that accesses your Rails environment. (The -s option tells the console to roll back any changes you make to the database when you exit.)

~/cookbook/test$ ruby script/console -s
Loading development environment in sandbox.
Any modifications you make will be rolled back on exit.

First, let's create a Chapter object:

>> c = Chapter.new
=> #<Chapter:0x8e158f4 @new_record=true, @attributes={"sort_order"=>0,
"title"=>nil}>

Then a Recipe object:

>> r = Recipe.new
=> #<Recipe:0x8e131d0 @new_record=true, @attributes={"see_also"=>nil,
"discussion"=>nil, "sort_order"=>0, "title"=>nil, "chapter_id"=>nil,
"solution"=>nil, "problem"=>nil}>

Now, add that recipe to the chapter:

>> c.recipes << r
=> [#<Recipe:0x8e131d0 @new_record=true, @attributes={"see_also"=>nil,
"discussion"=>nil, "sort_order"=>0, "title"=>nil, "chapter_id"=>nil,
"solution"=>nil, "problem"=>nil}>]

Inspecting the Chapter object shows that it added our recipe as expected. (Certainly easier than the corresponding SQL, right?)

>> c
=> #<Chapter:0x8e158f4 @new_record=true, @recipes=[#<Recipe:0x8e131d0
@new_record=true, @attributes={"see_also"=>nil, "discussion"=>nil,
"sort_order"=>0, "title"=>nil, "chapter_id"=>nil, "solution"=>nil,
"problem"=>nil}>], @attributes={"sort_order"=>0, "title"=>nil}>

We now have access to the recipes of our chapter via the chapter's recipes array.

>> c.recipes
=> [#<Recipe:0x8e131d0 @new_record=true, @attributes={"see_also"=>nil,
"discussion"=>nil, "sort_order"=>0, "title"=>nil, "chapter_id"=>nil,
"solution"=>nil, "problem"=>nil}>]

Remember that you can always view all the methods available for an object by calling methods.

>> c.methods

To play with our recipes to tags relationship, we create a Tag object and add it to our Recipe object:

>> t = Tag.new
=> #<Tag:0x8e09e3c @new_record=true, @attributes={"name"=>nil}>

>> r.tags << t
=> [#<Tag:0x8e09e3c @new_record=true, @attributes={"name"=>nil}>]

Finally, inspection confirms that the Tag was added to our Recipe object:

>> r.tags
=> [#<Tag:0x8e09e3c @new_record=true, @attributes={"name"=>nil}>]

See Also