4
\$\begingroup\$

I have three models which look like this:

class Post < ActiveRecord::Base
  belongs_to :user, :counter_cache => :count_post
  has_many :likes
end

class User < ActiveRecord::Base
  has_many :posts
  has_many :likes
end

class Like < ActiveRecord::Base
  belongs_to :user
  belongs_to :post, :counter_cache => :count_like
end

A user can only like once per Post so I have a method that checks if the current user already like a post and the dislike link is displayed.

<% if post.has_like_from? current_user %>
    <li id="like_<%= dom_id(post) %>"><%= link_to "like", create_like_path(:post_id => post.id), :remote => true %></li>
<% else %>
    <li id="dislike_<%= dom_id(post) %>"><%= render 'dislike', :post => post %></li>
<% end %>

And I have this method on model post:

 def has_like_from?(target_user)
   likes.where(:user_id => target_user.id).first == nil
 end

Is there any way to check every post if current user already like a post? I want to increase performance because load to many posts per request.

Example: startrace when load 7 records

Started GET "/" for 127.0.0.1 at 2014-06-30 23:32:51 +0700
  ←[1m←[35mUser Load (1.0ms)←[0m  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Processing by HomesController#index as HTML
  ←[1m←[36mPost Load (1.0ms)←[0m  ←[1mSELECT "posts".* FROM "posts" ORDER BY created_at DESC←[0m
  ←[1m←[35mLike Load (1.0ms)←[0m  SELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 7 AND "lik
es"."user_id" = 1 LIMIT 1
  ←[1m←[36mLike Load (0.0ms)←[0m  ←[1mSELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 6 AND
"likes"."user_id" = 1 LIMIT 1←[0m
  ←[1m←[35mLike Load (1.0ms)←[0m  SELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 5 AND "lik
es"."user_id" = 1 LIMIT 1
  ←[1m←[36mLike Load (1.0ms)←[0m  ←[1mSELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 4 AND
"likes"."user_id" = 1 LIMIT 1←[0m
  ←[1m←[35mLike Load (0.0ms)←[0m  SELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 3 AND "lik
es"."user_id" = 1 LIMIT 1
  ←[1m←[36mLike Load (0.0ms)←[0m  ←[1mSELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 2 AND
"likes"."user_id" = 1 LIMIT 1←[0m
  ←[1m←[35mLike Load (0.0ms)←[0m  SELECT "likes".* FROM "likes" WHERE "likes"."post_id" = 1 AND "lik
es"."user_id" = 1 LIMIT 1
  Rendered homes/_posts.html.erb (29.0ms)
\$\endgroup\$
2
\$\begingroup\$

Have you created an index for likes.post_id? Note also that you can simplify that method by using has_many with the option through and ActiveRecord#include?. That's what I'd write (with positive logic, I don't see why has_like_from? should perform a == nil):

class Post < ActiveRecord::Base
  belongs_to :user, :counter_cache => :count_post
  has_many :likes
  has_many :like_users, :through => :likes

  def liked_by?(user)
    like_users.include?(user)
  end
end
\$\endgroup\$
  • \$\begingroup\$ i think this will load whole like_users table to memory, it may be more efficient to do like_users.exists?(user_id: user.id). \$\endgroup\$ – rui Jul 24 '14 at 9:38
  • \$\begingroup\$ @rui, Here #include? comes from active-record, not Array, it should generate the same SQL than your code. I cannot test right now, but I'd be surprised if that's not the case. \$\endgroup\$ – tokland Jul 24 '14 at 9:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.