Chapter 4. Active Record Relationships

You’ve seen how Active Record treats a model backed by a single table. You wrote very little code, and got impressive behavior for free. But Photo Share will need many different models working together. In this chapter, you’ll learn how to deal with relationships. Active Record takes advantage of the Ruby language and naming conventions to simplify the way you work with related tables through related models.

In Chapter 3, you got a taste of Active Record’s language for relationships and validations. Adding the macro validates_presence_of :filename record to the class added all of the code that your class needed to support validation. You’ll deal with relationships the same way. Rather than adding a bunch of methods and attributes to your class explicitly to manage a foreign key relationship, you’ll describe the relationship using a little bit of syntax called a macro and let Active Record do the rest. Rails will add everything you need to manage each relationship based on a few short lines of code.

You’ll specify a relationship, also called an association, in three parts: the relationship macro, the association or target, and an optional hash of additional parameters. For example, this code:

class Slideshow < ActiveRecord::Base
  has_many :slides, :order => 'position'

specifies that a single Slideshow has_many :slides, and they are ordered by the position attribute. The macro is has_many, the association is :slides, and the hash of options is {:order => 'position'}.

Let’s add relationships to the existing Photo, Slide, Slideshow, and Category classes. If you haven’t already done so, and you want to start coding your way through the book with us, copy your code from http://www.oreilly.com/catalog/9780596522001, using the file for Chapter 3. Run rake photos:reset to run your migrations and reset your test data. Now you’re ready to follow along.

belongs_to

The most common database relationship is many-to-one. Figure 4-1 shows how Active Record maps the “many” side of such a relationship. In Photo Share, we want users to be able to build slideshows. A slideshow contains an ordered list of pictures. We can’t simply use pictures in a slideshow because a picture has no way of keeping its position in a slideshow, so we’ll introduce a Slide class. We’ll then need a many-to-one relationship between slides and slideshows because a slideshow consists of many slides, but each slide (a photo combined with a position) can belong to only one slideshow. A slide also belongs to a photo. We’ll implement both relationships with belongs_to.

belongs_to :association relationship between entity (slide) and association (slideshow)

Figure 4-1. belongs_to :association relationship between entity (slide) and association (slideshow)

You’ve already generated a model and migration for Slide, and another for Slideshow. You can open up db/schema.rb to verify that your Photos schema has a table called slides and another called photos. Notice that the slide has a slideshow_id and a photo_id. Any model that uses belongs_to must be backed by a table that has the supporting foreign key. Next, modify the Slide model in app/models/slide.rb, adding the two relationships:

class Slide < ActiveRecord::Base
  belongs_to :slideshow
  belongs_to :photo
end

That’s it. You have two working belongs_to relationships. Verify them briefly in the console. (Remember, you can start the console with ruby script/console, or simply use reload! to reload it whenever your models change.)

>> slide = Slide.new
=> #<Slide id: nil, position: nil, photo_id: nil, slideshow_id: nil, 
created_at: nil, updated_at: nil>
>> slide.create_slideshow
=> #<Slideshow id: 2, name: nil, created_at: "2008-05-10 17:45:46", 
updated_at: "2008-05-10 17:45:46">
>> slide.photo = Photo.find(2)
=> #<Photo id: 2, filename: "lighthouse.jpg", thumbnail: "lighthouse_t.jpg", 
description: "My ride to work", created_at: "2008-05-21 02:07:29", 
updated_at: "2008-05-21 02:07:29">
>> slide.photo.filename
=> "lighthouse.jpg"
>> slide.slideshow.id
=> 2

You can see the belongs_to relationship at work. You have at least two new attributes: slideshow and photo. You also have the create_slideshow and create_photo methods. These methods are only the tip of the iceberg. Table 4-1 shows all of the methods and attributes introduced by the belongs_to macro.

Table 4-1. Metaprogramming for belongs_to and has_one

Added Feature

Description

Methods

<association>.nil?

Test the association for a nil value: slide.photo.nil?

build_<association>

Build an object of the associated type, but do not save it yet: slide.build_photo(:filename => "cat.jpg").

In this example, slide.photo is initialized to a new unsaved photo with the specified attributes.

create_<association>

Create and save an object of the associated type, initialized to the root object. It takes a hash map of attributes for the new object as a parameter: slideshow.create_slide({...}).

Attributes

<association>

An attribute of the type of the associated object: belongs_to :photo on Slide allows slide.photo and slide.photo = nil

As you can see, just learning the macros isn’t enough. To learn about using Active Record to the fullest, you need to understand all of the methods and attributes that belongs_to creates for you. You’ll find the association attribute and the constructors—all forms of build and create methods—particularly useful. We’ll show you tables such as Table 4-1 for each macro we cover.

In the meantime, let’s take another look at the new models in the Rails console:

>> slide = Slide.find 1
=> #<Slide id: 1, position: 1, photo_id: 1, slideshow_id: 1, 
created_at: "2008-05-10 17:45:34", 
updated_at: "2008-05-10 17:45:34">
>> slide.photo.filename
=> "train.jpg"
>> slide.slideshow.name
=> "Interesting Pictures"

belongs_to is only the “many” end of a many-to-one relationship. Let’s look at the “one” side.

Get Rails: Up and Running, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.