Ruby on rails Rails多态关联:getter和setter方法
我当前的项目允许Ruby on rails Rails多态关联:getter和setter方法,ruby-on-rails,ruby,getter-setter,polymorphic-associations,Ruby On Rails,Ruby,Getter Setter,Polymorphic Associations,我当前的项目允许医生有许多患者,我可以利用这些患者做以下工作: dennis = Patient.create frank = Doctor.create dennis.update(doctor: frank) dennis.doctor #=> frank frank.patients #=> [dennis, ...] 但是现在我想增加一个一级医院,也可以有很多病人。我不想只在Hostpital类中添加另一个has\u many,因为Patient的“所有权”可能会再次发生变
医生
有许多患者
,我可以利用这些患者做以下工作:
dennis = Patient.create
frank = Doctor.create
dennis.update(doctor: frank)
dennis.doctor #=> frank
frank.patients #=> [dennis, ...]
但是现在我想增加一个一级医院,也可以有很多病人。我不想只在Hostpital
类中添加另一个has\u many
,因为Patient
的“所有权”可能会再次发生变化,最终我的患者模型将充斥着外键字段,其中只有一个字段是空的。多态关联似乎正是我所寻找的:患者可以被医生或医院“拥有”:
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.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
class Patient
这似乎工作得很好,但是这种关联仍然没有反映在我的数据库中。我有一些自定义SQL查询,这些查询引用了患者.doctor
。这将抛出具有多态关联的Mysql2::Error:Unknown列“patients.doctor”
我有没有更好的方法来实现这一点?此时,返回我的所有代码和sql查询以将.doctor
更改为.owner
将非常耗时
TL;DR试图从has\u many切换到多态关联,但我希望保留has\u many关系提供的方便的getter和setter方法(以及数据库中的关联)
感谢您的帮助 警告:我不强烈推荐这种方法,因为它相当粗糙,并且依赖于绕开几个Rails行为,这很容易出错,而且永远不会成为未来的证据。例如,您还必须覆盖
构建医生
,创建医生
,以及创建医生代码>方法,如果您使用它们,这是我在下面没有做的。我也同意@doctor\u of_ogz的观点,如果你能避免多态关联,你应该这样做。目前我还不清楚最好的解决方案是什么,但是你可能需要更多地考虑多个外键列,因为它可能值得额外的NILS。< /P>
但如果你不想走这条路,你需要的应该是:
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
class Patient{加入(:患者)。其中(患者:{所有者类型:“医生”}),外键:“所有者id”
属于:医院,->{加入(:患者)。其中(患者:{所有者类型:“医院”}),外键:“所有者id”
def医生(*args)
所有者类型==“医生”?超级:零
结束
def医生=(医生)
超级的
self.owner=医生
结束
def医院(*args)
所有者类型==“医院”?超级:零
结束
def医院=(医院)
超级的
self.owner=医院
结束
结束
说明:
关联在资源类型上添加一个作用域,以允许您为联接和预加载等正确定义关联。但是,如果您打算使用实例方法patient.doctor
,则需要显式地将连接添加到作用域中(在不使用的情况下尝试,您将看到发生了什么)
我仍然在doctor
和hospital
实例方法上使用方法覆盖,因为如果调用patient.hospital
,它将返回一个医院
,它会找到一个有患者
,并且医院
的id
与当前患者
所有者id
匹配的医院,即使Patient.owner\u type==“Doctor”
。Rails正在生成一个关于医院模型的查询,而不考虑我们从中调用它的实例。这正是它开始感觉不好的地方,不推荐使用它,但是重写只需像您已经做的那样打开self.owner\u type
,就可以解决这个问题,但现在我们可以使用super
,它可以让您了解Rails的缓存行为以及Rails的关联getter方法中内置的任何其他虚饰。类似地,setter方法doctor=
没有设置owner\u type
,因此我再次以类似于现有方法的方式对其进行了重写,但使用了super
。请注意,如果提供了错误的参数,我将首先调用super(例如,您将医院
传递给医生=
,这将引发ActiveRecord::AssociationTypeMismatch
),在这种情况下,它不会更改self.owner\u type
。然后,我没有简单地说self.owner\u type=“Doctor”
,而是选择了self.owner=Doctor
,它确实设置了owner\u type
,并且知道如何填充:owner
关联的缓存
关于这个解决方案,我写了更多的细节,我强烈建议避免多态关联。它们很难在数据库中实施(数据完整性),因为您失去了具有外键约束的能力。如果每个可能的所有者都有一个外键,则可以使用外键并检查约束。这在我的工作项目中是非常有效的(也许有一天我们会使它开源)。我们还有您描述的getter和setter方法(owner
和owner=
)。
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