Contents 

Ruby on Rails:
Table of Contents
Preface
Zero to Sixty: Introducing Rails
1.1. Rails Strengths
1.2. Putting Rails into Action
1.3. Organization
1.4. The Web Server
1.5. Creating a Controller
1.6. Building a View
1.7. Tying the Controller to the View
1.8. Under the Hood
1.9. What's Next?
Active Record Basics
2.1. Active Record Basics
2.2. Introducing Photo Share
2.3. Schema Migrations
2.4. Basic Active Record Classes
2.5. Attributes
2.6. Complex Classes
2.7. Behavior
2.8. Moving Forward
Active Record Relationships
3.1. belongs_to
3.2. has_many
3.3. has_one
3.4. What You Haven't Seen
3.5. Looking Ahead
Scaffolding
4.1. Using the Scaffold Method
4.2. Replacing Scaffolding
4.3. Generating Scaffolding Code
4.4. Moving Forward
Extending Views
5.1. The Big Picture
5.2. Seeing Real Photos
5.3. View Templates
5.4. Setting the Default Root
5.5. Stylesheets
5.6. Hierarchical Categories
5.7. Styling the Slideshows
Ajax
6.1. How Rails Implements Ajax
6.2. Playing a Slideshow
6.3. Using Drag-and-Drop to Reorder Slides
6.4. Drag and Drop Everything (Almost Everything)
6.5. Filtering by Category
Testing
7.1. Background
7.2. Ruby's Test::Unit
7.3. Testing in Rails
7.4. Wrapping Up
Installing Rails
1.1. Windows
2.1. OS X
3.1. Linux
Quick Reference
5.1. General
5.2. Testing
5.3. RJS (Ruby JavaScript)
5.4. Active Record
5.5. Controllers
5.6. Views
5.7. Ajax
5.8. Configuring Your Application
About the Authors
Colophon
Index
A
B
C
D
E
F
G
H
I
J
L
M
N
O
P
R
S
T
U
V
W
X
Y
Z

Ruby on Rails manual

Prev Page Next Page
Previous Page
Next Page

2.6. Complex Classes

For Photo Share, we've built an object model in which one table relates to one class. Sometimes, you'll want to map more sophisticated object models to a database table. The two most common scenarios for doing so are inheritance and composition. Let's look at how you'd handle each mapping with Active Record. These examples are not part of our Photo Share application, but the problems are common enough that we will show them to you here.

2.6.1. Inheritance

Active Record supports inheritance relationships using a strategy called single-table inheritance, shown in "#rubyrails-chp-2-fig-3">Figure 2-3. With this kind of inheritance mapping, all descendents of a common class use the same table. For example, a photographer is a person with a camera. With single-table inheritance, all columns for both Person and Photographer go into the same table. Consider this table:

CREATE TABLE people (
   id INT AUTO_INCREMENT NOT NULL,
   type VARCHAR(20),
   name VARCHAR(20),
   email VARCHAR(30),
   camera VARCHAR(20),
   PRIMARY KEY (id)
);

Figure 2-3. Rails supports single-table inheritance between an entity (Person) and subclass (Photographer)

A query against Person will return people and photographers. Active Record doesn't need to build any special support to handle a query against a superclass. Subclasses are more difficult. In order to allow a query returning only Photographers, Active Record must have some way to determine the type of an object for an individual row. Active Record uses the type field for this purpose.

Now, we need classes, which are trivial:

class Photographer < Person
end

class Person < ActiveRecord::Base
end

We declare Photographer as a subclass of Person. Active Record will manage the type attribute and everything else. You'll be able to access the camera property from Photographer. We don't need these classes for Photo Share, so we'll delete them.

You've probably noticed that Active Record's implementation of inheritance is not true inheritance because all items in an inheritance tree have the same attributes. In our example, all people have cameras even if they are not photographers. In practice, that limitation is not severe. A parent can ignore attributes introduced by subclasses. This strategy is a compromise. You get slightly better performance (because fewer tables means fewer joins) and simplicity at the cost of muddying the abstraction a little.

Normally, only Active Record needs to set the type attribute. Be careful when you need to manage type yourself. You can't say person.type because type is a class method on Object. If you need to see the value of the type field, use person[:type] instead.


2.6.2. Composition

If you want to extend a Person class with Address, you can use a has_one relationship, or you can use composition. Composition works well when you want to use a pervasive type like address or currency across many Active Record models. You'll use composed_of for this type of relationship, as shown in "#rubyrails-chp-2-fig-4">Figure 2-4.

Figure 2-4. Composed of maps many objects onto one table

Let's look at a Person that is composed_of an Address. In a composition relationship, there's a main class (Person) and one or more component classes (Address). Each component class explicitly references one or more database columns. Start with a table that's defined like this:

CREATE TABLE people (
   id INT AUTO_INCREMENT NOT NULL,
   type VARCHAR(20),
   name VARCHAR(20),
   email VARCHAR(30),
   street_address VARCHAR(30),
   city VARCHAR(30),
   state VARCHAR(20),
   zip INTEGER(5),
   camera VARCHAR(20),
   PRIMARY KEY (id)
);

You then map the table onto two different classes. First, create a Person class with a composed_of relationship:

class Person < ActiveRecord::Base
  composed_of :address, :class_name => "Address",
              :mapping => [[:street_address, :street_address],
                           [:city, :city],
                           [:state, :State],
                           [:zip, :zip]]
end

If the first parameter for composed_of and the name of the component class are the same, Active Record can infer the name of the component class. Otherwise, you can override it with a :class_name modifier. For example, you can use composed_of person_address class_name => "Address". Next, create an Address class:

class Address
  def initialize(street_address, city, state, zip)
    @street_address = street_address
    @city = city
    @state = state
    @zip = zip
  end

  attr_reader :street_address, :city, :state, :zip
end

Address is the component class. For each database column that the component represents, the component class must have an attribute and a parameter in the initialize method:

>> elvis=Person.new
>> elvis.name="Elvis Presley"
>> elvis.email= "elvis@graceland.com"
>> address=Address.new("3734 Elvis Presley Blvd", "Memphis", "Tennessee", 38118)
>> elvis.address=address
>> elvis.save
>> puts elvis.address.street_address
3734 Elvis Presley Blvd

Though street_address, city, state, and zip are columns on the people table, you don't use those attributes on any Person object directly. Instead, access these attributes through the address attribute on Person. Table 2-3 shows the attributes added by a composed_of relationship.

Table 2-3. Metaprogramming for composed_of :class

Attributes

Description

<class>

The component class (person.address)

<class>_<attribute>

Attributes for the component class (person.address_zip)



Previous Page
Next Page