0

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
Hommer Smith
  • 26,772
  • 56
  • 167
  • 296

1 Answers1

1

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.

Community
  • 1
  • 1
hjing
  • 4,922
  • 1
  • 26
  • 29
  • So, I can't do that with an include when doing: @page.comments ? – Hommer Smith May 29 '14 at 22:16
  • What do you mean? Are you trying to load ratings and comments at the same time? – hjing May 29 '14 at 22:19
  • hjing, ideally, page.comments where each comment has the rating for the current user. Yes. – Hommer Smith May 29 '14 at 22:26
  • Do you mean you want to eager load every rating for every comment? http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations, or do you want to have a separate instance variable called `@ratings` which only contains ratings made by the current user for comments loaded in the `@comments` instance variable? – hjing May 29 '14 at 22:33
  • I would like to eager load every rating (done by the current user) for every comment. A comment can have just ONE rating for each user. – Hommer Smith May 29 '14 at 22:42
  • I updated my answer with alternatives, but I'm not aware of a way to do what you're looking for. If you're worried about performance, I would probably look into figuring out a good caching strategy. – hjing May 29 '14 at 23:03