2012-09-23 38 views
0

我有一个Family类,其中包括mother_idfather_id。从家庭模式的角度来看,了解哪个父母是母亲,哪个父亲是重要的,但母亲和父亲拥有所有相同的属性(即数据库列)。所以理想情况下,我希望我的模型文件看起来像这样:has_one带有多个可能的外键列

class Resident < ActiveRecord::Base 
    has_one :family, :dependent => :nullify, :foreign_key => :father_id 
    has_one :family, :dependent => :nullify, :foreign_key => :mother_id 
    attr_accessible :email, :cell, :first_name, :last_name 
end 

class Family < ActiveRecord::Base 
    belongs_to :father, :class_name => 'Resident', :foreign_key => 'father_id' 
    belongs_to :mother, :class_name => 'Resident', :foreign_key => 'mother_id' 
    attr_accessible :address, :city, :state, :number_of_children 
end 

这是行不通的。 my_family.mothermy_family.father工作,所以Rails似乎很高兴与双belongs_to。但是,my_dad.family == nil,表明第二个has_one重写第一个。这是合理的,否则,如果resident_id出现在mother_id和father_id列中,会发生什么情况? (虽然我计划添加模型级别验证以确保永不发生,但has_one不会与验证方法进行交谈。)此外,my_dad.family = Family.new是什么意思? ActiveRecord如何选择是否将my_dad.id插入Family.mother_idFamily.father_id

this Stackoverflow question,我得到了主意,使用不同的名称,即has_one线更改为:

has_one :wife_and_kids, :class_name => 'Family', :dependent => :nullify, :foreign_key => :father_id 
has_one :husband_and_kids, :class_name => 'Family', :dependent => :nullify, :foreign_key => :mother_id 

我的问题是:

1)是否有更好的方式来做到这一点?一个不同的数据库模式,也许?

2)是数据库级别的验证可以补充模型级验证,以确保my_dad.id不能同时在mother_idfather_id栏显示?

3)你能想到比husband_and_kids/wife_and_kids更好的名字吗? (诚​​然不是一个编程的问题...)

编辑: 它发生在我加入一个家庭的getter:

def family 
    @family ||= self.wife_and_kids || self.husband_and_kids 
end 
after_save :reset_family 
def reset_family 
    @family = nil 
end 

这使得语法更清洁(因为我真的不是一个风扇的[husband|wife]_and_kids),没有造成任何歧义,因为没有设置。

回答

0

你面临的主要问题是,你有一个“条件”外键,这意味着用于解析外键:家庭居民的取决于居民是否是男性或女性(母亲或父亲) 。在我看来,处理这个问题的最好方法是使用STI(单表继承)来区分这两种情况。

class Resident < ActiveRecord::Base 
    attr_accessible :email, :cell, :first_name, :last_name 
end 

class Mother < Resident 
    has_one :family, :dependent => :nullify, :foreign_key => :mother_id 
end 

class Father < Resident 
    has_one :family, :dependent => :nullify, :foreign_key => :father_id 
end 

你仍然可以使用住户表,但你需要迁移:string类型的类型字段和存储值“母亲”或“父亲”视情况而定。另外,将这些类定义中的每一个放置在models /中它自己的文件中。

编辑:我觉得这也解决了你的第二个和第三个问题提出的各种问题。

EDIT2:

鉴于目前的模式,你需要在你的families表中创建一个check constraint。首先,活动记录没有直接的支持,所以你将不得不执行原始的sql来添加约束。从理论上讲,每次在“家庭”的“mother_id”栏中添加或更改一个值时,支票必须与“居民”表互相参照,确定“居民”的“类型”栏是“母亲。”这将(理论上)添加此约束的SQL是

ALTER TABLE families 
ADD CONSTRAINT must_be_mother CHECK ((SELECT type FROM residents WHERE residents.id = families.mother_id) = 'Mother') 

的问题是,这CHECK包含一个子查询,而据我所知,在检查子选择被许多数据库不允许的。 (具体细节请见question)。

如果你真的想在这里实现数据库级验证,你可能需要通过将“居民”分成“母亲”和“父亲”来改变模式。

+0

我不明白为什么它会回答我的第二个问题。如果居民有类型'男性',但我尝试将其ID插入到Family.mother_id中,数据库将不知道它是无效的。 –

+0

我想它间接地回答第二个问题,因为它使得明确的验证是不必要的。创建新记录时,如果限制自己仅实例化Mother对象或Father对象,则外键的含义不会有任何歧义。 Mother对象在family表中有一个外键:mother_id,并且这种方式无法与以下内容混淆:father_id – cdesrosiers

+0

但是该验证在模型中,即在应用程序中。问题#2是关于数据库级的验证 - 如果将来有另一个应用程序需要连接到数据库会怎么样 - 我该如何强制未来应用程序的开发人员将无法将父亲放入mother_id列的错误? 当你运行rails生成model family mother_id:integer:uniq'时,rails会在模式文件中放入: 'add_index“family,[”mother_id“],:name =>”index_families_on_mother_id“,:unique => true' 我想要类似的验证,说没有mother_id列可以输入father_id列 –