Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent upvote model from being called for every comment

I have three models: User, Comment and Upvote. User-to-Comment has a one-to-many relation, Comment-to-Upvote has a one-to-many relation and User-to-Upvote has a one-to-many relation.

I want to do something similar to the upvoting done on Stackoverflow. So when you upvote/downvote the arrow will highlight and remain highlighted even if you refresh the page or come back to the page days/weeks later.

Currently I am doing this:

<% if Upvote.voted?(@user.id, comment.id) %>
  <%= link_to '^', ... style: 'color: orange;'%>
<% else %>
  <%= link_to '^', ... style: 'color:black;'%>
<% end %>

where the voted? method looks like this:

  def self.voted?(user_id, comment_id)
    find_by(comment_id: comment_id, user_id: user_id).present?
  end

So if I have 10 comments on a page, this will load an upvote from my database 10 times, just to check if it exist!

There has to be a better way to go about doing this, but I think my brain stopped working, so I can't think of any.

like image 970
Tucker Avatar asked Jun 16 '15 13:06

Tucker


2 Answers

Assuming you have properly set relations

# user.rb
class User
  has_many :upvotes
end

we can load comments, current user and his upvotes:

# comments_controller.rb
def index
  @comments = Comment.limit(10)
  @user = current_user
  user_upvotes_for_comments = current_user.upvotes.where(comment_id: @comments.map(&:id))
  @upvoted_comments_ids = user_upvotes_for_comments.pluck(:comment_id)
end

And then change if condition in view:

# index.html.erb
<% if @upvoted_comments_ids.include?(comment.id) %>
  <%= link_to '^', ... style: 'color: orange;'%>
<% else %>
  <%= link_to '^', ... style: 'color:black;'%>
<% end %>

It will require only 2 DB queries. Hope it helps.

like image 69
hedgesky Avatar answered Nov 15 '22 08:11

hedgesky


We can do it the following way if you want it to be handled by a single query.

Lets make sure the relations are proper

# user.rb
class User < ActiveRecord::Base
  has_many :comments
  has_many :upvotes
end

# comment.rb
class Comment < ActiveRecord::Base
  belongs_to :user
  has_many :upvotes
end

# upvote.rb
class Upvote < ActiveRecord::Base
  belongs_to :user
  belongs_to :comment
end

Then in the controller

def index
  current_user = User.first # current_user may come from devise or any authentication logic you have.
  @comments = Comment.select('comments.*, upvotes.id as upvote').joins("LEFT OUTER JOIN upvotes ON comments.id = upvotes.comment_id AND upvotes.user_id = #{current_user.id}")
end

And in view

# index.html.erb
<% @comment.each do |comment| %>
  <% link_color =  comment.upvote ? 'orange' : 'black' %>
  <%= link_to '^', ...style: "color: #{link_color}" %>
<% end %>
# And all of your logics ;)
like image 36
Ojash Avatar answered Nov 15 '22 06:11

Ojash