Section 2.6. Complex Classes


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 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 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 )




Ruby on Rails[c] Up and Running
Ruby on Rails[c] Up and Running
ISBN: B003D3OGCY
EAN: N/A
Year: 2006
Pages: 94

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net