Retrieve all posts and their most recent comments in 1 query - php

I'm trying to retrieve a (paginated) list of posts and include their most recent comments in one Eloquent query statement. Posts and comments are connected by a one to many relationship.
This was my attempt:
Post::with(['comments' => function ($query) {
$query->orderBy('created_at', 'asc')->take(3);
}])->simplePaginate(15);
But that only retrieves 3 comments in total, not 3 comments per post.

Joined table Id is necessary
Try this:
Post::with(array('comments'=>function($query){
$query->select('id as commentid','comment')->orderBy('created_at', 'asc')->take(3);
}))->simplePaginate(15);

UPDATED
It seems you can not do it with 1 query.
You still can have this function in the parent model, called "lastComments":
function lastComments()
{
return $this->hasMany('App\Post', 'post_id')->orderBy('created_at')->take(3);
}
You load posts with normal eloquent code:
Post::simplePaginate(15);
And in the view page you show the comments in this way:
#foreach($post->lastComments as $comment)
<!-- ... -->
#endforeach

You need to set the $with variable in your App\Post model:
protected $with = ['lastComments'];
This way whenever you fetch a post, Laravel will automatically load the comments for it.

Related

Get comment sub-comments when only one sub-comment

I have comments table where has parent_id
This is Comment table sub_comments relation.
public function sub_comments()
{
return $this->hasMany(self::class, 'parent_id');
}
This code return all comments with related all sub-comments
Comment::with('sub_comments')->get();
But I want to get all comments also sub-comments when sub-comments is single. That mean if comment have 2 or more comments for that comment I did not want get that sub-comments.
Now I use this code
$oneSubcommentCommentIds = Comment::has('sub_comments', '=', 1)->pluck('id');
Comment::with([
'sub_comments' => function ($q) use ($oneSubcommentCommentIds) {
$q->whereIn('parent_id', $oneSubcommentCommentIds);
}
])->get();
but this make one additional query.
Try this:
Comment::with('sub_comments')->has('sub_comments', '=', 1)->get();
Update
Your question wasn't clear, I can't imagine another way to doing this without previosly loaded the relationship or the count of the relationship.. so I'd do this:
// First get all your comments with an aditional count field
$comments = Comments::withCount('sub_comments')->get();
// separate the ones with just one sub_comment from the rest
list($oneSubComment, $theRest) = $collection->partition(function ($comment) {
return $comment->sub_comments_count == 1;
});
// Then load the relationship on just the selected elements
$oneSubComment->load('sub_comments');
// re-join the collection
$comments = $oneSubComment->union($theRest);
What am I doing here?
Adding an additional field to each $comment with the relationship count (it should be something like sub_comments_count)
Partition the resulting collection in two parts: the ones with one comment and the rest. Using the partition() method.
Lazy eager loading the collection.
Re-joining the two collections using the union() method.

Eloquent - access parent value in nested function

I'm trying to access the parent value, the posts date, in a nested function.
In my application, a user has many posts, with each post being associated to a product (textbook). Each time someone views a page with the product, a new row is added to the product-views table.
I want to return the cumulative amount of times the users products have been seen. I've been able to get all the users posts, then the associated product, and then the count of all the views of that product.
Now I'd like to add another where() condition to only return views that have occured after the post was created. To do so, I need to get the posts date, e.g. views->product->post, while constructing the query like user->posts->product->views.
// getting all of the users available posts
$user = $request->user()->posts()->available()->with([
// with the textbook (product) associated with the post
'textbook' => function ($q) {
// get the count of textbook-page views
return $q->withCount([
// from the related table views
'views' => function ($q) {
// ===!!! Q !!!=== How do I access the posts (parent (grandparent?)) date, so that I only select the rows that have been viewed after the post was created ===!!!===
->where('date-viewed', '>=', 'posts.date');
},
]);
//some cleanup
}])->distinct()->get()->unique('isbn')->pluck('textbook.views_count')->sum();
How do I go backwards in a nested function to access the posts date?
It looks like as Jonas said in the comments, each with() relationship was a new query, so I ended up creating a hasManyThrough() relationship between the posts and views through the product.
// getting all of the users available posts
$user = $request->user()->posts()->available()->withCount([
'views' => function ($q) {
// ===!!! A !!!=== Fixed ===!!!===
->whereRaw('`date-viewed` >= posts.date');
},
])->distinct()->get()->unique('isbn')->pluck('views_count')->sum();

