ruby-on-rails - Rails/ActiveRecord:在一侧是多态的情况下,建立多对多关联的正确方法是什么?
问题描述
我有Badge
和。评论和项目可以有很多徽章。一个徽章可以有很多评论和项目。Comment
Project
徽章通过名为反应的连接表指向评论和项目。
# 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
我现在可以为反应目标添加徽章:
> @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>]
但我不能反过来:
> 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!'
我不太确定为什么当我的关联应该是多态的时它建议我指定源类型。但是,我尝试过:
class Badge < ApplicationRecord
has_many :reactions
has_many :reaction_targets, through: :reactions, source_type: "ReactionTarget"
end
但后来我得到一个错误:
> Badge.first_or_create(name: "test").reaction_targets
NameError: uninitialized constant Badge::ReactionTarget
我正在努力解决这个问题。我错过了什么?我哪里出错了?是什么阻止我从徽章方面确定反应目标?
解决方案
TL;博士:
应用程序/模型/徽章.rb
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
解释
据我所知,多态多对多关联,你不能做
class Badge < ApplicationRecord
has_many :reaction_targets, through: :reactions
# ...
end
...(正如您所观察到的那样会引发错误),因为如果这是可能的,这意味着 dobadge.reaction_targets
应该返回一个“不同模型”实例的数组,即:
badge = Badge.find(1)
puts badge.reaction_targets.to_a
# => [<Comment id: 1>,
# <Project id: 45>,
# <SomeModel id: 99>,
# <COmment id: 3>,
# ...]
^ 上面的代码看起来很直观吧?...因为它应该返回一个不同类型记录的数组,这完全有道理,因为它是一种多态关系,对吧?是的,这是完全正确的,但是应该生成什么 SQL 字符串就成了问题。请参阅下面的示例 SQL 等效项:
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
^ ...因为reaction_targets
预计会有不同的模型实例,那么想象一下上面应该执行什么 SQL 来获取所有记录?虽然,我认为可以获得所有这些,但它可能不是直接的 SQL 语句,而是可能结合了一些应用程序端的 Ruby 代码逻辑。而且,返回类型不应该是 a ActiveRecord::Associations::CollectionProxy
,而可能是一种新类型的对象,专门为了迎合这种has_many
多态关系。我的意思是想象一下,如果你做下面这样的事情:
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语句,所以这可能就是为什么你需要为不同的“模型”添加一个source
和source_type
,就像我上面的回答一样。
可能的解决方法
如果您可以返回一个Array
对象而不是普通ActiveRecord::Associations::CollectionProxy
对象,您可以执行以下操作。(尽管您仍然必须指定每个多态has_many
关系,并且将来可能会添加更多。)
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
使用示例:
badge = Badge.find(1)
puts badge.reaction_targets
# =>[<Comment id: 1>,
# <Comment id: 45>,
# <Project id: 99>,
# <Project id: 3>,
# ...]
推荐阅读
- python - 将颜色条置于 2d 直方图上方(而不是下方)
- soot - 在 Soot 中为 java 代码使用类和方法
- java - 如何在 Java 的 Native HTTP Client 上使用代理密码认证
- java - 如何使 addSnapshotListener 不启动新活动?适用于 Java 但不适用于 Kotlin
- r - 获取R中两个元素的组合
- cuda - NVCC 不会在 /usr/lib/x86_64-linux-gnu 中查找库 - 为什么?
- youtube-api - Youtube API inStream 参数为空
- javascript - 如果所有项目具有相同的 Id,如何使用单击事件删除单击的项目
- visual-studio-code - VS Code:当我关闭子文件夹并返回文件时,子文件夹会重新打开
- html - 单选按钮不会出现在网站上