It's funny how simple questions can have complex answers. In this case, the implementation of the parent / child reflexive relationship is quite simple, but adding the relationship between father / mother and siblings creates a few twists.
To get started, we create tables to hold parent-child relationships. Relations have two foreign keys, both indicate contact:
create_table :contacts do |t| t.string :name end create_table :relationships do |t| t.integer :contact_id t.integer :relation_id t.string :relation_type end
In the Relationships model, we point the father and mother back to the contact:
class Relationship < ActiveRecord::Base belongs_to :contact belongs_to :father, :foreign_key => :relation_id, :class_name => "Contact", :conditions => { :relationships => { :relation_type => 'father'}} belongs_to :mother, :foreign_key => :relation_id, :class_name => "Contact", :conditions => { :relationships => { :relation_type => 'mother'}} end
and define inverse associations in Contact:
class Contact < ActiveRecord::Base has_many :relationships, :dependent => :destroy has_one :father, :through => :relationships has_one :mother, :through => :relationships end
Now you can create the relationship:
@bart = Contact.create(:name=>"Bart") @homer = Contact.create(:name=>"Homer") @bart.relationships.build(:relation_type=>"father",:father=>@homer) @bart.save! @bart.father.should == @homer
It's not so great that we really want to build relationships in one call:
class Contact < ActiveRecord::Base def build_father(father) relationships.build(:father=>father,:relation_type=>'father') end end
so we can do:
@bart.build_father(@homer) @bart.save!
To find contact children, add a contact area and (for convenience) instance method:
scope :children, lambda { |contact| joins(:relationships).\ where(:relationships => { :relation_type => ['father','mother']}) } def children self.class.children(self) end Contact.children(@homer)
Siblings are the hard part. We can use the Contact.children method and manage the results:
def siblings ((self.father ? self.father.children : []) + (self.mother ? self.mother.children : []) ).uniq - [self] end
This is not optimal, since fathers and children will overlap (thus uniq ) and can be done more efficiently by generating the necessary SQL (left as an exercise :)), but given that self.father.children and self.mother.children will not overlap in the case of half-point brothers (same father, different mother), and the contact may not have a father or mother.
Here are the complete models and some specifications: