47
View All Posts By An Author in Laravel
Ep#29@Laracasts: View All Posts By An Author
This post is a part of the Week X of 100DaysOfCode Laravel Challenge series. We are building the Blog project while following the Laravel 8 from Scratch series on Laracasts with the 100DaysOfCode challenge.
In previous episodes, we added the feature to view all posts by a category. We already have set up relations between our Post
and User
models. So, we are all set to view all posts by an author too. All we need now is to add the corresponding route to our app.
-
routes/web.php
Route::get('/authors/{author}', function (User $author) {
return view('posts', [
'posts' => $author->posts
]);
});
I hope you remember from the previous lessons that the wildcard and the callback variable names should match up, author
in this case. You should also remember that the wildcard by default looks for the id
attribute of the model. So we should now get all posts by the user with id
equal to 1 using the URL http://127.0.0.1:8000/authors/1
(local).
Let's update our views to make the author clickable.
posts.blade.php
-
post.blade.php
<p>
By <a href="/authors/{{ $post->user->id}}">
{{ $post->user->name }}
</a> in <a href="/categories/{{ $post->category->slug }}">
{{ $post->category->title }}
</a>
</p>
Here you should notice one problem with our code. The problem is we call the writer of the post the author but when we fetch it from the Post
model as user
. This is a contradiction between what we speak and what we code. So, we should rename our relation from user
to author
in the Post
model, and update the views accordingly.
-
\App\Models\Post
public function author() {
return $this->belongsTo(User::class);
}
posts.blade.php
-
post.blade.php
<p>
By <a href="/authors/{{ $post->author->id}}">
{{ $post->author->name }}
</a> in <a href="/categories/{{ $post->category->slug }}">
{{ $post->category->title }}
</a>
</p>
But would it work now just by renaming the relation? The answer is no.
Because Laravel makes some assumptions based on the relation name. Now Laravel will look for the author_id
as the foreign key of the relation which is not present in the users
table. The solution is to specify the foreign key name as the second optional parameter of the belongsTo()
relationship.
-
\App\Models\Post
public function author() {
return $this->belongsTo(User::class, "user_id");
}
Now click on the author name and it should work now, listing all the posts by that author.
The author id in the URL http://127.0.0.1:8000/authors/1
is neither looking very prettier nor is it very useful. We may want to manually write the name or username of an author in the URL and filter posts by that author. So, what User
attribute should we replace the id
with? The full name doesn't look very suitable attribute. We can update it to username
instead. So update the route definition to specify the username
in the route wildcard.
-
routes/web.php
Route::get('/authors/{author:username}', function (User $author) {
return view('posts', [
'posts' => $author->posts
]);
});
But the problem is that we don't have a username
key in our users
table. So, add the line $table->string('username')->unique();
to your users
table migration file.
-
database/migrations/2014_10_12_000000_create_users_table.php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('username')->unique();
...
});
And add the line 'username' => $this->faker->unique()->username()
to the UserFactory
.
-
database/factories/UserFactory.php
public function definition()
{
return [
'name' => $this->faker->name(),
'username' => $this->faker->unique()->username(),
...
];
}
Run the Artisan command
php artisan migrate:fresh --seed
Now a username
column is added to your users
table and you can filter posts by username as http://127.0.0.1:8000/authors/JohnDoe
. Update views for blog overview and individual blog post pages to replace the id
with username
in the link.
posts.blade.php
-
post.blade.php
<p>
By <a href="/authors/{{ $post->author->username}}">
{{ $post->author->name }}
</a> in <a href="/categories/{{ $post->category->slug }}">
{{ $post->category->title }}
</a>
</p>
On the Blog overview page, the latest posts are currently appearing at the bottom. We want them to be on the top of the list. Let's review our Blog overview page route definition.
-
routes/web.php
Route::get('/', function () {
return view('posts', [
'posts' => Post::with("category")->get()
]);
});
We can use the latest()
method on the Post
model to fetch posts in the latest-first order.
-
routes/web.php
Route::get('/', function () {
return view('posts', [
'posts' => Post::latest()->with("category")->get()
]);
});
Test now and your latest posts should appear on the top. You can create a new post in tinker \App\Models\Post::factory()->create();
to verify it appears on the top.
In a previous episode we fixed the N+1
problem when we referenced the related Category
model on the blog overview page. Now we are again in the N+1
problem as we showed authors on the blog overview page.
We are looping over an array of posts and for each post, we are Lazy Loading its author. The solution is to Eager Load or include the author with the posts in our route definition.
-
routes/web.php
Route::get('/', function () {
return view('posts', [
'posts' => Post::latest()->with("category", "author")->get()
]);
});
We already had added the "category"
parameter in the with()
method, now we added the "author"
too to eager load it with the posts. Visit blog overview page http://127.0.0.1:8000/
again and notice the number of queries in the Clockwork tab this time.
Yeah, we fixed it again.
Thank you for following along with me. Stay tuned for the next article. Comments and suggestions are always appreciated and welcome.
47