【问题标题】:Rails: Class inheritance and complex polymorphic has_many :through associationRails:类继承和复杂的多态has_many:通过关联
【发布时间】:2012-12-31 16:27:16
【问题描述】:

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

  1. 问题
  2. 用户
    1. 专业
    2. 代表
  3. 分类
    1. 类别
    2. 主题
    3. 职业
    4. 地区
    5. 地区
    6. 国家

有多种用户(UserProfessional < UserRepresentant < User),它们都继承自单表继承的User类。

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

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

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

迁移文件:

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"

问题.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"

专业.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 =&gt; "Lorem Ipsum Dolor Sit Amet", :author =&gt; current_user, :topics =&gt; [list of topics,...]) 效果很好,除了未保存的主题。)

  2. Where 子句无法正常工作(即 Question.joins(:topics).where(:conditions =&gt; {:topics =&gt; {:id =&gt; [list of topics,...]}}) 失败并出现 no such column: "Topics"."id" 错误。

有什么帮助吗?谢谢!

更新

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

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

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

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'

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

【问题讨论】:

  • 我的问题可能是我的所有分类类(主题、专业等)中都没有has_many :through 语句吗?如果这是问题所在,则需要一个非常非 DRY 的解决方案......

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


【解决方案1】:

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

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

对于问题 #1,似乎当您设置 question.topics = [...] 时,Rails 创建的分类对象正在设置为“分类”(而不是“主题”)的分类类型。这似乎是由于 Rails 的 through_association.rb:51,它采用正在存储的模型的 base_class,而不仅仅是实际的类名。

我能够通过分类模型上的 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

【讨论】:

  • 在通过 GitHub 查找 Rails issues referring to base_class 之后,我找到了 store_base_sti_class gem。它会改变这种行为,但会在整个 Rails 应用程序中改变(这可能是也可能不是你想要的)。
  • 谢谢!我已经安装了宝石。它成功了一半。请查看我的更新。
  • 我已经通过更多研究和另一个链接更新了答案。但我可能需要查看更多您的应用程序才能提供帮助。根据您在示例中发布的代码,我引用的 gem 应该为您处理关联范围。
  • 太棒了!现在一切正常。非常感谢@Alex!
猜你喜欢
  • 1970-01-01
  • 2014-12-16
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
相关资源
最近更新 更多