Rails:类继承和复杂的多态has_many:通过关联

时间:2012-12-31 16:27:16

标签: ruby-on-rails has-many-through polymorphic-associations single-table-inheritance

我正在开发的应用程序有3个主要模型和许多单表继承模型:

  1. 问题
  2. 用户
    1. 专业
    2. Representant
  3. 分类
    1. 分类
    2. 主题
    3. 行业
    4. 局部性
    5. 区域
    6. 国家
  4. 有多种用户(UserProfessional < UserRepresentant < User),这些用户都继承自具有单表继承的User类。

    有多种分类法(Category < TaxonomyTopic < TaxonomyProfession < TaxonomyLocality < TaxonomyRegion < TaxonomyCountry < Taxonomy)都是从具有单表继承的Taxonomy类。

    问题,以及专业人士也通过多对多的关系进行分类(他们可以有很多主题,许多专业,许多类别等......)

    现在,我正在寻找一种方法来建立这些多态对象之间的多对多关系。我已经尝试了has_many :through解决方案并创建了一个Classification类。

    迁移文件:

    class CreateClassifications < ActiveRecord::Migration
      def change
        create_table :classifications, :id => false do |t|
          t.references :classifiable, :null => false, :default => 0, :polymorphic => true
          t.references :taxonomy,     :null => false, :default => 0, :polymorphic => true
        end
    
        add_index :classifications, [:classifiable_id, :taxonomy_id]
        add_index :classifications, [:taxonomy_id, :classifiable_id]
      end
    end
    

    模型文件:

    class Classification < ActiveRecord::Base
    
      attr_accessible :classifiable, :classifiable_id, :classifiable_type,
                      :taxonomy, :taxonomy_id, :taxonomy_type
    
      belongs_to :classifiable, :polymorphic => true
      belongs_to :taxonomy,     :polymorphic => true
    
    end
    

    然后,我为问题,专业人士和分类法添加了has_many :through个关联。

    Taxonomy.rb

    has_many :classifications, :as => :taxonomy, :foreign_key => :taxonomy_id
    has_many :classifiables, :through => :classifications, :source => :classifiable
    has_many :users,         :through => :classifications, :source => :classifiable, :source_type => "User"
    has_many :professionals, :through => :classifications, :source => :classifiable, :source_type => "Professional"
    has_many :representants, :through => :classifications, :source => :classifiable, :source_type => "Representant"
    has_many :questions,     :through => :classifications, :source => :classifiable, :source_type => "Question"
    has_many :guides,        :through => :classifications, :source => :classifiable, :source_type => "Guide"
    

    Question.rb

    has_many :classifications, :as => :classifiable, :foreign_key => :classifiable_id, :dependent => :destroy
    has_many :taxonomies, :through => :classifications, :source => :taxonomy
    has_many :topics,     :through => :classifications, :source => :taxonomy, :source_type => "Topic"
    

    Professional.rb

    has_many :classifications, :as => :classifiable, :foreign_key => :classifiable_id, :dependent => :destroy
    has_many :taxonomies,  :through => :classifications, :source => :taxonomy
    has_many :topics,      :through => :classifications, :source => :taxonomy, :source_type => "Topic"
    has_many :professions, :through => :classifications, :source => :taxonomy, :source_type => "Profession"
    

    现在,在完成所有这些之后,事情不能很好地运作......

    1. 我似乎无法将分类标准分配给专业人员或问题(即Question.create(:title => "Lorem Ipsum Dolor Sit Amet", :author => current_user, :topics => [list of topics,...])除了未保存的主题外,效果很好。)

    2. where子句无法正常工作(例如Question.joins(:topics).where(:conditions => {:topics => {:id => [list of topics,...]}})no such column: "Topics"."id"错误而失败。

    3. 有任何帮助吗?谢谢!

      更新

      我已按照指示安装了gem'store_base_sti_class'。它对分类模型产生了预期的效果。

      #<Classification classifiable_id: 1, classifiable_type: "Professional", taxonomy_id: 17, taxonomy_type: "Topic">
      

      但是,当我查询主题(Professional.find(1).topics)时,ActiveRecord仍然在寻找“User”类而不是“Professional”...

      SELECT "taxonomies".* FROM "taxonomies" INNER JOIN "classifications" ON "taxonomies"."id" = "classifications"."taxonomy_id" WHERE "taxonomies"."type" IN ('Topic') AND "classifications"."classifiable_id" = 1 AND "classifications"."classifiable_type" = 'User' AND "classifications"."taxonomy_type" = 'Topic'
      

      知道如何解决这两个问题吗?

1 个答案:

答案 0 :(得分:3)

对于问题#2,where子句中的键应映射到表名,而不是关联名。所以我想你会想要:

Question.joins(:topics).where(Topic.table_name => {:id => [...]})

对于问题#1,当你设置question.topics = [...]时,似乎Rails创建的Classification对象被设置为taxonomy_type为“Taxonomy”(而不是“Topic”)。这似乎是由于Rails的through_association.rb:51,它将存储模型的base_class,而不仅仅是实际的类名。

我能够通过Classification模型上的before_validation回调解决这个问题。在我看来,替代方案是实际Rails关联代码的补丁,以使此行为可配置。

class Classification < ActiveRecord::Base
  attr_accessible :classifiable, :classifiable_id, :classifiable_type,
                  :taxonomy, :taxonomy_id, :taxonomy_type

  belongs_to :classifiable, polymorphic: true
  belongs_to :taxonomy, polymorphic: true
  before_validation :set_valid_types_on_polymorphic_associations

  protected

  def set_valid_types_on_polymorphic_associations
    self.classifiable_type = classifiable.class.model_name if classifiable
    self.taxonomy_type = taxonomy.class.model_name if taxonomy
  end
end

<强>更新

在设置关联范围时,似乎有另一个Rails决定(在preloader/association.rb:113中)使用model.base_class.sti_name而不是model.sti_name

宝石应该为你照顾这个。请参阅store_base_sti_class_for_3_1_and_above.rb:135了解它如何包装has_many :as选项。在我的本地环境中,这可以按预期工作:

$ bundle exec rails console
irb(main):001:0> topics = 3.times.map { Topic.create }
irb(main):002:0> p = Professional.new
irb(main):003:0> p.topics = topics
irb(main):004:0> p.save!
irb(main):005:0> exit

$ bundle exec rails console
irb(main):001:0> puts Professional.find(1).topics.to_sql
SELECT "taxonomies".* FROM "taxonomies" INNER JOIN "classifications" ON "taxonomies"."id" = "classifications"."taxonomy_id" WHERE "taxonomies"."type" IN ('Topic') AND "classifications"."classifiable_id" = 2 AND "classifications"."classifiable_type" = 'Professional' AND "classifications"."taxonomy_type" IN ('Topic')
irb(main):002:0> Professional.find(1).topics.count
=> 3