has_many:通过多个has_one关系? [英] has_many :through multiple has_one relationships?
问题描述
我正在为我们在铁轨上的教堂编写指导计划(仍然是铁轨上的新手).
I'm writing a mentorship program for our church in rails (im still farily new to rails)..
我需要对此建模.
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
因此,基本上,对象兄弟姐妹"需要映射父亲和母亲的所有孩子,而不包括对象本身..
So basically an objects "siblings" needs to map all the children from both the father and mother not including the object itself..
这可能吗?
谢谢
丹尼尔
推荐答案
有趣的是,看起来简单的问题如何具有复杂的答案.在这种情况下,实现自反的父母/子女关系非常简单,但是添加父亲/母亲和兄弟姐妹关系会带来一些麻烦.
It's funny how questions that appear simple can have complex answers. In this case, implementing the reflexive parent/child relationship is fairly simple, but adding the father/mother and siblings relationships creates a few twists.
首先,我们创建表来保存父子关系.关系有两个外键,都指向联系人:
To start, we create tables to hold the parent-child relationships. Relationship has two foreign keys, both pointing at 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 Relationship model we point the father and mother back to 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
并定义Contact中的逆关联:
and define the inverse associations in 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
这不是很好,我们真正想要的是在单个调用中建立关系:
This is not so great, what we really want is to build the relationship in a single call:
class Contact < ActiveRecord::Base
def build_father(father)
relationships.build(:father=>father,:relation_type=>'father')
end
end
所以我们可以做:
@bart.build_father(@homer)
@bart.save!
要查找联系人的子级,请将范围添加到联系人,并(为方便起见)添加实例方法:
To find the children of a Contact, add a scope to Contact and (for convenience) an instance method:
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方法并处理结果:
Siblings are the tricky part. We can leverage the Contact.children method and manipulate the results:
def siblings
((self.father ? self.father.children : []) +
(self.mother ? self.mother.children : [])
).uniq - [self]
end
这不是最优的,因为father.children和mother.children将重叠(因此需要uniq
),并且可以通过制定必要的SQL来更有效地完成(留作练习:)),但请记住,self.father.children
和self.mother.children
在同父异母的兄弟姐妹(父亲相同,母亲不同)的情况下不会重叠,并且联系人可能没有父亲或母亲.
This is non-optimal, since father.children and mother.children will overlap (thus the need for uniq
), and could be done more efficiently by working out the necessary SQL (left as an exercise :)), but keeping in mind that self.father.children
and self.mother.children
won't overlap in the case of half-siblings (same father, different mother), and a Contact might not have a father or a mother.
以下是完整的型号和一些规格:
Here are the complete models and some specs:
# 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
这篇关于has_many:通过多个has_one关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!