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

Scoping Topic Selection0:00

Quickly scoping out this feature before we start work, I think it makes sense that on the creator post page, we have a drop down between the title and the markdown editor that allows you to choose from all the different topics, with the topic obviously being a required field. And by default, it would probably have the general topic selected, but you would be able to filter down and choose from any of the existing topics. As always, why don't we start with the tests. Inside our PostController's store test, we have this valid data array, which we're using to pass around various tests in this file, so that we don't have to build up that data manually.

Refactoring Test Data0:36

to pass around various tests in this file, so that we don't have to build up that data manually. We need to add topicID, so we'll drop it in, but the problem appears when we decide on the ID to select for valid data. We can't put an integer in there, because there is no way to say for certain that there is a topic in the database that has that identifier. So we need to create it ahead of time. We could obviously do that with TopicFactory, create, and then we could call getKey in order to limit it to just the identifier. The issue with this is that we don't use valid data in every test, like this one here for

to limit it to just the identifier. The issue with this is that we don't use valid data in every test, like this one here for example. So we're creating a Topic in the database essentially for no reason, slowing our test down unnecessarily. To fix this, why don't we turn our valid data array into a closure, which we can call in our tests to build this data, but which will not be executed or called ahead of time. That means in requiresAuthentication, there will be no slowdown, because we never actually call this closure. If we run it stores a post, you'll note that this now fails, because the data has to be

call this closure. If we run it stores a Post, you'll note that this now fails, because the data has to be an array, and obviously we're now providing a closure to the post method. To rectify this, we'll need to extract this valid data to its own variable. So data equals, and let's make use of Laravel's value function here, passing in this valid data, which will essentially just call that closure for us. And now rather than passing valid data here, and here, we'll pass in our extracted data parameter. Let's rerun the test, and you'll see that it fails again, but with a different reason. This time there's an exception, and the exception is that the field TopicID doesn't have a default

Let's rerun the test, and you'll see that it fails again, but with a different reason. This time there's an exception, and the exception is that the field TopicID doesn't have a default value. That's exactly the error message that we're after. Let's jump into our PostController, and in the store method, well we're going to need to add TopicID to the validated data. TopicID, and for now we'll just set this to required, we'll be updating it in just a moment. And seen as TopicID is the correct field name, when we create the post, we can just pass the data directly in, and it should work without issues. Let's rerun the test, it passes.

the data directly in, and it should work without issues. Let's rerun the test, it passes. So we can be confident that that is working correctly. Let's go back into our test and move down. Here we check that we're redirecting to the show page, nothing out of the ordinary, but as before, we'll need to wrap this valid data in the value function, run it, it's passing, and finally we're checking for valid data here. Once again, we'll wrap this valid data in value, and make sure that that works. And once we're happy that it's working, we can go ahead and add our new TopicID to the data set that we have here.

Adding Topic Validation3:19

And once we're happy that it's working, we can go ahead and add our new TopicID to the data set that we have here. So perhaps between title and body, I'll insert TopicID, and I want a TopicID for the required validation rule that we've just added. We'll set it to null, and I expect a validation error on TopicID itself. Let's run those tests, brilliant, they're passing. The next rule I need to add is one that ensures that the topic you've selected actually exists in the Topics table. And to test this, well I'm going to set TopicID to a value I know will never exist in the Topics table, whilst also technically being of a valid type.

And to test this, well I'm going to set topicId to a value I know will never exist in the topics table, whilst also technically being of a valid type. So it has to be an integer, because that's what the ID is. But the only range I can be absolutely certain will never exist in the topics table is the negative range. So I'll set the value to minus one. This will avoid us having a flaky test that could fail intermittently down the line if we're running a lot of tests and they're in parallel, and we're not resetting the identifiers on our topics table. Let's run our test, and as we'd expect, it fails.

on our Topics table. Let's run our test, and as we'd expect, it fails. We can jump back into the PostController, and where we have our required rule for topicID, we'll add an Exists check, checking the Topics table on the ID column. So we're going to check that the value of topicID exists in the Topics table on the ID column. And if it doesn't, well a validation error will be returned. Let's rerun the tests, and now they all pass. Okay, from the perspective of storing a post, that's more or less all of the functionality required from the backend.

Passing Topics to Create4:54

Okay, from the perspective of storing a Post, that's more or less all of the functionality required from the backend. Thinking about our frontend, if we're going to have a select dropdown here, then we need to pass all of the Topics from the database down to this page so that it can make use of them. Again, we'll start with a test, this time it's going to be the Create test, and we're checking it returns the correct component, why don't we also check that it passes Topics to the view. We'll create a few Topics, topics equals TopicFactory, maybe two Topics, and we'll call Create. Then we can steal this code here, so that we can make a request, and then finally we're

