2009-07-14 143 views
31

查看更新的評論。Rails:多對多多態關係

我一直在努力得到一個清晰而直接的答案,我希望這一次我能得到它! :D 我肯定還有很多事情要跟Rails學習,但是我理解我面臨的問題,並且非常感謝您提供更多幫助。

  • 我有一個名爲「任務」的模型。
  • 我有一個叫做「Target」的抽象模型。
  • 我想將Target的子類的多個實例關聯到Task。
  • 我沒有使用單個表繼承。
  • 我想查詢多態關係以返回Target的子類的混合結果集。
  • 我想查詢Target的子類的單個實例以獲取它們與之關係的任務。

因此,我認爲任務和子目標之間的多態關係是多態的。 更詳細,我就可以做這樣的事情在控制檯(當然其他地方):

task = Task.find(1) 
task.targets 
[...array of all the subclasses of Target here...] 

但是!假設模型「商店」,「軟件」,「辦公室」,「車輛」,這是「目標」存在的所有子類,這將是很好也穿越在其他方向的關係:

store = Store.find(1) 
store.tasks 
[...array of all the Tasks this Store is related to...] 
software = Software.find(18) 
software.tasks 
[...array of all the Tasks this Software is related to...] 

的通過多態關係暗示的數據庫表似乎是能夠做這個穿越的,但我看到一些反覆出現的主題,試圖找到這對我打敗多態性關係的精神答案:

  • 使用我的例子是,人們似乎想要定義商店,軟件,辦公室,任務中的車輛,我們現在可以告訴它不是多態關係,因爲它只返回一種型號
  • 與最後一點類似,人們仍然希望以任何形式或形式定義「任務」中的「商店」,「軟件」,「辦公室」和「車輛」。這裏重要的一點是,這種關係對於子類化來說是盲目的。我的多形態最初只會與目標交互,而不是它們各自的子類類型。在Task中定義每個子類再次開始消除多態關係的目的。
  • 我看到連接表的模型可能是有序的,這似乎對我來說是正確的,只是它增加了一些複雜性,我認爲Rails願意取消。我懇求這方面缺乏經驗。

這似乎是軌道功能或集體社區知識的一個小漏洞。所以希望stackoverflow可以記錄我的搜索答案!

感謝大家的幫助!

+0

在你的六個要點,其中五人是平凡的實現如果你刪除了第六,「我不使用單表繼承」。 關於您在STI下面的觀點,由於額外的列確實會影響您,請考慮使用委派將額外的數據和行爲推送到其他模型。 – austinfromboston 2009-07-15 02:51:15

+2

向外推動是造成這種情況的原因。性傳播感染雖然不是一種選擇。我希望這是因爲,是的...每個人都是它的粉絲。但我希望存儲的數據具有內聚性,並且會有相當多的不同類型的目標。 我仍然覺得有點驚人的是,沒有辦法像這樣拉取混合的集合。我的設計看起來很合理。 – 2009-07-15 03:30:06

+0

我已經能夠通過使用has_many_polymorphs來完成我想要的大部分功能。 剩下的一個限制是我仍然堅持定義每個父類型(任務)中的多態類型。 歡迎使用其他解決方案,但我不確定解決方案會在我們面前出現,直到新版本的rails或has_many_polymorphs更新! – 2009-07-15 19:19:39

回答

0

這可能不是一個特別有用的答案,但簡單地說,我不認爲有一個簡單或自動化的方法來做到這一點。至少,不像簡單的一對多或多對多的關聯那麼簡單。

我認爲爲連接表創建ActiveRecord模型是解決問題的正確方法。一個正常的has_and_belongs_to_many關係假設兩個指定表之間的連接,而在你的情況下,它聽起來像你想加入tasksstores,softwares,officesvehicles(順便說一句,有沒有原因不使用STI在這裏呢?看起來它會通過限制你擁有的表的數量來降低複雜性)。所以在你的情況下,連接表也需要知道涉及的Target子類的名稱。像

create_table :targets_tasks do |t| 
    t.integer :target_id 
    t.string :target_type 
    t.integer :task_id 
end 

然後,在你Task類,你Target小類和TargetsTask類的東西,你可以使用關鍵字:throughActiveRecord::Associations::ClassMethods rdoc pages如記錄建立has_many協會。

但仍然,這隻會讓你的一部分,因爲:through將不知道使用target_type字段作爲Target子類名稱。爲此,您可能可以編寫一些自定義選擇/查找程序SQL片段,也記錄在ActiveRecord::Associations::ClassMethods中。

希望這會讓你朝着正確的方向前進。如果您找到完整的解決方案,我很樂意看到它!

0

