3

我目前的项目允许Doctors有许多Patients与我能做到以下几点:Rails的多态关联:getter和setter方法

dennis = Patient.create 
frank = Doctor.create 
dennis.update(doctor: frank) 
dennis.doctor #=> frank 
frank.patients #=> [dennis, ...] 

但现在我想补充一类医院,也可以有很多病人。我不想再为Hostpital级别添加has_many,因为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方法(以及我的数据库中的关联)通过的has_many关系提供。

任何帮助表示赞赏!

+0

为了什么缓存这是值得的,我强烈建议避免多态关联。在数据库中执行它们非常困难(数据完整性),因为您失去了拥有外键约束的能力。如果您为每个可能的拥有者拥有外键,那么您可以使用外键并检查约束。这在我的工作项目中非常有效地完成(也许有一天我们会使其开源)。我们也有你所描述的getter和setter方法('owner'和'owner =')。 –

回答

0

警告:我不强烈推荐这个,因为它很体面,而且依赖围绕几个Rails行为,这很容易出错,从来没有很好的面向未来的行为。例如。如果您使用它们,您还必须覆盖build_doctor,create_doctorcreate_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上添加一个范围,允许您定义关联和预加载等的正确关联。但是,如果您打算使用实例方法patient.doctor(尝试不用,您会看到会发生什么),但您需要明确地将joins添加到范围。

我还有使用方法覆盖在doctorhospital实例方法,因为如果你调用patient.hospital,它会返回一个Hospital找到一个具有Patient以及其中Hospitalid匹配当前patientowner_id ,即使patient.owner_type == "Doctor"。 Rails在Hospital模型上生成一个查询,而不考虑我们从中调用它的实例。这就是开始感觉不舒服并且不被推荐的地方,但是覆盖只需切换self.owner_type就可以解决这个问题,但是现在我们可以使用super,它可以让您获得Rails的缓存行为以及其他任何装饰内置于Rails的关联getter方法中。同样,setter方法doctor=没有设置owner_type,所以我再次覆盖它,类似于您现有的方法,但与super。请注意,如果提供了错误的参数(例如,您将Hospital传递给doctor=,这将引发ActiveRecord::AssociationTypeMismatch),那么我会首先呼叫超级,在这种情况下,它不会更改self.owner_type。然后,而不是简单地说self.owner_type = "Doctor",我选择了self.owner = doctor,这确实设置owner_type,并且还了解填补了:owner协会

我写了一个小更详细了解这个解决方案here