Reciprocal Relationships using ActiveRecord
Reciprocal relationships are present in many problem domains, and it's important to be able to model them using your ORM. In this case we'll look at a reciprocal relationship involving a Company and a Person.
A Person can be an employee of a single Company, or the CEO of a single Company. If a Person is a CEO of a Company, that Company is known as the Person's empire. If a Person is employed by a Company, that Company is known as the Person's employer.
If a Person is employed by a Company, that Person is known to the Company as an employee. If a Person is a CEO of a company, that Person is known as the Company's ceo.
The UML for this particular relationship looks like this:

It is possible to express this type of relationship using ActiveRecord. Note that the foreign_key calls are to the Red Hill on Rails Core plugin, which is being used to enforce referential integrity in the database.
class Company < ActiveRecord::Base
has_many :employees, :class_name => 'Person', :foreign_key => 'employer_company_id'
belongs_to :ceo, :class_name => 'Person', :foreign_key => 'ceo_person_id'
end
class Person < ActiveRecord::Base
belongs_to :employer, :class_name => 'Company', :foreign_key => 'employer_company_id'
has_one :empire, :class_name => 'Company', :foreign_key => 'ceo_person_id'
end
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :employer_company_id, :integer
t.timestamps
t.foreign_key :employer_company_id, :companies, :id
end
end
def self.down
drop_table :people
end
end
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.column :ceo_person_id, :integer
t.timestamps
t.foreign_key :ceo_person_id, :people, :id
end
end
def self.down
drop_table :companies
end
end
To prove this works, we can fire up a console and create an imaginary company, with employees and a CEO:
>> megacorp = Company.create => #<Company id: 1, ceo_person_id: nil, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:07"> >> bob = megacorp.employees.create => #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15"> >> joe = megacorp.employees.create => #<Person id: 2, employer_company_id: 1, created_at: "2009-11-15 00:34:22", updated_at: "2009-11-15 00:34:22"> >> megacorp.ceo = bob => #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15"> >> megacorp.save! => true >> megacorp.employees => [#<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15">, #<Person id: 2, employer_company_id: 1, created_at: "2009-11-15 00:34:22", updated_at: "2009-11-15 00:34:22">] >> megacorp.ceo => #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15"> >> bob.employer => #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33"> >> bob.empire => #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33"> >> joe.employer => #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33"> >> joe.empire => nil
As you can see it's possible to build up just the kind of relationships described. In practice you would probably want to extend the above example with some constraints and observers - so for example, making a Person the CEO of a company would automatically add that Person to the employees collection.

