تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Introducing Post Topics0:00

Going back to our post index, it is looking so much better than it was before, but it is still a little bit samey samey, and that's because everything is being dropped into a single topic, a single category. We have no distinction between, say, a movie review versus a question about a movie, or a conspiracy theory about a movie versus fan fiction about a sequel. So we could do with a way to separate all of these items up into topics that a user can select from when creating a post. Let's tackle that in the next few episodes. Thinking about it, this is nothing more than a model. So let's go to the terminal and type php artisan make:model.

Creating Topic Model0:41

Thinking about it, this is nothing more than a model. So let's go to the terminal and type php artisan make:model Topic --factory --migration. We want a factory. Of course, we want a migration as well. The difference with this model from models that we've done previously is that it's not going to be a CRUD resource. A User does not create a Topic. The topics are predefined by us, the administrators. And at the moment, that's a one-man team consisting of a developer.

Building Topic Migration1:02

The topics are predefined by us, the administrators. And at the moment, that's a one-man team consisting of a developer. So we don't have an admin backend. We don't have some form of control panel. We need to do this the programmatic way. And the easiest way to do that is using a database seeder. So we'll ask for a seeder as well, hit enter, and our files have been created. Let's start by diving into the migration and adding the necessary columns. So we'll say table. I want a string for the name.

So we'll say table. I want a string for the name. I also want a description. So we can say table longText and we'll say description. Of course, it's important to explain to the user briefly why a topic actually exists. And we're going to use the topics to separate different routes or routes if you're from the states in our application. So we should probably have a slug that we can use to make that pretty. So we'll say table string slug. And of course, that will need to be unique so that we can uniquely identify one topic.

So we'll say table string slug. And of course, that will need to be unique so that we can uniquely identify one topic from another based on the slug. And this is the first time we have encountered the order of migration problem in the application. If you take a look at our migrations directory, we have our posts table here and our topics table underneath. And the issue is that the posts table actually needs a column adding to it, which will be a foreign relationship to a topic because a post belongs to a topic. And now there are a couple of ways to tackle this. If you have not yet published the application, then you can change the name of the file here.

And now there are a couple of ways to tackle this. If you have not yet published the application, then you can change the name of the file here so that the date is before the posts table, and that will work absolutely fine. Then you go in, edit the posts table, add the topic foreign ID, you're off to the races. I'm actually going to do it the other way. Not that we've published this application at the moment, but it's good practice. So we'll stay in the topics migration. And underneath this schema declaration, I'm going to use Schema again to edit the existing posts table. I'll add a closure.

posts table. I'll add a closure. I'm going to grab the table blueprint. And then obviously underneath, I can say on the table, I need to add a foreign ID for the Topic model. There we go. Let's constrain it. And simply to ensure that we've stated what our intent is, I'm going to restrict underlead. Especially since this is not a CRUD resource, what we don't want is as administrators to dive into this project, start clicking around and accidentally delete a topic along with

Especially since this is not a CRUD resource, what we don't want is as administrators to dive into this project, start clicking around and accidentally delete a topic along with all the posts that that topic belongs to. So restrictUnderlead is definitely what we want here. This isn't super important, but if you take a look at the posts table, we actually define our foreign ID for a User at the top of the table. And it would be nice if in the database, the topic_id appeared here, rather than the default, which would be down here. It's very easy to do that using the after method. So I can say after and say, well, this column should appear after the user_id column on.

It's very easy to do that using the after method. So I can say after and say, well, this column should appear after the user_id column on the database. This is also the first time we've done something unusual, or rather that we've done something that isn't creating a table in one of our migrations. So we need to give thought to the down method. There are various opinions on the down method in migrations, and none of them are wrong. One opinion is that you don't even need it. Just get rid of it. In fact, a lot of developers will publish the stubs for migrations and they will just

Just get rid of it. In fact, a lot of developers will publish the stubs for migrations and they will just remove the down method entirely. I'm not going to do that. The important thing to remember for a down method to work is that you have to do it in reverse order. So whatever you do in the up method, you do backwards in the down method. In this case, that means basically dropping this foreign id before dropping the topics table. So let's come in here and we'll say Schema::table again.

table. So let's come in here and we'll say schema table again. I want the posts table. It's going to give me a blueprint as before, and I can call a method on that table, which is dropForeignIdFor, and I can pass in the Topic model. So long as you followed Laravel conventions and you've not tried to be clever with renaming the index, with renaming the foreign ID, with renaming the column, Laravel can do all of the heavy lifting for you with this one method call. Just one more reason why you should stick to the defaults. If we go back to the terminal and run php artisan migrate:fresh --seed, I think we're going to

Fixing Factories for Seeding5:24

Just one more reason why you should stick to the defaults. If we go back to the terminal and run php artisan migrate --fresh --seed, I think we're going to hit an error. Yes, we do. And if we go to the top here, the error is probably going to be the field topic_id doesn't have a default value. So we're trying to seed our database, but we've not actually stated how a Topic is created for the seeder. Let's go to the TopicFactory and fix that. We have three columns to worry about.

