E 'strano come le domande che appaiono semplici possono avere risposte complesse. In questo caso, l'implementazione della relazione genitore/figlio riflessiva è abbastanza semplice, ma l'aggiunta delle relazioni padre/madre e fratelli crea alcuni colpi di scena.
Per iniziare, creiamo tabelle per mantenere le relazioni padre-figlio. Relazione ha due chiavi esterne, sia che punta a contatto:
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
Nel modello di rapporto si segnala il padre e la madre torna a contatto:
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
e definire le associazioni inverse in contatto:
class Contact < ActiveRecord::Base
has_many :relationships, :dependent => :destroy
has_one :father, :through => :relationships
has_one :mother, :through => :relationships
end
Ora un rapporto può essere creato:
@bart = Contact.create(:name=>"Bart")
@homer = Contact.create(:name=>"Homer")
@bart.relationships.build(:relation_type=>"father",:father=>@homer)
@bart.save!
@bart.father.should == @homer
Questo non è così grande, ciò che realmente vogliamo è quello di costruire il rapporto in una singola chiamata:
class Contact < ActiveRecord::Base
def build_father(father)
relationships.build(:father=>father,:relation_type=>'father')
end
end
modo che possiamo fare:
@bart.build_father(@homer)
@bart.save!
di trovare i figli di un contatto, aggiungi uno scope a Contact e (per praticità) un metodo di istanza:
scope :children, lambda { |contact| joins(:relationships).\
where(:relationships => { :relation_type => ['father','mother']}) }
def children
self.class.children(self)
end
Contact.children(@homer) # => [Contact name: "Bart")]
@homer.children # => [Contact name: "Bart")]
I fratelli sono la parte difficile. Possiamo sfruttare il metodo Contact.children e manipolare i risultati:
def siblings
((self.father ? self.father.children : []) +
(self.mother ? self.mother.children : [])
).uniq - [self]
end
Questo non è ottimale, in quanto father.children e mother.children si sovrapporranno (così la necessità di uniq
), e potrebbe essere fatto in modo più efficiente elaborando l'SQL necessario (lasciato come esercizio :)), ma tenendo presente che self.father.children
e self.mother.children
non si sovrappongono nel caso di fratellastri (stesso padre, madre diversa) e un contatto potrebbe non avere un padre o una madre.
Qui ci sono i modelli completi e alcune specifiche:
# app/models/contact.rb
class Contact < ActiveRecord::Base
has_many :relationships, :dependent => :destroy
has_one :father, :through => :relationships
has_one :mother, :through => :relationships
scope :children, lambda { |contact| joins(:relationships).\
where(:relationships => { :relation_type => ['father','mother']}) }
def build_father(father)
# TODO figure out how to get ActiveRecord to create this method for us
# TODO failing that, figure out how to build father without passing in relation_type
relationships.build(:father=>father,:relation_type=>'father')
end
def build_mother(mother)
relationships.build(:mother=>mother,:relation_type=>'mother')
end
def children
self.class.children(self)
end
def siblings
((self.father ? self.father.children : []) +
(self.mother ? self.mother.children : [])
).uniq - [self]
end
end
# app/models/relationship.rb
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
# spec/models/contact.rb
require 'spec_helper'
describe Contact do
before(:each) do
@bart = Contact.create(:name=>"Bart")
@homer = Contact.create(:name=>"Homer")
@marge = Contact.create(:name=>"Marge")
@lisa = Contact.create(:name=>"Lisa")
end
it "has a father" do
@bart.relationships.build(:relation_type=>"father",:father=>@homer)
@bart.save!
@bart.father.should == @homer
@bart.mother.should be_nil
end
it "can build_father" do
@bart.build_father(@homer)
@bart.save!
@bart.father.should == @homer
end
it "has a mother" do
@bart.relationships.build(:relation_type=>"mother",:father=>@marge)
@bart.save!
@bart.mother.should == @marge
@bart.father.should be_nil
end
it "can build_mother" do
@bart.build_mother(@marge)
@bart.save!
@bart.mother.should == @marge
end
it "has children" do
@bart.build_father(@homer)
@bart.build_mother(@marge)
@bart.save!
Contact.children(@homer).should include(@bart)
Contact.children(@marge).should include(@bart)
@homer.children.should include(@bart)
@marge.children.should include(@bart)
end
it "has siblings" do
@bart.build_father(@homer)
@bart.build_mother(@marge)
@bart.save!
@lisa.build_father(@homer)
@lisa.build_mother(@marge)
@lisa.save!
@bart.siblings.should == [@lisa]
@lisa.siblings.should == [@bart]
@bart.siblings.should_not include(@bart)
@lisa.siblings.should_not include(@lisa)
end
it "doesn't choke on nil father/mother" do
@bart.siblings.should be_empty
end
end
Tu signore sei un mostro e un mostro stackoverflow (Specifiche nelle tue risposte !?) fantastico !! se potessi, ti bacerei! Grazie :) –
Ah .. una idea però, non funzionerebbe aggiungere padre_id e mother_id al modello di contatto e quindi aggiungere has_many: children,: class_name => "Contact",: finder_sql => 'SELECT * FROM contacts WHERE contacts .father_id = # {id} OR contacts.mother_id = # {id} "e has_many: fratelli,: class_name =>" Contatto ",: finder_sql => 'SELECT * FROM contatti WHERE contacts.father_id = # {father_id} O contatti .mother_id = # {mother_id} '? Solo un'idea: P –
Puoi farlo in una tabella, ma questo ti limiterà alle relazioni che possono essere specificate attraverso le chiavi esterne. Con una tabella separata hai la flessibilità di specificare altri tipi di relazioni, come 'padrino' o 'zio'. – zetetic