Eloquent HasMany relationship, with limited record count

I want to limit related records from
$categories = Category::with('exams')->get();
this will get me exams from all categories but what i would like is to get 5 exams from one category and for each category.
Category Model
public function Exams() {
return $this->hasMany('Exam');
}
Exam Model
public function category () {
return $this->belongsTo('Category');
}
I have tried couple of things but couldnt get it to work
First what i found is something like this
$categories = Category::with(['exams' => function($exams){
$exams->limit(5);
}])->get();
But the problem with this is it will only get me 5 records from all categories. Also i have tried to add limit to Category model
public function Exams() {
return $this->hasMany('Exam')->limit(5);
}
But this doesnt do anything and returns as tough it didnt have limit 5.
So is there a way i could do this with Eloquent or should i simply load everything (would like to pass on that) and use break with foreach?
There is no way to do this using Eloquent's eager loading. The options you have are:
Fetch categories with all examps and take only 5 exams for each of them:
$categories = Category::with('exams')->get()->map(function($category) {
$category->exams = $category->exams->take(5);
return $category;
});
It should be ok, as long as you do not have too much exam data in your database - "too much" will vary between projects, just best try and see if it's fast enough for you.
Fetch only categories and then fetch 5 exams for each of them with $category->exams. This will result in more queries being executed - one additional query per fetched category.
I just insert small logic inside it which is working for me.
$categories = Category::with('exams');
Step 1: I count the records which are coming in response
$totalRecordCount = $categories->count()
Step 2: Pass total count inside the with function
$categories->with([
'exams' => function($query) use($totalRecordCount){
$query->take(5*$totalRecordCount);
}
])
Step 3: Now you can retrieve the result as per requirement
$categories->get();

How do I take related posts (by category) with Eloquent?

I have a question on how to fetch the related posts of a particular post by category using Eloquent. I know how to do it in pure MySQL but am sure Eloquent will have nicer alternative to it.
My tables are: posts categories post_category (pivot)
I have made the neccessary Eloquent connections, so I want to do something like: $post->categories()->posts()->exclude($post)->get().
Of course this won't work. I get an error on posts() because "Builder doesn't have a method posts()", but hopefully you get the idea. How would you do it with Eloquent?
It's hard to say what you want to achieve, bot probably you want to get:
Posts::whereIn('id', $post->categories()->lists('id'))->whereNot('id',$post->id)->get();
One of the confusing parts about Eloquent's relations is that the method on a model that defines the relation will bring back a relation object when called like you're calling it:
$posts->categories();
To return a collection of category models attached to your posts you should use something like this:
Post::find(primary key of post)->categories;
Or get all posts and iterate through the models individually:
$posts = Post::all();
foreach($posts as $post) {
$post->categories;
}
Here's a resource I found very helpful in learning to use Eloquent's relation methods: http://codeplanet.io/laravel-model-relationships-pt-1/
I was trying to get my related posts by category and searched on google and got here.
I made this, and it worked fine.
public function getSingle($slug){
$post = Post::where('slug', '=', $slug)->first();
$tags=Tag::all();
$categories=Category::all();
$related= Post::where('category_id', '=', $post->category->id)
->where('id', '!=', $post->id)
->get();
return view('blog.show')
->withPost($post)
->withTags($tags)
->withCategories($categories)
->withRelated($related);
}
In my view('blog.show')
$post->title
$post->content
//related posts
#foreach($related as $posts)
$posts->title
$posts->category->name
#endforeach
I don't know if this is the right way, but it works for me. I hope this helps someone
Why don’t you define a ‘relatedposts’ relation where search for posts with the same category id?
Then you can simply do $post->relatedposts...
You’re making it overcomplicated imo...
if you have multiple category (Pivot ex:RelPortfolioCategory)
Portfolio Model:
public function getCats(){
return $this->hasMany(RelPortfolioCategory::class,'portfolioID','id');
}
controller:
public function portfolioDetail($slug){
$db = Portfolio::where('slug' , $slug)->with('getCats')->firstOrFail();
$dbRelated = RelPortfolioCategory::whereIn('categoryID' , $db->getCats->pluck('categoryID'))->whereNot('portfolioID' , $db->id)
->with('getPortfolioDetail')->get();
return view('portfolioDetail' , compact('db' , 'dbRelated'));
}

