Search code examples
ruby-on-railsassociationsrating

Include associated model for all objects (in index action)


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

Solution

  • 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.