[英]has_many :through multiple has_one relationships?
我正在為我們的鐵路教堂寫一個導師計划(我仍然很喜歡鐵路)。
我需要對此進行建模..
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
所以基本上一個對象“兄弟姐妹”需要映射父親和母親的所有孩子,不包括對象本身。
這可能嗎?
謝謝
丹尼爾
有趣的是,看似簡單的問題可以得到復雜的答案。 在這種情況下,實現反身父/子關系相當簡單,但添加父/母和兄弟關系會產生一些曲折。
首先,我們創建表來保存父子關系。 關系有兩個外鍵,都指向聯系人:
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
並在Contact中定義反向關聯:
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
這不是很好,我們真正想要的是在一次通話中建立關系:
class Contact < ActiveRecord::Base
def build_father(father)
relationships.build(:father=>father,:relation_type=>'father')
end
end
所以我們可以這樣做:
@bart.build_father(@homer)
@bart.save!
要查找聯系人的子項,請向Contact和(為方便起見)實例方法添加范圍:
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
這是非最優的,因為父親和孩子將會重疊(因此需要uniq
),並且可以通過制定必要的SQL(左側作為練習:)來更有效地完成,但要記住, 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
我完全同意zetetic。 這個問題看起來比答案簡單得多,我們幾乎無能為力。 我會添加我的20c。
表:
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
現在我們定期進行Rails分析。 所以我們可以
alice.parents << bob
alice.save
bob.chidren << cindy
bob.save
alice.parents.create(Contact.create(:name => 'Teresa', :gender => 'f')
以及所有類似的東西。
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來清理關系記錄。 起初我在字符串周圍使用雙引號但發現導致錯誤。 切換到單引號有效。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.