I am trying to develop ratings for my application, where a User is able to set a specific rating for a comment. I have followed the following tutorial in order to do so.
Here are my associations:
class Rating < ActiveRecord::Base
belongs_to :comment
belongs_to :user
end
class Comment < ActiveRecord::Base
has_many :ratings
belongs_to :user
end
class User < ActiveRecord::Base
has_many :ratings
has_many :comments
end
My problem here is that, in the index action of my comments controller, I need to include the rating that the user has done for that comment. In the tutorial is just shown how to select a particular rating by doing this:
@rating = Rating.where(comment_id: @comment.id, user_id: @current_user.id).first
unless @rating
@rating = Rating.create(comment_id: @comment.id, user_id: @current_user.id, score: 0)
end
However, I will have several ratings, because in my controller I have:
def index
@comments = @page.comments #Here each comment should have the associated rating for the current_user, or a newly created rating if it does not exist.
end
You want to find the comment's rating where the rating's user_id matches the current user.
<%= comment.ratings.where(user_id: current_user.id).first %>
However this sort of logic is pretty cumbersome in the views, a better strategy would be to define a scope in Rating
that returns all ratings made by a specific user.
class Rating
scope :by_user, lambda { |user| where(user_id: user.id) }
end
class Comment
# this will return either the rating created by the given user, or nil
def rating_by_user(user)
ratings.by_user(user).first
end
end
Now in your view, you have access to the rating for the comment created by the current user:
<% @comments.each do |comment| %>
<%= comment.rating_by_user(current_user) %>
<% end %>
If you want to eager load all ratings in your index page, you can do the following:
def index
@comments = page.comments.includes(:ratings)
end
You can then find the correct rating with the following:
<% @comments.each do |comment| %>
<%= comment.ratings.find { |r| r.user_id == current_user.id } %>
<% end %>
This would return the correct rating without generating any extra SQL queries, at the expense of loading every associated rating for each comment.
I'm not aware of a way in ActiveRecord to eager load a subset of a has_many relationship. See this related StackOverflow question, as well as this blog post that contains more information about eager loading.