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