Rails polymorphic many to many association

后端 未结 4 393
无人及你
无人及你 2021-02-04 21:39

I\'m trying setup a generic sort of web of related objects. Let say I have 4 models.

  • Book
  • Movie
  • Tag
  • Category

I would lik

相关标签:
4条回答
  • 2021-02-04 22:09

    I think the only way to do it exactly as you described is the join tables. It's not so bad though, just 6, and you can pretty much set-and-forget them.

    0 讨论(0)
  • 2021-02-04 22:16

    Support for polymorphism has improved dramatically since the early days. You should be able to achieve this in Rails 2.3 by using a single join table for all your models -- a Relation model.

    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
    

    A drawback of this kind of data structure is that you are forced to create two different roles for what may be an equal pairing. If I want to see all the related movies for my Book, I have to add the sets together:

    ( pwned_movies + pwning_movies ).uniq
    

    A common example of this problem is the "friend" relationship in social networking apps. One solution used by Insoshi, among others, is to register an after_create callback on the join model ( Relation, in this case ), which creates the inverse relationship. An after_destroy callback would be similarly necessary, but in this way at the cost of some additional DB storage you can be confident that you will get all your related movies in a single DB query.

    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
    
    0 讨论(0)
  • 2021-02-04 22:16

    depending on how closesly related your movies/books db tables are

    what if you declared

    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
    

    make sure you put type in your items table

    0 讨论(0)
  • 2021-02-04 22:32

    I have come up with a bit of solution. I'm not sure it's the best however. It seems you cannot have a polymorphic has_many through.

    So, I fake it a bit. But it means giving up the association proxy magic that I love so much, and that makes me sad. In a basic state, here is how it works.

    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]
    

    I wrapped it up in a reusable module, that can be added to any model class with a single has_relations class method.

    http://gist.github.com/123966

    I really hope I don;t have to completely re-implement the association proxy to work with this though.

    0 讨论(0)
提交回复
热议问题