我当前的项目允许Doctors
有许多Patients
我可以执行以下操作:
dennis = Patient.create
frank = Doctor.create
dennis.update(doctor: frank)
dennis.doctor #=> frank
frank.patients #=> [dennis, ...]
但现在我想增加一个班级医院,也可以有很多病人。我不想在has_many
课程中添加另一个Hostpital
,因为Patient
'所有权'可能会再次改变,最终我的病人模型会被外键字段所困扰,除了其中一个外,其他所有字段都是空的。多态关联似乎正是我所寻找的:Patient
可以拥有'通过Doctor
或Hospital
:
class Patient < ActiveRecord::Base
belongs_to :owner, polymorphic: true
end
class Doctor < ActiveRecord::Base
has_many :patients, as: :owner
end
class Hospital < ActiveRecord::Base
has_many :patients, as: :owner
end
这允许我们执行以下操作:
dennis = Patient.create
frank = Doctor.create
dennis.update(owner: frank)
dennis.owner #=> frank
frank.patients #=> [dennis, ...]
但是,我们无法致电dennis.doctor
来返回frank
。我了解所有者可能并不总是Doctor
类的实例,但我当前的大部分代码都使用#doctor
和#doctor=
方法。所以我想我可以定义它们:
class Patient < ActiveRecord::Base
belongs_to :owner, polymorphic: true
def patient=(patient)
self.owner_id = patient.id
self.owner_type = "Patient"
end
def patient
return nil unless self.owner
self.owner.class == Patient ? self.owner : nil
end
end
这似乎工作正常,但这种关联仍然没有反映在我的数据库中。我有一些引用patients.doctor
的自定义SQL查询。现在抛出Mysql2::Error: Unknown column 'patients.doctor'
多态关联。
我有更好的方法来实现这个吗?此时,返回我的所有代码和SQL查询以将.doctor
更改为.owner
将非常耗时。
TL; DR 尝试从has_many切换到多态关联,但我想保留方便的getter和setter方法(以及我的数据库中的关联)有很多关系。
感谢任何帮助!
答案 0 :(得分:0)
build_doctor
,create_doctor
和create_doctor!
方法,我在下面没有这样做。我同意@doctor_of_ogz,如果你可以避免多态关联,你应该。我不清楚最好的解决方案是什么,但你可能想要更多地考虑多个外键列,因为它可能值得额外的nils。
但如果你不想走那条路,你想要的应该是:
class Patient < ActiveRecord::Base
belongs_to :owner, polymorphic: true
belongs_to :doctor, ->{joins(:patients).where(patients: {owner_type: "Doctor"})}, foreign_key: "owner_id"
belongs_to :hospital, ->{joins(:patients).where(patients: {owner_type: "Hospital"})}, foreign_key: "owner_id"
def doctor(*args)
owner_type == "Doctor" ? super : nil
end
def doctor=(doctor)
super
self.owner = doctor
end
def hospital(*args)
owner_type == "Hospital" ? super : nil
end
def hospital=(hospital)
super
self.owner = hospital
end
end
说明:
关联在resource_type上添加范围,以允许您为连接和预加载等正确定义关联。但是如果你打算使用实例方法joins
,你需要显式地将patient.doctor
添加到范围中(不用试试,你会看到会发生什么)。
我仍然在doctor
和hospital
实例方法上使用方法覆盖,因为如果您调用patient.hospital
,它将返回Hospital
它会找到一个具有Patient
以及Hospital
的{{1}}与当前id
的{{1}}匹配的位置,即使patient
。 Rails正在owner_id
模型上生成查询而不考虑我们从中调用它的实例。这是它开始感到hacky并且不推荐的地方,但是覆盖只是通过像你已经完成的那样开启patient.owner_type == "Doctor"
来解决这个问题,但现在我们可以使用Hospital
来获取你Rails的缓存行为和Rails的关联getter方法中内置的任何其他褶边。同样,setter方法self.owner_type
没有设置super
,所以我再次覆盖它与现有方法类似,但使用doctor=
。请注意,如果提供了错误的参数,我会调用super first (例如,您将owner_type
传递给super
,这将引发Hospital
),在哪种情况下它不会改变doctor=
。然后,我选择ActiveRecord::AssociationTypeMismatch
而不是简单地说self.owner_type
, 设置self.owner_type = "Doctor"
,并且知道填充self.owner = doctor
的缓存关联
我写了一些关于这个解决方案的更多细节here