Testing paginated comments0:01
So, with all the legwork that we've put in, building components and testing helpers and all the rest of it, should we see how straightforward it now is to add paginated comments to the post show page? Let's go. As always we're going to start this process with a test, so I'm going to jump into our show test where we have, currently, it passes a post to the Vue, but we want to say that it also passes comments to the Vue, so it passes comments to the Vue as well, and we'll need a post that has comments attached to it. Now, how do you do that? Well, there are numerous ways. One would be to create a CommentFactory here, so CommentFactory, and you could create 10 comments, say, and call the create method there, but you'll need to link them to this specific post, so you can use the for method in order to make that link.
could create 10 comments, say, and call the create method there, but you'll need to link them to this specific post, so you can use the for method in order to make that link like so. The other way would be to attach them directly to the post, so you could say hasMany comments 10, and that would create 10 comments directly on this post and handle attaching it for you. Seeing as we're going to actually want these comments available for use, I'm going to do it this way. So, I'll do it back to front, if that makes sense, and then I can get rid of this here. So, we have 10 comments for this post, and 10 comments is overkill. I think we can just create two or three comments. Again, keep the data in your tests to a minimum. We don't need to load any relationships because we're not going to be checking for that, and
think we can just create two or three Comments. Again, keep the data in your tests to a minimum. We don't need to load any relationships because we're not going to be checking for that, and then we'll want to assert that we have a resource, but of course, it's paginated, so assert hasPaginatedResource, comments, and then we'll want the Comment resource. We'll use the collection method, and then we'll pass in those comments like so. Okay, let's see if this works. Obviously, it doesn't. That's exactly what we'd expect. Let's write the implementation until this test passes. The first issue we need to tackle is the fact that we haven't actually even added a comments property to the controller. So, let's jump into the PostController, and inside that PostController under the show method, yeah, here's our post. We'll also add comments, and let's do the Comment resource.
Passing comments from controller2:04
So, let's jump into the PostController, and inside that PostController under the show method, yeah, here's our post. We'll also add comments, and let's do the Comment resource collection. We want the post. We're going to grab the comments in that post, but we want to paginate them, so I'll use this rather than a property accessor. The method accessor will give you a query builder, which we can then build on. So, I'm grabbing the comments, and for now, I'm literally just going to call paginate, and maybe we'll have, I don't know, 10 comments per page. I think that makes a lot of sense. Okay, let's go back to our test and run it, and it's still failing. This time, it's saying it's not a valid inertia response. So, something's happening in the meantime that is throwing an exception, but we're not able to see that exception. At the top of the test, I'm going
Fixing lazy-loading in resource2:45
it's not a valid Inertia response. So, something's happening in the meantime that is throwing an exception, but we're not able to see that exception. At the top of the test, I'm going to add a line, this without exception handling. When you do that, Laravel isn't going to get in the way during the test. Ah, here we go. We have a lazy loading violation. So, this is the precaution we put in earlier. It's telling us that we're attempting to load something that we probably didn't mean to. Something's going on here that we're not quite sure about. Let's take a look at what it might be. So, we have the comments loaded here. If I dump and die comments, I'm pretty sure that will work. Yeah, that works fine. So, the lazy loading exception doesn't happen before then. Let's go into the PostController and take a look. We load the User. We're loading the post's comments and paginating them. Hmm,
loading exception doesn't happen before then. Let's go into the PostController and take a look. We load the User. We're loading the post's comments and paginating them. Hmm, let me dump and die this here and see what happens when we run the test again. Okay, that gives us a length of web paginator. So, I wouldn't imagine that that would cause a problem either. Where's this problem actually coming from? Can you see this? CommentResource. Ah, okay, yeah. So, we need to go back to our resources here because our CommentResource, we haven't actually updated with that logic like we did in the PostResource. So, in our PostResource, we use this whenLoaded trick in order to only load the relationships under certain circumstances. We want to do the same on the CommentResource. So, let's say this whenLoaded, I'm going to pass in the correct relationship. So, one will be User, the other
Rendering comments in Vue4:16
certain circumstances. We want to do the same on the Comment resource. So, let's say this when loaded, I'm going to pass in the correct relationship. So, one will be User, the other will be Post, and then obviously we can use a shorthand closure in order to correctly load that in. And we'll do the same here, right? So, closure and then lock that in at the end there. Okay, go back to our show test, run this again, and now we have a passing test, which means we are correctly passing down comments to Inertia. Let's see if we can make use of that now from the front end. So, in our show view component, we need to add the comments prop that is now being passed, and we'll want to output these comments. Perhaps I'll have a div here. We'll have a H2 that just tells us this is the comments section. We'll style this shortly. For now, it's fine. Let's have an unordered list, and we'll want
I'll have a div here. We'll have a H2 that just tells us this is the comments section. We'll style this shortly. For now, it's fine. Let's have an unordered list, and we'll want a list item, and we'll have a v-for. In fact, let's speed this up a little by going to the post index, and I'm going to more or less copy this code here, and we will come back and alter this in just a moment. But, yeah, I'm going to drop that in. Obviously, we're not looking for post anymore, so let's go ahead and alter any instance of post to actually be comment. So, v-for comment in comments.data. Yeah, we can key by the comment ID. We're not going to have a link on this because there is no show route for a comment. So, for now, let's take this class here, and we'll simply apply it to the list item, and then we'll remove the wrapping link. We obviously have the comment body, and we also want to
now, let's take this class here, and we'll simply apply it to the list item, and then we'll remove the wrapping link. We obviously have the comment body, and we also want to output who created the comment. So, in that case, we're going to need to load the correct relationships for the user who wrote each comment. For now, I'll just say by Joe Bloggs until we can come back and alter that in a moment or two. Okay, so hopefully, that will output all the comments correctly. Let's see what happens from the front end. Here we go. I have comments. Apparently, there are no comments on this blog post. Let's see if there are comments on some of the later blog posts. Yeah, here we go. So, here we have two comments for this particular post. Let's go ahead and style this up a bit so we can kind of see how it should be working. I'm going to get rid of group, and I'm going
have two comments for this particular post. Let's go ahead and style this up a bit so we can kind of see how it should be working. I'm going to get rid of group, and I'm going to get rid of the group-hover because you cannot click on any of these things, and we don't need block anymore on that, so we can get rid of that, too. Let's add some margin to the top here, maybe mt-12, and for comments, I'm going to say class, and we'll say text-xl. No, text-excel, and we'll say font-semibold. How does that look? Yeah, that's pretty nice. The actual body is way too large, so I can actually say text-sm, maybe even here, and get rid of the bold on it as well. Okay, that's looking good, and we'll just add a little bit of spacing above here, maybe mt-4. Nice, and we need our paginator right as well, so we'll need to drop the paginator in down here. So, pagination, and we can pass
Eager-loading comment authors7:14
add a little bit of spacing above here, maybe mt-4. Nice, and we need our paginator right as well, so we'll need to drop the paginator in down here. So, pagination, and we can pass in meta, which is going to be equal to comments.meta, like so. Okay, and there we go. We can see the previous and next page, but there is only one page of comments in this case, and obviously, we set the paginator to five, so that's exactly what we'd expect. Nice. Let's now tackle eagerLoading the User relationship on the comments so that we can add the author underneath as we have done for our posts, and again, I'm going to have to jump into the test here. It passes comments to the view, and I should simply be able to call comments->load('user') in order to access that relationship instance, and now when I run the test, it fails because obviously our test is passing the User through, but the controller is not. So, let's go fix
to access that relationship instance, and now when I run the test, it fails because obviously our test is passing the User through, but the controller is not. So, let's go fix that. I'll jump back into that PostsController, and here we have comments. We are loading the comments relationship, and this is where I would say with the user relationship as well, which should make this pass, and it does. Very nice. So, because that's now available, we can alter this to actually access the user property. So, I could say by comment.user.name, and if we refresh the page, there we go. Now we have the real name of the author of each comment coming through. It would be good to also add the relative date that the comment was posted on, and obviously we're doing that in three places now. So, we've already done that up here with this formattedDate. So, this logic here we're now repeating quite
Extracting relative date utility8:48
was posted on, and obviously we're doing that in three places now. So, we've already done that up here with this formatted date. So, this logic here we're now repeating quite a few times. I think it's about time that we extract this to something that's a bit more reusable across our code base. I tend to put these things in my JS directory under a utilities folder. So, I'll create a new directory called utilities, and in there, well, this is handling dates. So, let's call this date.js. Okay? Now, we're going to use the same logic, and we'll create a const called relativeDate, and relativeDate is basically going to take the date you want to be relative to, and then it's going to perform, well, the exact same logic that we had in our code from earlier. So, we format the distance, and we pass the ISO of whatever date you've passed. So, here, we just pass in the date.
the exact same logic that we had in our code from earlier. So, we format the distance, and we pass the ISO of whatever date you've passed. So, here, we just pass in the date. Then we need to export this. So, I'll export an object, and that object is going to export as one of its properties the relativeDate method or function. So, we can call that from anywhere simply by importing it in our view files. So, let's go ahead and refactor, first of all, this here. So, rather than calling all of these things, I should instead simply be able to call relativeDate passing in props.post.createdAt, and that should have the exact same effect on the front end. Let's test. Oh, it looks like we have a little bit of an issue. Two arguments required, but only one present. What am I missing here? Let's jump back into relativeDate. Okay, I've formatted the distance past the ISO,
of an issue. Two arguments required, but only one present. What am I missing here? Let's jump back into relativeDate. Okay, I've formatted the distance past the ISO, but I need to pass in a new date as well in order for that to work. I think I made that mistake last time. There we go. So, now two days ago by Jermaine Gibson, and we can call that very easily anywhere in our code. So, let's also update the index at the same time. Jump into PostIndex, and we have that formatted date method that we were making use of, but we should actually now be able to replace all of this with relativeDate passing in the post.createdAt timestamp. And I'll tell you what, at the same time, why don't we inline this, make it a one line method like so, and then refresh. Cool, that's working as well. If we go back into a Post, and as I said before, we'll need to actually go into a higher
this, make it a one line method like so, and then refresh. Cool, that's working as well. If we go back into a Post, and as I said before, we'll need to actually go into a higher page number than that. There we go. Here are our comments. Let's now add the relative date to the comments as well. So, in our show page, we can come up here and where we say by comment.user.name, I can simply output the relative date of comment.createdAt. Let's see if that works. And there we go. By Jermaine Gibson two days ago, by Luke Downing two days. Oh, it looks like we need to add the ago as we did before. There we go. So, by Jermaine Gibson two days ago, by Luke Downing two days ago. Awesome. So, that's the basics of comments. I think we need to add more comments to this list so that we can actually see if this is working properly. Let's go to the database seeder and edit how our seeding works.
Seeding more comments11:44
of comments. I think we need to add more comments to this list so that we can actually see if this is working properly. Let's go to the database seeder and edit how our seeding works. I think we could just slightly refactor this line to actually be called inside posts instead. So, here we can say that it has a number of comments and, of course, we can grab the comments here and we'll be recycling the users. So, each comment or each post could have, I don't know, let's say 15 comments, recycling the users from up here. And if I run php artisan migrate --fresh --seed, like so, we'll recreate the database, reseed the database. And now if we refresh this page, yeah, we can have as many comments as required. So, one, two, three, four, five, six, seven, eight, nine, ten. And then we should have on the next page five more. You can see without any additional work, because we built that pagination component,
Ordering comments latest-first12:30
three, four, five, six, seven, eight, nine, ten. And then we should have on the next page five more. You can see without any additional work, because we built that pagination component, this just works. It works immediately. We're able to easily switch between pages of comments. We should also give consideration to the order of these comments. If I go back to the show test, currently we're just showing the comments in any order. But again, we should likely be showing these in reverse order because the latest comment should be the first thing displayed at the top of the page, at least for now. It might be that we switch the order around later on, and maybe you even have buttons to be able to control the order. But for now, they should always show in reverse order. So, latest first. That causes our test to pass. So, let's go in and update our PostController to make it work again. So, we can
they should always show in reverse order. So, latest first. That causes our test to pass. So, let's go in and update our PostController to make it work again. So, we can tag latest onto here, and we can also tag latestId so that it orders by created_at first, and then the ID descending. So, that should make our test pass, which it does wonderful. And if we refresh again, yeah, we won't actually see any difference here because all of the seeded posts have two minutes ago as their posting time, but essentially it is working. Now, one other piece of data that we actually have available, if I jump to view here, and we just take a look under show data and user is profile_photo_url. So, we can actually output their profile photo next to the comment, which I think would be a really nice touch. But I don't want to design this myself. My design skills are terrible. So,
Creating reusable comment component14:02
actually output their profile photo next to the comment, which I think would be a really nice touch. But I don't want to design this myself. My design skills are terrible. So, I'm actually going to turn to Tailwind UI for this, and they have this basic responsive media object that will work perfectly. I'm going to copy the view component version of this because what I actually want to do is compartmentalize this as a component that can be reused across as many pages as I'd like. So, we'll come up into the component section here, and let's call this comment.view. So, in comment.view, we can paste the code from Tailwind UI, and then we should just be able to update this slightly to have the syntax that we desire. Down at the bottom, I'll add the script tag with the little setup prop, and then we'll define the properties passing in the comment that we want to display
syntax that we desire. Down at the bottom, I'll add the script tag with the little setup prop, and then we'll define the properties passing in the comment that we want to display and render. Now, we don't need this H4 because a comment is simpler than that, and obviously all of the content of a comment, well, that's going to go inside here, and it will simply be the comment.body, like so. This is where the profile photo URL will go. So, we'll need to handle that next. Perhaps we could do this as an image. So, this is an image, and the source is going to be the comment.user.profilePhotoURL, like that. And we'll want to add the correct width and height, so I'll say height 16 and width 16, and let's say rounded full as well, at least for now, and we can change that if the need arises in just a moment. We can get rid of this SVG, and then finally, well, we'll want to actually show that little piece of
at least for now, and we can change that if the need arises in just a moment. We can get rid of this SVG, and then finally, well, we'll want to actually show that little piece of text at the bottom, as we did before, so I'll add that here, and we can style it again in just a moment. Okay, let's make use of this new component in our existing code. So, inside this list item, I'm going to bring in the Comment component, and we have to pass the comment in, like so. That should allow me to get rid of these two spans, and once I've done that, well, let's see if it works. Back in our code, we'll refresh, and here we are. Here are our comments outputting the profile photo URL along with the text and the posting date. I might actually make this text just a little bit smaller. It doesn't actually need to be that large, so let's go ahead and say that this should be text-extra-small.
date. I might actually make this text just a little bit smaller. It doesn't actually need to be that large, so let's go ahead and say that this should be text-xs. Yeah, that's better, and I don't think the profilePhotoUrl needs to be that big either. Maybe we could reduce that to 12, or maybe even 10. Yeah, that's a little bit better, isn't it? It doesn't need to take up quite as much room as it was doing before, but other than that, that's really nice. That looks great, and it'll be nice if people have uploaded custom photos that we'd actually be able to see that in the interface as well. It makes it a little bit more personal and nicer to look at. So, thanks to all the groundwork we put in with Posts, adding comments to the postShow page was actually very simple, and it's fast, it's performant, and it works well, but there
Next: Inertia performance trick17:00
So, thanks to all the groundwork we put in with Post, adding comments to the post show page was actually very simple, and it's fast, it's performant, and it works well, but there is a little trick we can do with Inertia that will just eek out a little bit more performance, especially as our application starts to expand, and seeing as it's easy to add, we might as well do it now rather than later. We'll discuss that trick in the next episode.