How to get a list of posts and check if the user already "Liked" them? (Check existing relation between two models)

I'm having fun with Laravel framework trying to build a little "blog" site that works the following way:
When you enter the webpage, a cached index of the latest / most popular posts is shown
There is a ManyToMany Relationship "user_likes" between the User and Post models, so that if you're logged in then you can "Like" or "upvote" each of these posts with a "heart" button, so that a relation between the two models will be created in the user_likes pivot table.
I have two database tables
users table:
user_id name
1 TheUser
posts table:
post_id title
1 MyPost
2 Another Post
3 Yet another post
user_likes pivot table:
post_id user_id
1 1
3 1
And the User Model Class:
class User extends BaseUser {
protected $table = 'users';
public function likes()
{
return $this->belongsToMany('Post', 'user_likes','user_id','post_id');
}
}
The question is, what's an efficient way of retrieving a list of Post models and checking if the logged in User has already liked / upvoted them?
The thing is that i'd like to check for every Post if the relation between the logged in User and the Post model exists in the most efficient way.
Thanks in advance!
EDIT:
This SQL Fiddle would be a way of achieving what I'd like to do, the problem is that If i'm not wrong, I won't be able to cache the posts, as I'll need to check them every time a new user logs in.
http://www.sqlfiddle.com/#!2/e5bab/2
Using Laravel's Query builder:
DB::table('posts')
->leftJoin('user_likes', function($join) {
$join->on('user_likes.post_id', '=', 'posts.id')
->where('user_likes.user_id', '=', 1);
})
->select('posts.id as post_id', 'posts.title', 'user_likes as did_i_like')
->groupBy('posts.id')
->orderBy('posts.id');
Right now, I'm doing it in my application in a different way:
I have the paginated posts cached, and then each User has a cached collection of the posts ids that they liked, so when they log in, I'll check if each post is in the user's liked post collection:
$liked = Auth::user()->getLikedPosts(); // returns a cached collection of the User liked posts ids
$posts = Post::popular(1); // returns a cached and paginated index of the most popular posts.
Then, I'll check if the liked collection contains the post id:
foreach($posts as $post)
{
.......
if($liked->contains($post->id)
{
// show red Heart
}
else
{
// show gray heart
}
.......
I think It's not a good way of doing it, since the $liked collection will be growing a lot... However, with the new Query, I really don't know how could I cache the posts, so that I don't have to query the Database each time a user logs in or navigates throught the posts pages.
Thanks in advance!
I am honestly not sure if it's good or not, but I did it like this:
Post Model:
public function likes()
{
return $this->hasMany('Like');
}
public function likedByUser()
{
return $this->likes()->where('user_id', Auth::user()->id);
}
than in blades:
#if(isset($post->likedByUser[0]))
style="background-position: right;" data-value="liked"
#endif
I don't much about speed, but I'm paginating 9-12 each time, so It's not that bad, I hope :)
What's an efficient way of retrieving a list of Post models and
checking if the logged in User has already liked / upvoted them?
in your Post model declare a relationship:
public function likedByUser()
{
return $this->belongsToMany('User', 'user_likes', 'post_id', 'user_id')
->where('user_id', Auth::user()->id);
}
Then retrieve posts which has been liked by the logged in user like this:
$posts = Post::has('likedByUser')->get();
Update:
$posts = Post::get();
$firstPost = $posts->first();
$firstPost->has('likedByUser');
Also you may loop and check:
$posts = Post::get();
foreach($posts as $post) {
if($post->has('likedByUser')) {
// liked it
}
}

Categories