Rails多态多对多关联

时间:2009-06-04 22:50:26

标签: ruby-on-rails

我正在尝试设置一个通用的相关对象网络。假设我有4个模型。

  • 电影
  • 标签
  • 分类

我希望能够做到:

book = Book.find(1)
book.relations << Tag.find(2)
book.relations << Category.find(3)
book.relations #=> [Tag#2, Category#3]

movie = Movie.find(4)
movie.relations << book
movie.relations << Tag.find(5)
movie.relations #=> [Book#1, Tag#5]

基本上我希望能够获取任何模型类(或我允许的模型类)的任何2个对象,并声明它们是相关的。

显然我不想创建一大堆连接表。这似乎并不是很多通过关联,而不是多态的关联。

这是Rails可以通过它的关联声明支持的东西还是我应该在这里滚动自己的逻辑?

4 个答案:

答案 0 :(得分:7)

自早期以来,对多态性的支持已大大改善。您应该能够在Rails 2.3中通过对所有模型使用单个连接表来实现这一点 - 一个Relation模型。

class Relation
  belongs_to :owner, :polymorphic => true
  belongs_to :child_item, :polymorphic => true
end

class Book
  has_many :pwned_relations, :as => :owner, :class_name => 'Relation'
  has_many :pwning_relations, :as => :child_item, :class_name => 'Relation'

  # and so on for each type of relation
  has_many :pwned_movies, :through => :pwned_relations, 
           :source => :child_item, :source_type => 'Movie'
  has_many :pwning_movies, :through => :pwning_relations, 
           :source => :owner, :source_type => 'Movie'
end

这种数据结构的一个缺点是,您必须为可能相同的配对创建两个不同的角色。如果我想查看我的书的所有相关电影,我必须将这些集合添加到一起:

( pwned_movies + pwning_movies ).uniq

此问题的一个常见示例是社交网络应用中的“朋友”关系。 Insoshi使用的一种解决方案是在连接模型上注册after_create回调(在本例中为Relation),这将创建反向关系。同样需要after_destroy回调,但这样以一些额外的数据库存储为代价,您可以确信在单个数据库查询中获得所有相关电影。

class Relation
  after_create do 
    unless Relation.first :conditions => 
      [ 'owner_id = ? and owner_type = ? and child_item_id = ? and child_item_type = ?',       child_item_id, child_item_type, owner_id, owner_type ]
      Relation.create :owner => child_item, :child_item => owner
    end
  end
end

答案 1 :(得分:3)

我想出了一些解决方案。我不确定它是最好的。看来你不能有多态的has_many通过。

所以,我假装了一下。但这意味着放弃我非常喜爱的协会代理魔术,这让我感到难过。在基本状态下,这是它的工作原理。

book = Book.find(1)
book.add_related(Tag.find(2))
book.add_related(Category.find(3))
book.related        #=> [Tag#2, Category#3]
book.related(:tags) #=> [Tag#2]

我将它包装在一个可重用的模块中,可以使用单个has_relations类方法添加到任何模型类中。

http://gist.github.com/123966

我真的希望我不必完全重新实现关联代理来处理这个问题。

答案 2 :(得分:0)

我认为完全按照你所描述的那样做的唯一方法是连接表。虽然它不是那么糟糕,只有6,你几乎可以设置并忘掉它们。

答案 3 :(得分:0)

取决于您的电影/书籍数据库与数据库的密切关系

如果你宣布

怎么办?
class Items < ActiveRecord::Base
  has_many :tags
  has_many :categories
  has_and_belongs_to_many :related_items,
       :class => "Items",
       :join_table => :related_items,
       :foreign_key => "item_id",
       :associated_foreign_key => "related_item_id"
end

class Books < Items
class Movies < Items

确保将类型放入商品表