Let's go to the topic factory and fix that. We have three columns to worry about. The first is slug, and I'm going to use the unique modifier here, which basically informs the Faker library that every value it generates has to be unique. It can't have been used before. Of course, that's very important when you're dealing with a unique column. Then we have the name. Well, for this scene, as it's only going to be used in tests, I'm just going to use a fake word. And then for the description, I'll use a fake sentence.

fake word. And then for the description, I'll use a fake sentence. Once we have the factory set up, we can jump into our PostFactory. And under user_id, we can introduce our topic_id, which is basically just going to be a TopicFactory instance. And now I imagine if we go back to the terminal and rerun php artisan migrate:fresh --seed, the seeding should work without issue. Looking quickly in TablePlus, yep, here's our new topics table, but you'll see even in our local environment, it has been filled with fake topics. And as we've already stated, we want real topics that will be preceded into our environments.

Seeding Real Topics6:43

in our local environment, it has been filled with fake topics. And as we've already stated, we want real topics that will be preceded into our environments. The fake topics should only be used for tests. To fix that, let's go to our IDE and we'll find that TopicSeeder that we created, which is where we're going to actually set up the data that will be persisted for our real topics. We'll say data equals, and it's going to be an array of associative arrays that contain all of the necessary information. I'll tell you what, to save you some time, I'm going to go ahead and use the magic of editing to fill this out for you. And you're back.

editing to fill this out for you. And you're back. So I've gone ahead, created the general topic. I've got the Reviews topic, the Questions topic, and the Conspiracies topic. Of course, with this data defined, we need to actually insert it. And there's a really cool method on Eloquent that we can use here. So I need to grab the topic and I'm going to use the upsert method, which is a mixture of inserting data whilst also updating existing data. So enter the data here. And the second parameter is the unique columns that we use to decide whether it's existing.

So enter the data here. And the second parameter is the unique columns that we use to decide whether it's existing data or whether it's new data. And in our case, because we have that slug, that is what we're going to use. So let me briefly explain how upsert works. When it loops through the data that we provide, it's going to take each entry and say, well, is there already a Topic with the slug of general? If there is, I'm not actually going to insert a new entry into the database. In fact, I'm just going to update the existing entry with whatever data you've provided. But let's say it comes to the reviews slug and it realizes that no, there is no existing

In fact, I'm just going to update the existing entry with whatever data you've provided. But let's say it comes to the reviews slug and it realizes that no, there is no existing topic called reviews. In that case, it will go and insert this as a new topic in our database. So that means essentially, no matter how many times we run this cda, it will still only include the topics that we actually describe inside this cda. Super useful, perhaps for adding this to a deployment script where we can just forget about it until we want to add a new topic down the line. Why don't we execute the cda? So php artisan db:cd, and we can provide an argument, which is the name of the cda we

Why don't we execute the cda? So php artisan db:cd, and we can provide an argument, which is the name of the cda we want to run. In this case, the topic cda, you can see that it's cda the database. Let's jump back into TablePlus, and we're still going to have all of these fake topics. We'll fix that in a moment. But you can see here are our new topics. And if I go back and rerun this again, and again, and again, and again, and we refresh the table, there are still only those four topics. Isn't that cool?

the table, there are still only those four topics. Isn't that cool? I'll tell you what, I have actually forgotten one very important topic. Let me drop it in here. Fanfiction. So if you've got a great idea about a sequel, you should absolutely go ahead and write it up so that everybody can comment on it. And once we add that to our data array, well, it's a simple matter of going and rerunning php artisan db:seed. And as you can imagine, we now have that new fanfiction topic, but we only have a single

php artisan db:seed. And as you can imagine, we now have that new fanfiction topic, but we only have a single instance of all of the other topics. upsert is so cool, so useful to know about. Keep it in mind. Let's go ahead and fix the existing database cda so that we're not creating all of those fake topics at the same time. In order to do this, I want to first of all execute that topic cda. So we can use call for this, this call, and we pass in the fully qualified class name of the cda we want to run.

So we can use call for this, this call, and we pass in the fully qualified class name of the Cda we want to run. So now our database Cda is first of all going to create all of our topics using the TopicCda. And then I can grab those topics using a simple Eloquent Topic::all. Once I have those in place and assigned to a variable, I can start to use them. So where we have the posts here, I want to recycle users, but I'm also going to recycle topics to ensure that no new topics are being created, only the existing ones from our TopicCda. And well, I'll need to do the same here as well.

cda. And well, I'll need to do the same here as well. So let's use recycle. And of course, I'll pass topics in again. Okay, I think that's everything. Let's jump back to the terminal php artisan migrate --fresh --seed. You can see it's executed our topic cda as expected. Let's go into TablePlus refresh the topics table. And now we have just five topics instead of all those fake topics as well. With our migration and data defined, we can now jump into our Post model because we'll

