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)
1 Answer 1
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
-
\$\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– rui2014年07月24日 09:38:03 +00:00Commented Jul 24, 2014 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– tokland2014年07月24日 09:39:20 +00:00Commented Jul 24, 2014 at 9:39