Why make this post
It was a problem i faced myself with and couldn't find any real direct answer to. "How do i eager load if a user has liked a post? If a user is already friends with another user?". I wanted to know how to eager load if an association that is directly related to the user exists(though it can be used in other ways, not just this one) and that now has lead me to write this so that other people that come across the same problem might find a possible answer here.
In this example we will eager load posts alongside the user's like.
Current Attributes
To be able to know if an Active Record object is related to an user we first need to share some kind of unique identifier related to the user with the model. Enters current attributes.
class Current < ActiveSupport::CurrentAttributes
attribute :user_id
end
(Should be a file called Current inside models)
We make use of the Current class to make the user id available globally, we could make the whole user object available but we only need the id so we keep it simple(Current Attributes shouldn't be "abused").
Now comes the question of how to set the id, there are different ways to do it and it depends on how your authentication is being done but we can keep it simple and set it before every request if the user is logged in (here we are using Devise).
class ApplicationController < ActionController::Base
before_action :set_user_id, if: :user_signed_in?
protected
def set_user_id
Current.user_id = current_user.id
end
end
Association
Now that we have access to the id in the model we can establish a has_many association that will allow us to check if a relation exists. We will first make the Like Model to connect user and post.
class Like < ApplicationRecord
belongs_to :post
belongs_to :user
end
We will after add the has_many association to the Post model.
class Post < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
has_many :liked, -> { where(id: Current.user_id) }, through: :likes, source: :user
end
In this case we will get the current user in the association, you can also just get the like itself, we are getting user here to show how do it with a through association. The association Liked will either have 1 Active Record object or will be empty (it will NOT be nil).
Usage in Controller and View
Now we can finally eager load it in controllers.
class PostsController < ApplicationController
before_action :authenticate_user!
def index
@posts = Post.eager_load(:liked).all
end
end
And then we can use it like this in Views.
<% @posts.each do |post| %>
<% if post.liked.any? %>
<div>Liked</div>
<% else %>
<div>Like</div>
<% end %>
<%# OR %>
<div> <%= post.liked.any? ? "Liked" : "Like" %> </div>
<% end %>
Conclusion
Just wanted to share one of the ways to handle this specific issue that will surely arise if you keep using Rails. Not sure if i came up with the best title for this but hopefully it gets the message across.
Top comments (0)