Adding Topic Resources11:09

And now we have just five topics instead of all those fake topics as well. With our migration and data defined, we can now jump into our Post model because we'll need to define the belongsTo relationship. So obviously we have a belongsTo topic to set up, which points to the topic fully qualified class name. Once we're using Inertia and view, and we decided on using resources, we'll need obviously our TopicResource. And we'll also need to be able to output that topic in our PostResource. So let's go ahead and from the terminal run php artisan make:resource TopicResource.

So let's go ahead and from the terminal run php artisan make:resource, we'll call it the TopicResource. And once we have that, we'll access it from our IDE. And we'll fill it out correctly. So in toArray here, what columns do we need, we need an id column, which will point to this id, we want to output our slug, that will be this slug. And then the name, this name, and finally, that description that we talked about, which will point to this description. Okay, so that's set up, finally, we'll need our PostResource updated to also be able to output the related Topic.

Okay, so that's set up, finally, we'll need our Post resource updated to also be able to output the related Topic. We can duplicate this User column here, and we'll just update references to User with Topic instead. Why don't we test this out by jumping into our index test? Because now really, when it passes Post to the view, yeah, it should pass the User information, but it should also be passing in the Topic information. So we'll load in the Topic as well. And then this is going to basically check that the Topic actually exists in the output. And when I run this test, I would expect it to fail because it fails a certain that two

And then this is going to basically check that the topic actually exists in the output. And when I run this test, I would expect it to fail because it fails a certain that two arrays are equal, and the missing key is topic exactly as we'd expect. We can fix that very easily by jumping into the PostController. And in the PostController, let's find our index method. And where we load the posts here using with, well, I'll just include the topic as well. Rerun the test. And now it passes. And I imagine if we go to our browser, let's open up view dev tools. Here we have our posts.

And I imagine if we go to our browser, let's open up view dev tools. Here we have our posts. Take a look at one of these posts. And yep, here's the topic object. Oh, the columns are a little out. I know what that is. Yep, we've copied and pasted this user column on our PostResource. We never actually updated the resource to use for topic. One of the problems with copying and pasting, just be careful. Let's update this to use the TopicResource instead.

One of the problems with copying and pasting, just be careful. Let's update this to use the topic resource instead. Refresh and take a look again at one of these posts. Here we go. Topic. We have the description, the ID, the name, and the slug. Wonderful. So to wrap this up, why don't we output the correct slug on the front end in the index? We'll jump into the index component. And what have we got here?

Displaying Topic Pills14:02

We'll jump into the index component. And what have we got here? We've got a link with the list item. You can't have a link within a link, and I want these pills to eventually be clickable. So I'll have to put it as a sibling of the initial link, which is this here. So we'll drop a link in. And for now, I'm just going to have it point to maybe /. Inside the link, well, this is where our pill is going to reside. What will it say? Well, we need the post.topic.name.

What will it say? Well, we need the post.topic.name. Let's see what that looks like. There we go. We can see that being output. And let's style this link up. So maybe we have rounded-full. Let's give it a py of 0.5. Is that available? And maybe a px of 2.

Is that available? And maybe a px of 2. Then let's give it a background color. Perhaps we could use a hot pink for this. So pink-500. That's going to be a bit much. Why don't we use a border instead? So we'll give it a border and a border of pink-500. Yeah, that'll be nicer. And then we could go ahead and maybe set the text to the same thing.

Yeah, that'll be nicer. And then we could go ahead and maybe set the text to the same thing. So text-pink-500. And let's inline these two items by using a class of flex here. And we'll say justify-between. And let's also say items-baseline. There we go. That's looking a lot better. Seeing as I want these to be clickable links at some point, why don't we go ahead and add a hover state?

Seeing as I want these to be clickable links at some point, why don't we go ahead and add a hover state? So perhaps we could say when you hover, we'll make the background indigo-500 perhaps. Okay, and we could say when you hover, we make the text indigo-50. All right, yeah, that looks pretty good. Let's just resize the browser, see how this looks in mobile. Now we probably need to fix that. Let's think how we can fix that. We probably want something like, well, only actually drop onto a new line.

Let's think how we can fix that. We probably want something like, well, only actually drop onto a new line when you are in a smaller viewpoint. So we could use maybe flex-col, but then on medium displays, you jump into flex-row. That's already looking a lot better than it was. Okay, maybe on the pill, I could say that it has an mb-2. So we can add a little margin bottom there. And as we expand into a full width display, it still looks great, fills up that right hand side, and these pills are looking nice.

And as we expand into a full width display, it still looks great, fills up that right hand side, and these pills are looking nice. Of course, clicking these pills at the moment will lead to the dashboard instead of what we actually want, which is filtering that index down to only posts that have the specified topic. So in the next episode, let's give some consideration to routing to fix that problem.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