Rails4,获得作者和作者评级的所有(多级)评论

时间:2015-09-06 11:30:25

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

有模特:

class Movie < ActiveRecord::Base   
  has_many :comments
  has_many :ratings
end

# self join to unable multilevel comments
class Comment < ActiveRecord::Base
  belongs_to :upcomment, class_name: "Comment"
  belongs_to :user
end

class Rating < ActiveRecord::Base
  belongs_to :movie
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :ratings
  has_many :comments
end

当我不想得到他们的回复和评论/回复作者的所有评论我做以下查询:

@comments = Comment
  .where('comments.movie_id = ? AND comments.upcomment_id IS NULL', params[:movie])
  .includes([:user, :replies => :user])

Q1:但是,如何修改此查询以包含每条评论的用户评分并回复?

我的尝试

现在为了简单起见,我会跳过回复。我在.includes和where子句中添加了等级如下:

@comments = Comment
  .where('comments.movie_id = ? AND comments.upcomment_id IS NULL', params[:movie]) 
  .includes([:user => :ratings])
  .where(ratings: { movie_id: params[:movie] })

它有效,但只返回作者评分的那些评论。当我查看Rails生成的SQL时,我注意到了“收视率”。“movie_id”=?'是(可能)在错误的地方:

SELECT [...]
LEFT OUTER JOIN "users" "users" ON "users"."id" = "comments"."user_id" 
LEFT OUTER JOIN "ratings" "users_ratings" ON "ratings"."user_id" = "users"."id" 
WHERE (comments.movie_id = '131' AND comments.upcomment_id IS NULL) AND "ratings"."movie_id" = 131

| So I changed and made a query in SQLite to check if it works
V
SELECT [...]
LEFT OUTER JOIN "users" ON "users"."id" = "comments"."user_id" 
LEFT OUTER JOIN "ratings" ON "ratings"."user_id" = "users"."id" AND "ratings"."movie_id" = "comments"."movie_id"
WHERE (comments.movie_id = '131' AND comments.upcomment_id IS NULL)

似乎有效,但是 Q2:如何在Rails中执行多个条件的连接(不自己编写连接)?

由于我没有找到上述问题的答案,我自己写了这些联接。我不得不添加eager_load以使其工作并最终得到以下查询:

@comments = Comment
  .where('comments.movie_id = ? AND comments.upcomment_id IS NULL', params[:movie]) 
  .joins('LEFT OUTER JOIN "comments" "replies_comments" ON "replies_comments"."upcomment_id" = "comments"."id"')
  .joins('LEFT OUTER JOIN "users" "users_replies_comments" ON "users_replies_comments"."id" = "replies_comments"."user_id"')
  .joins('LEFT OUTER JOIN "ratings" "ratings_users_replies_comments" ON "ratings_users_replies_comments"."user_id" = "users_replies_comm    ents.id" AND "ratings_users_replies_comments"."movie_id" = "comments"."movie_id"')
  .joins('LEFT OUTER JOIN "users" "users_comments" ON "users_comments"."id" = "comments"."user_id"')
  .joins('LEFT OUTER JOIN "ratings" "ratings_users_comments" ON "ratings_users_comments"."user_id" = "users_comments"."id" AND "ratings_    users_comments"."movie_id" = "comments"."movie_id"')
  .eager_load([:replies => { :user => :ratings }, :user => :ratings])

它似乎有效,但我认为它可能是性能杀手。所以我的最后一个想法是用作者/回复加载所有注释并迭代它,收集所有作者ID并使用“IN(id1,id2,...)”进行附加查询。然后将其作为单独的变量传递给视图。像这样:

 ids = @comments.map { |c| c.user.id }
 @comments.each { |c| c.replies.map { |r| ids.push r.user.id } }
 ids.uniq!

 @ratings = Rating
   .where('movie_id = ?', params[:movie])
   .where('user_id IN(?)', ids)

或者mayby我不应该简单地做任何事情并让Rails在需要时加入懒惰的负载?你怎么看?

1 个答案:

答案 0 :(得分:0)

所以,评分与评论不一样吧?为什么不?使评论也作为(可选)评级,并删除评级表和模型。这需要添加评分分数以进行评论并使评论正文可选。然后你只需要加载电影的所有评论。

不要担心要预加载所有线程子注释的查询。当你有几百万条评论时,你会担心这一点。您应该为每个孩子,孙子等添加最顶级“adam / eve”评论的ID,以便您可以在一个查询中快速加载所有后代。您还可以添加movie_id,以便将其放入索引并快速加载电影的每个评论,无论多深。

此外,在创建或更新评级时,将电影的总评分分数缓存在电影表中。然后,您可以显示包含评分的电影列表,而不会为每个请求执行COUNT。