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