Reputation: 9040
This is an extension to a previous question that I asked, which was how to set up a relationship between users, ideas, and 'likes'. A user can create multiple ideas. Any user can 'like' or 'dislike' any idea.
As mentioned in the previous post, here are my relationship in Laravel:
User
public function ideas(){
return $this->hasMany('Idea');
}
public function liked(){
return $this->belongsToMany('Idea', 'ideas_likes')->withPivot('liked');
}
Idea
public function likes(){
return $this->belongsToMany('User', 'ideas_likes')->withPivot('liked');
}
public function user(){
return $this->belongsTo('User');
}
My 'ideas_likes' table looks like:
ideas_likes
------------
id primary key
user_id foreign key
idea_id foreign key
liked boolean
I have a webpage that lists ideas from various users. Only an authenticated user can reach this page. Next to each idea is a like and dislike button. When the like button is clicked, an entry is added to the ideas_likes table with the 'liked' boolean set to true. When the dislike button is clicked, and entry is added with the 'liked' boolen set to false.
When a user dislikes an idea, that idea should no longer show up in the list for that user. If the user likes an idea, then the idea will be highlighted in the list.
When displaying the ideas in my view, I'll be looping through them like so:
@foreach ($ideas as $idea)
<div class="idea">
<h1>{{ $idea->title }}</h1>
<p>{{ $idea->description }}</p>
</div>
@endforeach
However, as noted, I'll want to highlight ideas that have been 'liked' by the user viewing the list. Maybe it would look something like this:
@foreach ($ideas as $idea)
<div class="idea@if($idea->liked) highlight@endif">
<h1>{{ $idea->title }}</h1>
<p>{{ $idea->description }}</p>
</div>
@endforeach
Plus, I want the idea author's username (which is not necessarily the same as the logged in user) shown too:
@foreach ($ideas as $idea)
<div class="idea@if($idea->liked) highlight@endif">
<h1>{{ $idea->title }}</h1>
<p>{{ $idea->description }}</p>
<p>{{ $idea->user->username }}</p> <=== SOMETHING LIKE THIS
</div>
@endforeach
Previous to introducing the concept of 'likes' to my application, my query for gathering the idea and user information looked like:
$ideas = Idea::with('user')->orderBy('created_at', 'desc')->get();
That was returning a list of all ideas, plus their associated user information. Now, however, it gets trickier. I want all of the ideas, plus their associated user information (whomever posted it), except for those ideas disliked by the currently authenticated user, and also highlighted if they were liked by the authenticated user. Phew!
Any help would be much appreciated.
Upvotes: 1
Views: 844
Reputation: 152890
This takes care of the eager loading and filters out the ideas the current user disliked.
$ideas = Idea::with('user', 'likes')->whereDoesntHave('likes', function($q){
$q->where('user_id', Auth::id());
$q->where('liked', false);
})->get();
Now you can add an accessor to create a dynamic liked
property. In Idea
:
public function getLikedAttribute(){
$user = $this->likes->find(Auth::id());
return $user != null && $user->pivot->liked == true;
}
You can use it just like you had it in your question. $idea->liked
. Note that you should eager load likes
to use this accessor. Otherwise for every idea an additional query will be run to fetch likes
when you access ->liked
.
I found a way to make it a bit more efficient and without a loop for each idea. However I find it a bit more ugly. To be able to eager load the data I had to create a new relation.
public function likedByCurrentUser(){
return $this->belongsToMany('User', 'ideas_likes')
->where('user_id', Auth::id())
->where('liked', true)
->selectRaw('COUNT(*) > 0 AS liked');
}
And here's the rewritten accessor:
public function getLikedAttribute(){
return !! $this->likedByCurrentUser->first()['liked'];
}
You see, the loop is gone!
Finally you need to eager load the new relation. Here's the example:
$ideas = Idea::with('user', 'likedByCurrentUser')->whereDoesntHave('likes', function($q){
$q->where('user_id', Auth::id());
$q->where('liked', false);
})->get();
Alternatively you can add likedByCurrentUser
to the with
array in the model so it will be eager loaded all the time.
protected $with = array('likedByCurrentUser');
Looking now at my old solution, I believe this can be done a bit nicer (and hopefully it will be working correctly too)
Relation:
public function likedByCurrentUser(){
return $this->belongsToMany('User', 'ideas_likes')
->where('user_id', Auth::id())
->where('liked', true);
}
And the accessor:
public function getLikedAttribute(){
return ! $this->likedByCurrentUser->isEmpty();
}
The usage stays the same as above.
Upvotes: 2