We'll create a few Topics, Topics equals TopicFactory, maybe two Topics, and we'll call create. Then we can steal this code here, so that we can make a request, and then finally we're going to assert that we have a resource, the resource is Topics, and as we've done before, we're going to grab the TopicResourceCollection method, which will wrap the Topics that we created above. Let's run this, it fails, let's go ahead and fix it, PostController, we're looking for the create method, and let's update our inertia call here to pass Topics down. Of course we can wrap this in a closure again so that we lazily evaluate it, and we're looking for the TopicResourceCollection method, passing in all the Topics from the database. Rerun the test, and it passes, so now we can turn our focus to the front end.

Building Topic Dropdown6:05

for the TopicResourceCollection method, passing in all the Topics from the database. Rerun the test, and it passes, so now we can turn our focus to the front end. Inside our Create component, why don't we start by defining our props, so const props equals defineProps, and of course the only thing I'm passing down at the moment is those Topics. We'll need to update our Form object that we have here to also accept that topicId, and why don't we set the initial value of the topicId to props.topics, we'll grab the first Topic, and then we'll select the ID from it. So when you first load the page, it will always have a value, and that field will not be null by default.

So when you first load the page, it will always have a value, and that field will not be null by default. You'll be inside probably the general Topic by default. Let's turn our attention to the HTML, so here's our title input, here's our markdown editor, between the two we can have a div, and that div needs to include the InputLabel component. The InputLabel component will be for a field called topic_id, and let's call it Select a Topic, nice and declarative. Underneath, at least for now, I'm going to make use of a bug standard HTML Select element, and we can extract to a component down the line, but there's no point in extracting early. Okay, we'll have our options in here, and of course our options are going to be the

and we can extract to a component down the line, but there's no point in extracting early. Okay, we'll have our options in here, and of course our options are going to be the different Topics that have been provided from the backend. So v-for, and we'll loop over Topic in Topics, we'll set the key to Topic.id, and we'll set the value to Topic.id as well. The option will instead be Topic.name. Let's set up our v-model on the Select, v-model equals form.topic_id, and we'll give the Select an ID of topic_id as well to link it to our InputLabel up here. There we go, it doesn't look too terrible, it doesn't match the input styling, but it does work.

There we go, it doesn't look too terrible, it doesn't match the input styling, but it does work. I can move between the different Topic Types available to me, and they're all showing correctly. Let's update the styling slightly. I think I'm just going to head into the TextInput, steal the classes from that TextInput, and for now drop them directly on the Select component. How does that look? Yeah, that's already a million times better. We'll add a little margin between the Label and the Select component, maybe using mt-1. Nice.

We'll add a little margin between the Label and the Select component, maybe using mt-1. Nice. And for now, let's make it widthful. As with our other fields, we need to add the ValidationInputError component. I'll drop that under the Select, and we'll change Form.Errors.Title to Form.Errors.TopicID. Of course, unless you're hacking the HTML, you would never actually see a validation error for this field, because the only values you can select from are all valid values. Let's see if this works. So I'll use our CoolNewButton to fill the rest of the fields. Let's give this a Questions topic, and hit CreatePost.

Showing Topic on Post8:56

So I'll use our Cool New button to fill the rest of the fields. Let's give this a Questions topic, and hit Create Post. Okay, it worked in a way, however we can't see the topic that's been assigned to this post, so why don't we fix that? We'll head into our PostShowTest, and here we're checking that we pass a post to the view. We're loading the user relationship. Why don't we also load the topic relationship? And running this test will now fail, which means I can go into the PostController, to the show method, and where we load the user relationship on the post, I'll also load the

And running this test will now fail, which means I can go into the PostController, to the show method, and where we load the user relationship on the post, I'll also load the topic relationship. Rerun the test, and now it passes. So we can be sure that our post also contains information about its related topic on the Show page. Let's head to the PostShow component, and we have our page heading here. Why don't we introduce our little pill, and the pill href is going to be route('post.index'), passing in the topic, which will be post.topic.slug, and then the name of this pill can be, well, topic.name.

passing in the topic, which will be post.topic.slug, and then the name of this pill can be, well, topic.name. Let's see how that looks. There we go. Now we know that this post is linked to the questions topic. We'll add a little bit of spacing, class equals mt2. There we go. That looks much better, and now we know, yeah, the post has been correctly set up with the selected topic, and if I click this, I get taken straight to the index where I can see all posts to do with questions.

selected topic, and if I click this, I get taken straight to the index where I can see all posts to do with questions. Let's try creating a post under a different topic. Perhaps we'll select Fan Fiction, head down, Create Post, there we go, working perfectly. So there we have it. We now have a fully tested way of being able to categorize all posts inside our forum, making it far easier for a user to be able to filter down to exactly the content that they're interested in seeing.

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