我同意其他人我會去使用混合了STI和代表團的解決方案,這將更容易實施。

問題的核心是在哪裏存儲Target的所有子類的記錄。 ActiveRecord通過STI模型選擇數據庫。

您可以將它們存儲在目標中的類變量中,並使用繼承的回調向其中添加新變量。然後,您可以從該數組的內容中動態生成所需的代碼,並利用method_missing。

0

你有追逐的蠻力方法:

class Task 
    has_many :stores 
    has_many :softwares 
    has_many :offices 
    has_many :vehicles 

    def targets 
    stores + softwares + offices + vehicles 
    end 
    ... 

它可能不是那麼優雅,但說實話,它不是那麼詳細,並沒有什麼本質上低效的有關代碼。

53

您可以結合多態性與has_many :through獲得靈活的映射:

class Assignment < ActiveRecord::Base 
    belongs_to :task 
    belongs_to :target, :polymorphic => true 
end 

class Task < ActiveRecord::Base 
    has_many :targets, :through => :assignment 
end 

class Store < ActiveRecord::Base 
    has_many :tasks, :through => :assignment, :as => :target 
end 

class Vehicle < ActiveRecord::Base 
    has_many :tasks, :through => :assignment, :as => :target 
end 

...等等。

1

你提到的has_many_polymorphs解決方案並沒有那麼糟糕。

class Task < ActiveRecord::Base 
    has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle] 
end 

似乎做你想做的一切。

它提供了以下方法:

到任務:

t = Task.first 
t.targets # Mixed collection of all targets associated with task t 
t.stores # Collection of stores associated with task t 
t.softwares # same but for software 
t.offices # same but for office 
t.vehicles # same but for vehicles 

到軟件商店,辦公室,汽車:

s = Software.first # works for any of the subtargets. 
s.tasks    # lists tasks associated with s 

如果我正確遵循了意見,只有剩下的問題是,您不希望每次創建新類型的Subtarget時都必須修改app/models/task.rb。 Rails方式似乎要求您修改兩個文件以創建雙向關聯。 has_many_polymorphs只需要您更改任務文件。看起來像是一場勝利。或者至少如果你不需要編輯新的Model文件的話。

有幾種方法可以解決這個問題,但他們似乎太多的工作來避免每隔一段時間更改一個文件。但是如果你對修改任務自己添加到多態關係的死定了,這是我的建議:

保留一個子目標列表,我將建議在lib/subtargets格式化每行一個條目是基本上是table_name.underscore。 (大寫字母有下劃線前綴,然後一切由小寫)

store 
software 
office 
vehicle 

創建配置/初始化/ subtargets.rb以及與此填充:

SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym) 

接下來你會想或者創建一個自定義生成器或一個新的rake任務。生成新的子目標並將模型名稱添加到上面定義的子目標列表文件中。你可能最終會做一些簡單的骨骼來完成更改並將參數傳遞給標準生成器。

對不起,我真的不覺得自己現在走你通過正確的,但這裏有someresources

最後用SubtargetList

class Task < ActiveRecord::Base 
    has_many_polymorphs :targets, :from => SubtargetList 
end 

在替換列表中has_many_polymorphs聲明從這點你可以添加一個新的子目標與

$ script/generate subtarget_model home 

這將自動更新您的多形ic列表一旦你重新加載你的控制檯或重新啓動生產服務器。

正如我所說的,自動更新子目錄列表的工作量很大。然而,如果你走這條路線,你可以調整自定義生成器,以確保生成它時,子目標模型的所有必需部分都在那裏。

1

使用STI:

class Task < ActiveRecord::Base 
end 

class StoreTask < Task 
    belongs_to :store, :foreign_key => "target_id" 
end 

class VehicleTask < Task 
    belongs_to :vehicle, :foreign_key => "target_id" 
end 

class Store < ActiveRecord::Base 
    has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id" 
end 

class Vehicle < ActiveRecord::Base 
    has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id" 
end 

在你DATABSE你將需要: Task type:stringTask target_id:integer

的好處是,現在你通過模型可以是特定的每個任務類型有。

又見STI and polymorphic model together

乾杯!

10

雖然SFEley提出的答案是偉大的,有沒有一些缺陷:

  • 從目標(商店/車)任務的檢索工作,但不會向後。這基本上是因爲你不能遍歷a:通過關聯到一個多態數據類型,因爲SQL無法知道它在哪個表中。
  • 每個具有:through關聯的模型都需要與中間表
  • 的:通過分配關聯應該是複數的
  • 的:作爲聲明不會與一起工作:通過,你需要與中間工作臺

考慮到這一點所需的直接關聯先指定它,我最簡單的解決方案是:

個​​

參考文獻: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through