首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Rails/ActiveRecord:在一方是多态的情况下,建立多到多个关联的正确方法是什么?

Rails/ActiveRecord:在一方是多态的情况下,建立多到多个关联的正确方法是什么?
EN

Stack Overflow用户
提问于 2019-02-23 19:07:42
回答 1查看 161关注 0票数 0

我有BadgeCommentProject。评论和项目可以有许多徽章。一个徽章可以有许多评论和项目。

标记通过一个名为reactions的联接表指向评论和项目。

代码语言:javascript
复制
# reaction.rb
class Reaction < ApplicationRecord
  belongs_to :badge
  belongs_to :reaction_target, polymorphic: true
end

# badge.rb
class Badge < ApplicationRecord
  has_many :reactions
  has_many :reaction_targets, through: :reactions
end

# comment.rb
class Comment < ApplicationRecord
  has_many :reactions, as: :reaction_target
  has_many :badges, through: :reactions
end

# project.rb
class Project < ApplicationRecord
  has_many :reactions, as: :reaction_target
  has_many :badges, through: :reactions
end

我现在可以在反应目标上加上徽章:

代码语言:javascript
复制
> @comment.badges << Badge.first_or_create(name: "test")
=> [#<Badge:0x00007fcff7619d28
  id: 1,
  name: "test",
  created_at: Sat, 23 Feb 2019 18:25:54 UTC +00:00,
  updated_at: Sat, 23 Feb 2019 18:25:54 UTC +00:00>]

但我不能做相反的事:

代码语言:javascript
复制
> Badge.first_or_create(name: "test").reaction_targets << @comment
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have
a has_many :through association 'Badge#reaction_targets' on the
polymorphic object 'ReactionTarget#reaction_target' without 'source_type'.
Try adding 'source_type: "ReactionTarget"' to 'has_many :through'
definition. from /Users /elephant/.rvm/gems/ruby-2.6.0/gems/activerecord-5
.2.2/lib/active_record/reflecti on.rb:932:in `check_validity!'

我不太清楚为什么它会建议我指定源类型,而我的关联应该是多态的。然而,我试过:

代码语言:javascript
复制
class Badge < ApplicationRecord
  has_many :reactions
  has_many :reaction_targets, through: :reactions, source_type: "ReactionTarget"
end

但是我得到了一个错误:

代码语言:javascript
复制
> Badge.first_or_create(name: "test").reaction_targets
NameError: uninitialized constant Badge::ReactionTarget

我正努力弄清楚这件事。我遗漏了什么?我哪里出错了?是什么让我无法从警徽边确定反应目标?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-02-23 21:38:03

TL;DR:

app/models/badge.rb

代码语言:javascript
复制
class Badge < ApplicationRecord
  has_many :reactions
  
  has_many :reaction_target_comments, through: :reactions, source: :reaction_target, source_type: 'Comment'
  has_many :reaction_target_projects, through: :reactions, source: :reaction_target, source_type: 'Project '
  # ... etc
end

解释

就我所知的多态多到多关联而言,您不能这样做。

代码语言:javascript
复制
class Badge < ApplicationRecord
  has_many :reaction_targets, through: :reactions
  # ...
end

.(将引发一个错误,正如您所观察到的那样),因为如果这是可能的,这意味着执行badge.reaction_targets应该返回一个“不同模型”-instances的数组,即:

代码语言:javascript
复制
badge = Badge.find(1)
puts badge.reaction_targets.to_a
# => [<Comment id: 1>,
#     <Project id: 45>,
#     <SomeModel id: 99>,
#     <COmment id: 3>,
#     ...]

上面的代码看起来很直观,对吧?因为它应该返回一个由不同类型的记录组成的数组,这非常有意义,因为它是一个多态关系,对吗?是的,这是完全正确的,但是在应该生成什么SQL字符串时会出现问题。请参见下面的示例SQL等效:

代码语言:javascript
复制
puts badge.reaction_targets
# =>  SELECT "WHAT_TABLE_1".* FROM "WHAT_TABLE_1" INNER JOIN reactions ...
#     SELECT "WHAT_TABLE_2".* FROM "WHAT_TABLE_2" INNER JOIN reactions ...
#     SELECT "WHAT_TABLE_3".* FROM "WHAT_TABLE_3" INNER JOIN reactions ...
#     ... etc

^ ...because reaction_targets应该有不同的模型实例,然后想象上面要执行哪些SQL才能获得所有记录?虽然,我认为它们都是可能的,但是它可能不是一个直接的SQL语句,而是一些应用程序端Ruby代码逻辑的组合。而且,返回类型不应该是ActiveRecord::Associations::CollectionProxy,而可能是一种新的对象类型,专门用于满足这种has_many多态关系。我的意思是,想象一下,如果你做了下面这样的事情,否则:

代码语言:javascript
复制
badge.reaction_targets.where(is_enabled: true, first_name: 'Hello')
# or even more complex:
badge.reaction_targets.joins(:users).where(users: { email: 'email@example.com' }).

^…我认为上面对于多态联接没有直接的SQL语句,所以这也许就是为什么需要为不同的“模型”添加一个sourcesource_type的原因,就像我上面的回答一样。

可能的解决办法

如果您很好地返回一个Array对象而不是普通的ActiveRecord::Associations::CollectionProxy对象,您可以执行如下操作。(尽管您仍然必须指定每个多态has_many关系,并可能在将来添加更多关系。)

代码语言:javascript
复制
class Badge < ApplicationRecord
  has_many :reactions

  has_many :reaction_target_comments, through: :reactions, source: :reaction_target, source_type: 'Comment'
  has_many :reaction_target_projects, through: :reactions, source: :reaction_target, source_type: 'Project'

  def reaction_targets
    reaction_target_comments.to_a + reaction_target_projects.to_a
  end
end

用法示例:

代码语言:javascript
复制
badge = Badge.find(1)
puts badge.reaction_targets
# =>[<Comment id: 1>,
#    <Comment id: 45>,
#    <Project id: 99>,
#    <Project id: 3>,
#    ...]
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54845127

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档