8

मैं एक सदस्यता कार्यक्रम हमारे चर्च के लिए रेल में (आईएम अभी भी रेल को farily नया) तो मूल रूप से लिख रहा हूँ ..has_many: एकाधिक has_one संबंधों के माध्यम से?

और मैं इस मॉडल की जरूरत है ..

contact 
has_one :father, :class_name => "Contact" 
has_one :mother, :class_name => "Contact" 
has_many :children, :class_name => "Contact" 
has_many :siblings, :through <Mother and Father>, :source => :children 

एक वस्तुओं "भाई बहन" पिता और मां दोनों के सभी बच्चों को मानचित्र को स्वयं शामिल करने की आवश्यकता नहीं है ..

क्या यह संभव है?

धन्यवाद

डैनियल

उत्तर

9

यह सवाल इतना आसान दिखाई जटिल जवाब हो सकता है कि कैसे हास्यास्पद है। इस मामले में, रिफ्लेक्सिव पेरेंट/चाइल्ड रिलेशनशिप को लागू करना काफी सरल है, लेकिन पिता/माता और भाई बहन संबंध जोड़ना कुछ मोड़ बनाता है।

शुरू करने के लिए, हम अभिभावक-बाल संबंधों को पकड़ने के लिए टेबल बनाते हैं। रिश्ता दो विदेशी कुंजी, संपर्क में दोनों की ओर इशारा करते हैं:

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 

रिश्ता मॉडल हम माता-पिता का कहना है में वापस संपर्क करने के लिए:

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 

और संपर्क में उलटा संघों को परिभाषित:

class Contact < ActiveRecord::Base 
    has_many :relationships, :dependent => :destroy 
    has_one :father, :through => :relationships 
    has_one :mother, :through => :relationships 
end 

अब एक रिश्ता बनाया जा सकता है:

@bart = Contact.create(:name=>"Bart") 
@homer = Contact.create(:name=>"Homer") 
@bart.relationships.build(:relation_type=>"father",:father=>@homer) 
@bart.save! 
@bart.father.should == @homer 
,

@bart.build_father(@homer) 
@bart.save! 

एक संपर्क के बच्चों ढूंढने के लिए:

यह इतना महान नहीं है, क्या हम वास्तव में चाहते हैं एक कॉल में संबंध बनाने के लिए है:

class Contact < ActiveRecord::Base 
    def build_father(father) 
    relationships.build(:father=>father,:relation_type=>'father') 
    end 
end 

तो हम क्या कर सकते हैं संपर्क करने के लिए एक दायरा जोड़ें (सुविधा के लिए) एक उदाहरण विधि:

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

भाई बहन मुश्किल भाग हैं। हम Contact.children विधि का लाभ उठा सकें और परिणामों में हेरफेर:

def siblings 
    ((self.father ? self.father.children : []) + 
    (self.mother ? self.mother.children : []) 
    ).uniq - [self] 
end 

इस गैर इष्टतम है, क्योंकि father.children और mother.children ओवरलैप होगा (इस प्रकार uniq के लिए की जरूरत है), और अधिक कुशलता से किया जा सकता है आवश्यक एसक्यूएल (एक अभ्यास के रूप में छोड़ दिया :)) को ध्यान में रखते हुए, लेकिन ध्यान में रखते हुए कि self.father.children और self.mother.children आधे भाई बहनों (एक ही पिता, अलग मां) के मामले में ओवरलैप नहीं होगा, और एक संपर्क में पिता नहीं हो सकते या एक मां

# 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 
+0

आप महोदय एक रेल और स्टैक ओवरफ्लो राक्षस हैं (आपके उत्तरों में चश्मा !?) कमाल !! अगर मैं तुम्हें चुंबन दूंगा! धन्यवाद :) –

+0

आह .. हालांकि एक विचार, क्या यह संपर्क मॉडल में पिता_आईडी और mother_id जोड़ने के लिए काम नहीं करेगा और फिर has_many जोड़ें: बच्चों,: class_name => "संपर्क", finder_sql => 'चयन * संपर्कों से जहां संपर्क .father_id = # {id} या contact.mother_id = # {id} "और has_many: भाई बहनें: class_name =>" संपर्क करें ", finder_sql => 'चयन * संपर्कों से जहां contact.father_id = # {father_id} या संपर्क .mother_id = # {mother_id} '? बस एक विचार: पी –

+0

आप इसे एक तालिका में कर सकते हैं, लेकिन इससे आपको उन रिश्तों तक सीमित कर दिया जाएगा जिन्हें विदेशी कुंजी के माध्यम से निर्दिष्ट किया जा सकता है। एक अलग तालिका के साथ आपके पास निर्दिष्ट करने के लिए लचीलापन है अन्य रिश्ते के प्रकार, जैसे 'गॉडफादर' या 'चाचा'। – zetetic

2

मैं पूरी तरह से सहमत zetetic:

यहाँ पूरा मॉडल और कुछ ऐनक हैं। प्रश्न उत्तर के बाद कहीं अधिक सरल दिखता है और इसके बारे में हम बहुत कम कर सकते हैं। हालांकि मैं अपना 20 सी जोड़ दूंगा।
टेबल्स:

create_table :contacts do |t| 
     t.string :name 
     t.string :gender 
    end 
    create_table :relations, :id => false do |t| 
     t.integer :parent_id 
     t.integer :child_id 
    end 

तालिका संबंधों इसी मॉडल नहीं है।

class Contact < ActiveRecord::Base 
    has_and_belongs_to_many :parents, 
    :class_name => 'Contact', 
    :join_table => 'relations', 
    :foreign_key => 'child_id', 
    :association_foreign_key => 'parent_id' 

    has_and_belongs_to_many :children, 
    :class_name => 'Contact', 
    :join_table => 'relations', 
    :foreign_key => 'parent_id', 
    :association_foreign_key => 'child_id' 

    def siblings 
    result = self.parents.reduce [] {|children, p| children.concat p.children} 
    result.uniq.reject {|c| c == self} 
    end 

    def father 
    parents.where(:gender => 'm').first 
    end 

    def mother 
    parents.where(:gender => 'f').first 
    end 
end 

अब हमारे पास नियमित रेल assosiations है। तो हम

alice.parents << bob 
alice.save 

bob.chidren << cindy 
bob.save 

alice.parents.create(Contact.create(:name => 'Teresa', :gender => 'f') 

और इस तरह की सभी चीज़ें कर सकते हैं।

0
has_and_belongs_to_many :parents, 
    :class_name => 'Contact', 
    :join_table => 'relations', 
    :foreign_key => 'child_id', 
    :association_foreign_key => 'parent_id', 
    :delete_sql = 'DELETE FROM relations WHERE child_id = #{id}' 

    has_and_belongs_to_many :children, 
    :class_name => 'Contact', 
    :join_table => 'relations', 
    :foreign_key => 'parent_id', 
    :association_foreign_key => 'child_id', 
    :delete_sql = 'DELETE FROM relations WHERE parent_id = #{id}' 

मैंने इस उदाहरण का उपयोग किया लेकिन संबंधों को साफ करने के लिए delete_sql को जोड़ना पड़ा। पहले मैंने स्ट्रिंग के चारों ओर डबल कोट्स का इस्तेमाल किया लेकिन पाया कि त्रुटियों का कारण बन गया। सिंगल कोट्स पर स्विचिंग काम किया।

संबंधित मुद्दे