ActiveRecord - 查询多态关联

时间:2009-03-25 03:37:46

标签: ruby-on-rails ruby activerecord polymorphic-associations

我正在使用多态关联来跟踪项目中的注释。所有非常直接的东西。

我遇到的问题是根据多态关联查询并从Comment模型加入到它的所有者。

所以......

我有一个评论模型

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

还有一个ForumTopics模式:

class ForumTopic < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

我还有其他几个“可评论”的模型,现在并不重要。 所有这一切都有效。

我要做的是找到所有属于具有指定条件的ForumTopic的评论(在这种情况下,'featured'== true)。

当我尝试使用取景器加入模型时:

@comments = Comment.find(:all 
            :joins => :commentable
            :conditions => ["forum_topics.featured = ? ", true] 
            )

我收到以下错误:

  

无法急切加载多态关联:可评论

使用AR“包含语法”:

@comments = Comment.find(:all 
            :include => :forum_topics
            :conditions => ["forum_topics.featured = ? ", true] 
            )

返回:

  

未找到名为“forum_topics”的协会;也许你拼错了吗?

如果我尝试使用表名而不是关联名(字符串而不是符号)加入:

@comments = Comment.find(:all,
            :joins => "forum_topics",
            :conditions => ["forum_topics.featured = ? ", true] 
            )

我明白了:

  

Mysql ::错误:未知表'comments':SELECT comments。 FROM comments forum_topics WHERE(forum_topics.featured = 1)*

(你可以看到底层查询的语法完全关闭,连接完全丢失)。

不确定我正在做的事情是否可行,还有其他方法可以达到所需的结果,但似乎应该可行。

有什么想法吗? 我缺少什么?

8 个答案:

答案 0 :(得分:30)

哎呀!

我想我发现了问题。

加入时:

@comments = Comment.find(:all,
        :joins => "forum_topics",
        :conditions => ["forum_topics.featured = ? ", true] 
        )

你需要整个加入!

:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id",

看到非常棒的: http://guides.rubyonrails.org/active_record_querying.html#joining-tables

答案 1 :(得分:25)

一个老问题,但有一种更简洁的方法可以通过设置特定类型的直接关联以及多态来实现这一目标:

#comment.rb
class Comment < ActiveRecord::Base

  belongs_to :commentable, polymorphic: true
  belongs_to :forum_topics, -> { where( comments: { commentable_type: 'ForumTopic' } ).includes( :comments ) }, foreign_key: 'commentable_id'

  ...

end

然后,您可以将:forum_topics传递给includes,从而摆脱混乱加入的需要:

@comments = Comment
  .includes( :forum_topics )
  .where( :forum_topics => { featured: true } )

然后,您可以通过将查询移动到范围中来进一步清理它:

#comment.rb
class Comment < ActiveRecord::Base

  ...

  scope :featured_topics, -> { 
    includes( :forum_topics )
    .where( :forum_topics => { featured: true } ) 
  }

  ...

end

让你能够简单地做到

@comments = Comment.featured_topics

答案 2 :(得分:15)

一旦您使用“可评论”引入具有关联的另一个模型,则接受的解决方案不起作用。 commentable_id不是唯一的,因此您将开始检索错误的评论。

例如:

您决定添加接受评论的新闻模型......

class News < ActiveRecord::Base
   has_many :comments, :as => :commentable
end

现在,如果您对id为1的forum_topic和使用您的查询的ID为1的新闻文章发表评论,您可能会收到两条记录:

:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id"

您可以通过提供commentable_type作为您的条件之一来解决问题,但我认为这不是解决此问题的最佳方法。

答案 3 :(得分:15)

你需要一个条件,加Rails 3 +

很多人在答案和评论中暗示了这一点,但我觉得如果我在这里登陆并且阅读不够透彻,包括我在内的人会被绊倒。

所以,这是正确的答案,包括绝对必要条件。

@comments = Comment.joins( "INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id" )
                   .where( comments:     { commentable_type: 'ForumTopic' } )
                   .where( forum_topics: { featured:         true         } )

感谢所有人,特别是@Jits,@ Peter和@prograils的评论。

答案 4 :(得分:10)

我遇到了这个帖子,它引导我找到解决方案。使用commentable_type作为我的条件之一,但使用LEFT OUTER JOIN代替。这样就可以包含没有评论的论坛主题。

LEFT OUTER JOIN `comments` ON `comments`.`commentable_id` = `forum_topics`.`id` AND `comments`.`commentable_type` = 'ForumTopic'

答案 5 :(得分:7)

选中 Rails 5

解决方案1:

@comments = Comment
              .where(commentable_type: "ForumTopic")
              .joins("INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id")
              .where(forum_topics: {featured: true})
              .all

解决方案2:

@comments = Comment
              .joins("INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id AND comments.commentable_type = 'ForumTopic'")
              .where(forum_topics: {featured: true}).all

注意原始SQL语法:不允许反引号。请参阅http://guides.rubyonrails.org/active_record_querying.html#joining-tables

我个人更喜欢解决方案1,因为它包含较少的原始SQL语法。

答案 6 :(得分:0)

默认情况下,Rails不包含多态连接,但是此gem可以帮助您轻松地连接多态关系。 https://github.com/jameshuynh/polymorphic_join

答案 7 :(得分:0)

以下是适应您的目的的示例。

class AdwordsCampaign < ApplicationRecord
  has_many :daily_campaign_reports, as: :ga_campaign
end

class DailyCampaignReport < ApplicationRecord

  belongs_to :ga_campaign, polymorphic: true

  # scope
  def self.joins_ga_campaign(model)
    models = model.to_s.pluralize
    sql = <<~SQL
      INNER JOIN #{models}
      ON #{models}.id = daily_campaign_reports.ga_campaign_id
    SQL
    joins(sql).where(ga_campaign_type: model.to_s.camelize)
  end
end

然后我可以在需要的地方使用它,像这样:

DailyCampaignReport.joins_ga_campaign(:adwords_campaign)