- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols
JOINING TO MULTIPLE TABLES 343
Sharing Association Extensions
You’ll sometimes want to apply the same set of extensions to a number of associations. You can do this by putting your extension methods in a Ruby module and passing that module to the association declaration with the :extend parameter.
has_many :articles, :extend => RatingFinder
You can extend an association with multiple modules by passing :extend an array.
has_many :articles, :extend => [ RatingFinder, DateRangeFinder ]
18.4Joining to Multiple Tables
Relational databases allow us to set up joins between tables: a row in our orders table is associated with a number of rows in the line items table, for example. The relationship is statically defined. However, sometimes that isn’t convenient.
You could get around this with some clever coding, but fortunately you don’t have to do so. Rails provides two mechanisms for mapping a relational model into a more complex object-oriented one: single-table inheritance and polymorphic associations. Let’s look at each in turn.
Single-Table Inheritance
When we program with objects and classes, we sometimes use inheritance to express the relationship between abstractions. Our application might deal with people in various roles: customers, employees, managers, and so on. All roles will have some properties in common and other properties that are role specific. We might model this by saying that class Employee and class Customer are both subclasses of class Person and that Manager is in turn a subclass of Employee. The subclasses inherit the properties and responsibilities of their parent class.2
In the relational database world, we don’t have the concept of inheritance: relationships are expressed primarily in terms of associations. But single-table inheritance, described by Martin Fowler in Patterns of Enterprise Application Architecture [Fow03], lets us map all the classes in the inheritance hierarchy into a single database table. This table contains a column for each of the attributes of all the classes in the hierarchy. It additionally includes a column, by convention called type, that identifies which particular class of object is
2. Of course, inheritance is a much-abused construct in programming. Before going down this road, ask yourself whether you truly do have an is-a relationship. For example, an employee might also be a customer, which is hard to model given a static inheritance tree. Consider alternatives (such as tagging or role-based taxonomies) in these cases.
Report erratum
JOINING TO MULTIPLE TABLES 344
represented by any particular row. This is illustrated in Figure 18.2, on the following page.
Using single-table inheritance in Active Record is straightforward. Define the inheritance hierarchy you need in your model classes, and ensure that the table corresponding to the base class of the hierarchy contains a column for each of the attributes of all the classes in that hierarchy. The table must additionally include a type column, used to discriminate the class of the corresponding model objects.
When defining the table, remember that the attributes of subclasses will be present only in the table rows corresponding to those subclasses; an employee doesn’t have a balance attribute, for example. As a result, you must define the table to allow null values for any column that doesn’t appear in all subclasses. The following is the migration that creates the table illustrated in Figure 18.2, on the next page.
Download e1/ar/sti.rb
create_table :people, :force => true do |t| t.column :type, :string
#common attributes t.column :name, :string t.column :email, :string
#attributes for type=Customer
t.column :balance, :decimal, :precision => 10, :scale => 2
# attributes for type=Employee
t.column |
:reports_to, |
:integer |
t.column |
:dept, |
:integer |
#attributes for type=Manager
#- none -
end
We can define our hierarchy of model objects.
Download e1/ar/sti.rb
class Person < ActiveRecord::Base end
class Customer < Person end
class Employee < Person
belongs_to :boss, :class_name => "Employee", :foreign_key => :reports_to end
class Manager < Employee end
Report erratum
JOINING TO MULTIPLE TABLES 345
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Person |
|
|
|
|
|
class Person < ActiveRecord::Base |
|||||
|
|
|
name |
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
# .. |
|
|
|
|||
|
|
|
|
|
|
|
|
end |
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
class Customer < Person |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
# ... |
|
|
|
||||
Customer |
|
|
Employee |
|
|
|
||||||||
balance |
|
|
|
reports_to |
|
end |
|
|
|
|||||
|
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
dept |
|
class Employee < Person |
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
Manager |
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
class Manager < Employee |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end |
|
|
|
|
people |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
id |
type |
|
name |
balance reports_to dept |
|
||||||||
|
1 |
|
Customer |
|
John Doe |
john@doe.com |
78.29 |
|
|
|||||
|
2 |
|
Manager |
|
Wilma Flint |
wilma@here.com |
|
23 |
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
Customer |
|
Bert Public |
b@public.net |
12.45 |
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
Employee |
|
Barney Rub |
barney@here.com |
2 |
23 |
|
|||||
|
5 |
|
Employee |
|
Betty Rub |
betty@here.com |
2 |
23 |
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
Customer |
|
Ira Buyer |
ira9652@aol.com |
-66.76 |
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
Employee |
|
Dino Dogg |
dino@dig.prg |
2 |
23 |
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Figure 18.2: Single-Table Inheritance: A Hierarchy of Four Classes Mapped into One Table
Report erratum
JOINING TO MULTIPLE TABLES 346
Then we create a couple of rows and read them back.
Download e1/ar/sti.rb |
|
Customer.create(:name => 'John Doe', |
:email => "john@doe.com" , |
:balance => 78.29) |
|
wilma = Manager.create(:name => 'Wilma Flint', :email => "wilma@here.com" , :dept => 23)
Customer.create(:name => 'Bert Public', :email => "b@public.net" , :balance => 12.45)
barney = Employee.new(:name => 'Barney Rub', :email => "barney@here.com", :dept => 23)
barney.boss = wilma barney.save!
manager = Person.find_by_name("Wilma Flint")
puts manager.class |
#=> Manager |
||
puts |
manager.email |
#=> |
wilma@here.com |
puts |
manager.dept |
#=> |
23 |
customer = Person.find_by_name("Bert Public")
puts customer.class |
#=> Customer |
||
puts |
customer.email |
#=> |
b@public.net |
puts |
customer.balance |
#=> |
12.45 |
Notice how we ask the base class, Person, to find a row, but the class of the object returned is Manager in one instance and Customer in the next; Active Record determined the type by examining the type column of the row and created the appropriate object.
Notice also a small trick we used in the Employee class. We used belongs_to to create an attribute named boss. This attribute uses the reports_to column, which points back into the people table. That’s what lets us say barney.boss = wilma.
There’s one fairly obvious constraint when using single-table inheritance. Two subclasses can’t have attributes with the same name but with different types, because the two attributes would map to the same column in the underlying schema.
There’s also a less obvious constraint. The attribute type is also the name of a built-in Ruby method, so accessing it directly to set or change the type of a row may result in strange Ruby messages. Instead, access it implicitly by creating objects of the appropriate class, or access it via the model object’s indexing interface, using something such as
person[:type] = 'Manager'
Report erratum
JOINING TO MULTIPLE TABLES 347
Joe Asks. . .
What If I Want Straight Inheritance?
Single-Table Inheritance is clever—it turns on automatically whenever you subclass an Active Record class. But what it you want real inheritance–you want to define some behavior to be shared among a set of Active Record classes by defining an abstract base class and a set of subclasses?
The answer is to define a class method called abstract_class? in your abstract base class. The method should return true. This has two effects. First, Active Record will never try to find a database table corresponding to this abstract class. Second, all subclasses of this class will be treated as independent Active Record classes—each will map to its own database table.
Of course, a better way of doing this is probably to use a Ruby module containing the shared functionality, and mix this module into Active Record classes that need that behavior.
David Says. . .
Won’t Subclasses Share All the Attributes in STI?
Yes, but it’s not as big of a problem as you think it would be. As long as the subclasses are more similar than not, you can safely ignore the reports_to attribute when dealing with a customer. You simply just don’t use that attribute.
We’re trading the purity of the customer model for speed (selecting just from the people table is much faster than fetching from a join of people and customers tables) and for ease of implementation.
This works in a lot of cases, but not all. It doesn’t work too well for abstract relationships with very little overlap between the subclasses. For example, a content management system could declare a Content base class and have subclasses such as Article, Image, Page, and so forth. But these subclasses are likely to be wildly different, which will lead to an overly large base table because it has to encompass all the attributes from all the subclasses. In this case, it would be better to use polymorphic associations, which we describe next.
Report erratum
JOINING TO MULTIPLE TABLES 348
Polymorphic Associations
One major downside of STI is that there’s a single underlying table that contains all the attributes for all the subclasses in our inheritance tree. We can overcome this using Rails’ second form of heterogeneous aggregation, polymorphic associations.
Polymorphic associations rely on the fact that a foreign key column is simply an integer. Although there’s a convention that a foreign key named user_id references the id column in the users table, there’s no law that enforces this.3
In computer science, polymorphism is a mechanism that lets you abstract the essence of something’s interface regardless of its underlying implementation. The addition method, for example, is polymorphic, because it works with integers, floats, and even strings.
In Rails, a polymorphic association is an association that links to objects of different types. The assumption is that these objects all share some common characteristics but that they’ll have different representations.
To make this concrete, let’s look at a simple asset management system. We index our assets in a simple catalog. Each catalog entry contains a name, the acquisition date, and a reference to the actual resource: an article, an image, a sound, and so on. Each of the different resource types corresponds to a different database table and to a different Active Record model, but they are all assets, and they are all cataloged.
Let’s start with the three tables that contain the three types of resource.
Download e1/ar/polymorphic.rb
create_table :articles, :force => true do |t| t.column :content, :text
end
create_table :sounds, :force => true do |t| t.column :content, :binary
end
create_table :images, :force => true do |t| t.column :content, :binary
end
Now, let’s think about the three models that wrap these tables. We’d like to be able to write something like
# THIS DOESN'T WORK
class Article < ActiveRecord::Base has_one :catalog_entry
end
3. If you specify that your database should enforce foreign key constraints, polymorphic associations won’t work.
Report erratum
JOINING TO MULTIPLE TABLES 349
class Sound < ActiveRecord::Base has_one :catalog_entry
end
class Image < ActiveRecord::Base has_one :catalog_entry
end
Unfortunately, this can’t work. When we say has_one :catalog_entry in a model, it means that the catalog_entries table has a foreign key reference back to our table. But here we have three tables each claiming to have_one catalog entry: we can’t possibly arrange to have the foreign key in the catalog entry point back to all three tables...
...unless we use polymorphic associations. The trick is to use two columns in our catalog entry for the foreign key. One column holds the id of the target row, and the second column tells Active Record which model that key is in. If we call the foreign key for our catalog entries resource, we’ll need to create two columns, resource_id and resource_type. Here’s the migration that creates the full catalog entry.
Download e1/ar/polymorphic.rb
create_table :catalog_entries, :force => true do |t| t.column :name, :string
t.column :acquired_at, :datetime t.column :resource_id, :integer t.column :resource_type, :string
end
Now we can create the Active Record model for a catalog entry. We have to tell it that we’re creating a polymorphic association through our resource_id and resource_type columns.
Download e1/ar/polymorphic.rb
class CatalogEntry < ActiveRecord::Base belongs_to :resource, :polymorphic => true
end
Now that we have the plumbing in place, we can define the final versions of the Active Record models for our three asset types.
Download e1/ar/polymorphic.rb
class Article < ActiveRecord::Base
has_one :catalog_entry, :as => :resource end
class Sound < ActiveRecord::Base
has_one :catalog_entry, :as => :resource end
Report erratum
JOINING TO MULTIPLE TABLES 350
class Image < ActiveRecord::Base
has_one :catalog_entry, :as => :resource end
The key here is the :as options to has_one. It specifies that the linkage between a catalog entry and the assets is polymorphic, using the resource attribute in the catalog entry. Let’s try it.
Download e1/ar/polymorphic.rb
a = Article.new(:content => "This is my new article")
c = CatalogEntry.new(:name => 'Article One', :acquired_at => Time.now) c.resource = a
c.save!
Let’s see what happened inside the database. There’s nothing special about the article.
mysql> |
select |
* |
from articles; |
||
+---- |
|
+------------------------ |
|
|
+ |
| id | |
content |
|
| |
||
+---- |
|
+------------------------ |
|
|
+ |
| 1 |
| |
This is my new article | |
|||
+---- |
|
+------------------------ |
|
|
+ |
1 |
row in set (0.00 sec) |
The catalog entry has the foreign key reference to the article and also records the type of Active Record object it refers to (an Article).
mysql> |
select * from |
catalog_entries; |
+ |
+ |
+ |
|||
+---- |
|
+------------- |
|
+--------------------- |
|
|||
| id | |
name |
| |
acquired_at |
| resource_id |
| resource_type |
| |
||
+---- |
|
+------------- |
|
+--------------------- |
|
+------------- |
+--------------- |
+ |
| 1 |
| |
Article One |
| |
2006-07-18 16:48:29 | 1 |
| Article |
| |
||
+---- |
|
+------------- |
|
+--------------------- |
|
+------------- |
+--------------- |
+ |
1 |
row in set (0.00 |
sec) |
|
|
|
We can access data from both sides of the relationship.
Download e1/ar/polymorphic.rb |
|
|
|
article = Article.find(1) |
|
|
|
p article.catalog_entry.name |
#=> "Article One" |
|
|
cat = CatalogEntry.find(1) |
|
|
|
resource = cat.resource |
|
|
|
p resource |
#=> #<Article:0x640d80 |
@attributes={"id"=>"1", |
|
|
# |
"content"=>"This |
is my new article"}> |
The clever part here is the line resource = cat.resource. We’re asking the catalog entry for its resource, and it returns an Article object. It correctly determined the Active Record class, read from the appropriate database table (articles), and returned the right class of object.
Let’s make it more interesting. Let’s clear out our database and then add assets of all three types.
Report erratum
JOINING TO MULTIPLE TABLES 351
Download e1/ar/polymorphic.rb
c = CatalogEntry.new(:name => 'Article One', :acquired_at => Time.now) c.resource = Article.new(:content => "This is my new article")
c.save!
c = CatalogEntry.new(:name => 'Image One', :acquired_at => Time.now) c.resource = Image.new(:content => "some binary data")
c.save!
c = CatalogEntry.new(:name => 'Sound One', :acquired_at => Time.now) c.resource = Sound.new(:content => "more binary data")
c.save!
Now our database looks more interesting.
mysql> |
select * |
from articles; |
|
|
|
|
+---- |
+------------------------ |
|
+ |
|
|
|
| id | |
content |
| |
|
|
|
|
+---- |
+------------------------ |
|
+ |
|
|
|
| 1 |
| |
This is my new article | |
|
|
|
|
+---- |
+------------------------ |
|
+ |
|
|
|
mysql> |
select * |
from images; |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
| id | |
content |
| |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
| 1 |
| |
some binary data | |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
mysql> |
select * |
from sounds; |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
| id | |
content |
| |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
| 1 |
| |
more binary data | |
|
|
|
|
+---- |
+------------------ |
|
+ |
|
|
|
mysql> |
select * |
from catalog_entries; |
+ |
+ |
+ |
|
+---- |
+------------- |
|
+--------------------- |
|||
| id | |
name |
| acquired_at |
| resource_id |
| resource_type | |
||
+---- |
+------------- |
|
+--------------------- |
+------------- |
+--------------- |
+ |
| 1 |
| |
Article One | 2006-07-18 17:02:05 | 1 |
| Article |
| |
||
| 2 |
| |
Image One |
| 2006-07-18 17:02:05 | 1 |
| Image |
| |
|
| 3 |
| |
Sound One |
| 2006-07-18 17:02:05 | 1 |
| Sound |
| |
|
+---- |
+------------- |
|
+--------------------- |
+------------- |
+--------------- |
+ |
Notice how all three foreign keys in the catalog have an id of 1—they are distinguished by their type column.
Now we can retrieve all three assets by iterating over the catalog.
Download e1/ar/polymorphic.rb
CatalogEntry.find(:all).each do |c| puts "#{c.name}: #{c.resource.class}"
end
This produces
Article One: Article
Image One: Image
Sound One: Sound
Report erratum
|
|
JOINING TO MULTIPLE TABLES 352 |
|
|
|
|
|
|
has_one :other |
belongs_to :other |
|
|
|
|
|
other(reload=false) |
|
|
|
|
|
|
|
other= |
|
|
|
|
|
|
|
create_other(...) |
|
|
|
|
|
|
|
build_other(...) |
|
|
|
|
|
|
|
replace |
|
|
|
|
|
|
|
updated? |
|
|
|
|
|
|
|
|
has_many :others |
habtm :others |
|
|
|
|
|
others |
|
|
|
|
|
|
|
others= |
|
|
|
|
|
|
|
other_ids= |
|
|
|
|
|
|
|
others.<< |
|
|
|
|
|
|
|
others.build(...) |
|
|
|
|
|
|
|
others.clear(...) |
|
|
|
|
|
|
|
others.concat(...) |
|
|
|
|
|
|
|
others.count |
|
|
|
|
|
|
|
others.create(...) |
|
|
|
|
|
|
|
others.delete(...) |
|
|
|
|
|
|
|
others.delete_all |
|
|
|
|
|
|
|
others.destroy_all |
|
|
|
|
|
|
|
others.empty? |
|
|
|
|
|
|
|
others.find(...) |
|
|
|
|
|
|
|
others.length |
|
|
|
|
|
|
|
others.push(...) |
|
|
|
others.replace(...) |
|
|
|
others.reset |
|
|
|
others.size |
|
|
|
others.sum(...) |
|
|
|
others.to_ary |
|
|
|
others.uniq |
|
|
|
push_with_attributes(...) |
|
[deprecated] |
|
|
|
|
|
Figure 18.3: Methods Created by Relationship Declarations
Report erratum
SELF-REFERENTIAL JOINS 353
18.5Self-referential Joins
It’s possible for a row in a table to reference back to another row in that same table. For example, every employee in a company might have both a manager and a mentor, both of whom are also employees. You could model this in Rails using the following Employee class.
Download e1/ar/self_association.rb
class Employee < ActiveRecord::Base belongs_to :manager,
:class_name => "Employee", :foreign_key => "manager_id"
belongs_to :mentor,
:class_name => "Employee", :foreign_key => "mentor_id"
has_many :mentored_employees, :class_name => "Employee", :foreign_key => "mentor_id"
has_many :managed_employees, :class_name => "Employee", :foreign_key => "manager_id"
end
Let’s load up some data. Clem and Dawn each have a manager and a mentor.
Download e1/ar/self_association.rb
Employee.delete_all
adam = Employee.create(:id => 1, :name => "Adam") beth = Employee.create(:id => 2, :name => "Beth")
clem = Employee.new(:name => "Clem") clem.manager = adam
clem.mentor = beth clem.save!
dawn = Employee.new(:name => "Dawn") dawn.manager = adam
dawn.mentor = clem dawn.save!
Then we can traverse the relationships, answering questions such as “who is the mentor of X?” and “which employees does Y manage?”
Download e1/ar/self_association.rb
p adam.managed_employees.map {|e| e.name} |
# => [ "Clem", "Dawn" ] |
|||
p |
adam.mentored_employees |
# |
=> |
[] |
p |
dawn.mentor.name |
# |
=> |
"Clem" |
You might also want to look at the various acts as relationships.
Report erratum