00:06
because for our courses, the difficulty ID on that course is a not null relationship. So we need a difficulty to exist on a course.
00:15
So if we had just deleted a difficulty that was bound to a course, we would have gotten a foreign key constraint error because that difficulty
00:22
needs to exist in the database as it's referenced inside of a course. Now, we do have a good option to remedy that situation. Let's see if we're about to delete our test one here.
00:30
When we click on our delete icon to pop open the confirm delete dialog, we can utilize that courses count that we added whenever we were listing these difficulties out to determine whether or not any courses
00:40
reference that particular difficulty and make use of it. If we do, then we can offer a select inside of that confirm destroy that would allow the user to select one of their alternative difficulties
00:50
to replace the one being deleted with on that particular course or courses. Right now, we don't have any courses though. So let's jump into a Node Ace REPL session,
01:00
await and load our models in. So I'm going to go ahead and grab the user, set that equal to await models user, and we'll call the find by or fail because
01:10
the only thing that I really know about this user is the email. So testuser1 at test.com, we can hit enter and now we should have our user there and we sure do.
01:19
Check out the ID and okay, this is the user with an ID of one. In order to create a course, we're going to need an access level difficulty and status ID. But first, we need to know what the organization is.
01:29
So we can grab the org by reaching through the user, grabbing the related organizations,
01:35
querying where the name of the organization is my first organization, and we should only have one, so I'm going to call first or fail there.
01:44
Hit enter, and now we should have our organization, and I forgot to await it. So let's see if maybe we await org.name if we get the name and sure enough, we do.
01:52
So I'm just going to do const o equals await org since we set org as a const there. So now we should be able to do o name. There we go. Then for our status and access level,
02:02
I don't really care what we set this to, this is just simply a test. So let's just do const status equals await o related statuses.query,
02:12
and then we can just grab the first one that we have inside of there, and let's do the exact same for our access level.
02:18
Just await o related access levels query first or fail. There we go. In terms of our difficulty, we do want to know which one we're testing with,
02:27
and to make things easy, let's go ahead and just test with our test one here. So for that one, we'll do const, and I'm just going to do diff equals await o related difficulties query
02:37
where name is test one, and then grab the first or fail there. Hit enter on that,
02:43
and now what we should be able to do is const course equals await o related courses call create and pass in an object here.
02:53
I think we can do multi lines. Yep, there we go. All I did was hit enter there, and we should be able to set the name to course test one or whatever you'd like there.
03:01
The access level ID to the access level dot ID, the status ID to the status dot ID,
03:11
and then the difficulty ID to our diff dot ID. Go ahead and end that object and end our parentheses. Hit enter, and that should now run.
03:20
So if we check our course dot check the ID, awesome. So now that got created, we can check our difficulty ID, and it's created with 37 there.
03:29
Okay, so we have our test up and running. So now we have at least one course bound to our test one. So if we give our pager a refresh to pick that up, I'm going to right click, inspect,
03:39
and if you have the Vue.js dev tools installed in your browser, you should have that little view tab over there. Go into our app layout, go into the index page, and this should be our last difficulty
03:49
as it's listed last on the page. If we open that up, we can see that currently that count isn't coming through, and we aren't preloading our courses, so currently seeing zero for that is expected.
03:57
So let's go ahead and jump back into our text editor, and this is most likely going to need added into the model to DTO conversion.
04:05
So we'll jump into our DTO, go into our difficulty DTO, and sure enough, we don't have anything for the metadata or extras object within here.
04:12
So let's go ahead and declare meta as a record bring any, and then down in the constructor, we want to set that as well.
04:20
So this meta equals, and we can reach for our difficulty and pass the extras object into that. The models extras object contains anything
04:30
that was included with the query that isn't on that model as a column or property in any other way. So this is where our count is. So we're just going to set that onto a property called meta via our DTO.
04:40
So if we give this a save, jump back into our browser, let's refresh one more time, and all of our data here updated. If we take a look at for one more time,
04:48
we see this meta object now with our, I'm not sure what happened there, courses count set to one. So awesome. We're now getting that count through.
04:57
We're going to need this for our access levels and statuses as well. So while we're here thinking about it so that we don't forget, let's go ahead and copy our declare meta, jump over to our access levels,
05:07
plop that on there. And then let's go to our status DTO, plop it on there as well. Then we can go back to our difficulty and get the constructor setter for that and do the exact same thing.
05:16
So we'll plop that on the access levels constructor. And we just need to update the actual text there. Copy that one more time, jump into our status and do the exact same.
05:25
So plop that right there, switching the text to status. Okay, close those out as we're now done with them. And let's jump back into our difficulties index page.
05:34
First, with our destroy confirmation dialogue, we're going to want to update this dialogue's text a little bit. So first, let's go ahead and wrap this in a div
05:42
because we will want to do a VIF VLS there. But if we are displaying this particular div, then that means that no courses are currently using this difficulty
05:52
so that they know it's clean and clear to go ahead and delete if they wish. However, if a course is using this, so we'll add in another div with the VIF
06:01
and reach for the destroy resources, optionally chain in that meta key that we added and then reach for that courses count that contains the number of courses bound to this difficulty.
06:11
And we saw whenever we inspected this in the view debugger that it came through as a string. So we won't do the type safe equality check and instead we'll just do does not equal zero. And whoops, resources there should be singular. There we go.
06:21
Then on this div down here, we just want to do VLS. Then we'll say, what difficulty would you like to assign
06:28
reach for the destroy resource meta courses count courses using and then reach for the destroy resources name
06:38
of the difficulty that we are deleting. Then we can tack on a form input and we're gonna want to mutate that form input slightly to allow a type of select.
06:48
And then the label for this is going to be difficulty or you can do replacement difficulty and we'll set a view model. And this is where we wanna reach for that destroy data that we added on
06:58
and we'll have a replacement ID key on there to hold the new value that we should replace the one being deleted with. And then let's also add in a class with a margin top of four
07:08
to add a little bit of spacing in between the text and the form input. We'll set the form input so that whenever it's a type of select the child content are the select items. So we'll do select item
07:18
and we might need to install that. I don't think we've installed this yet from dot CM view and loop over. So before each item in our replacement options
07:28
which we'll set a computed property field for here in a moment. Then we'll set a key to that item's ID and set the value to the item's ID. And then we need to normalize that to a string
07:38
again to work appropriately with shot CM view. The text content can then be that item's name which now just leaves us with needing to set this data
07:47
up appropriately whenever we're deleting an item and then adding our replacement options. So let's start with our destroy data. Whenever we click on the trash button which calls to our on destroy show.
07:57
So we'll scroll up to that function. When we open our dialogue we're gonna wanna pass data in and this will have a key of our replacement ID. For the default value of this replacement ID
08:07
we just wanna grab the first difficulty that isn't the one being deleted. So the one that's not our resource. So we can do const replacement equals routes difficulties
08:17
and we can find the item where the item ID does not equal our resource ID and then set the replacement ID to that replacement.ID. We can add a question mark in there.
08:27
And we also need to add to string again to make that happy with shot CM view. Next we want our confirm destroy dialogue to send this form data up along with it whenever we click our deletion.
08:37
So within our confirm destroy dialogue we also wanna make use of our action data prop to pass that destroy data in
08:45
so that our link component binds the action data along with it. If we quickly jump into our confirm destroy dialogue here on that link we're adding that action data
08:54
as the data property on inertia's link. So whenever we click this link it goes out to the action href and since this is a method of delete any data that we add into this prop
09:04
will get sent up along with that request. All right, let's jump back into our difficulties because we still need to add up our replacement options. For this we just wanna make use of a computed property
09:12
and we'll add this just underneath our use resource action. So do const replacement options equals computed import that from view at a callback function here
09:21
and we'll want to return props difficulties dot and filter out the item where that item ID
09:28
does not equal the destroy value resource ID. And come to think of it we can actually clean up that on destroy a little bit by switching this to just reach
09:38
for that replacement options value and just try and find the first item in there. Okie doke. So that should do it for our client side changes. Since we're sending a form up
09:48
we're also gonna wanna add in a validator for this as well. So let's jump into our difficulty validator and add a new validation in. So let's export const
09:56
and we'll call this our difficulty destroy validator. Set that equal to vine compile and then vine object with that replacement ID
10:06
inside of here of type vine number. But we also wanna verify that this exists on the organization so that we don't allow cross organization mismatches there.
10:14
So this exists function takes in a callback that's provided a DB reference which is our database query builder. The value of that replacement ID
10:23
and then field context information as well. Inside of this callback function we're going to want to try and get back a result
10:29
using the await DB query from our difficulties table. We can select the ID column as all that we care about is whether or not
10:39
we get anything back where that ID column equals that replacement ID value and then grab the first result. Now, all this does is check and make sure
10:49
that the difficulty provided exists inside of our database. But what we care about is that it also exists inside of the organization users trying to bind it to.
10:57
So for that, we wanna make use of our field context information to pass the organization's ID into this validator here.
11:06
So with our vine compile, we can also do with metadata and provide the type in with organization ID
11:13
to specify that we need to provide the organization ID into this validator's metadata in order to use this particular validator.
11:21
Vine.js will then plop that information from the metadata onto the field contexts meta key. Meaning if I clean up my database query a little bit here,
11:30
just put all of these chains on different lines, we can add an additional where clause making sure that the organization ID
11:38
equals the field meta organization ID value, which allows us to verify that the replacement ID for our difficulty is bound
11:48
to a particular organization ID that we pass in to the metadata of this validator. And then the exists call requires us to return back a Boolean. So we wanna return,
11:57
we can use the bang bang operator to just return the result. So if we got anything back, then we wanna return true because it does exist and our validator is a-okay.
12:05
If it's false, then it does not exist and we need to fail the validation. So let's give that a save. And then we can jump back into our difficulty controller,
12:14
make use of our request. So const data equals await request and call validate using passing in our difficulty destroy validator.
12:23
And the way that we add in metadata information is through the options object as the second argument here. In these options, we can add in a meta object
12:32
and this will get passed through directly as the meta field context for use inside of this validator. So we wanna set the organization ID
12:40
to our organization ID like so. And then we also wanna pass our validated data into our destroyed difficulty handle method.
12:48
If we now jump into our destroyed difficulty handle method, we wanna add that into our params. So data, we can infer the type of our difficulty destroy validator,
12:58
extract that data out of our handle method. And we now need to replace our difficulty prior to deleting it. But we only wanna replace it if that deletion is also successful.
13:08
So let's wrap this inside of a transaction using await and we can import DB from Lucid services DB and call the transaction method, providing in a callback
13:18
to use the managed transaction approach. Do async and this provides us our transaction instance. And we can make this easy on ourselves by using transaction cascading by binding it on our organization.
13:28
So use transaction TRX as everything that we're gonna do instead of this callback is initiated directly off of our organization instance. So that transaction will cascade through
13:38
along with that relationship. So whenever we reach through our organization's related difficulties, Lucid will cascade that transaction through to our difficulty itself for use whenever we delete that difficulty.
13:48
Prior to deleting that difficulty, we now want to await organization, reach for the related courses, query from that where the difficulty ID
13:58
equals the ID of the difficulty we are deleting. And then we can call update, set the difficulty ID to now our data dot replacement ID
14:07
to swap out the difficulty being deleted with its new replacement inside of our database. And that should do it. So let's go ahead and give everything here a test,
14:17
close that out momentarily, and let's hit delete on our test one. Okay, so you can see what difficulty would you like to assign the one courses using test one.
14:26
So this all seems to work. All right, we need to update our form input yet. But real quick, if we go ahead and try to delete one that doesn't have a course, you'll see that we just get the same dialogue
14:36
that we had previously here. So that if else logic seems to be working, let's jump in now to our form input component and update this to allow a type of select.
14:46
So I think we first need to install select. So we'll look for a Shotzi and add new component, look for select, and let's just make sure that we get that installed. It's like a new Shotzi and versions available.
14:56
So we'll hit yes to confirm it and update that. And there we go. Okay, close that terminal out. And now we can add in a select component
15:04
and add to this a V else if the type equals select, set the view model to our internal value.
15:12
And then we can add in the disabled and required flags that we're using elsewhere as well. For the child content of our select, we need a select trigger,
15:21
which we can pass through a slot named trigger so that we can make use of it outside of this component. Otherwise we'll default that to select value
15:30
with a placeholder set to whatever placeholder text we provide in. Then we need the select content and we'll just set that to slot, which will be our select items
15:39
that we've added outside of this component. And then so that we don't have that same issue that we had last time, let's jump back into our difficulty page, make very small change,
15:47
save it so that our automatic component import picks up that we are making use of this select item component within here. And we should now be able to jump back into our browser. Let's click on delete.
15:57
And there we go. So what difficulty would you like to assign the one courses using test one? And then we can select the difficulty that will serve as a replacement whenever we delete our test one.
16:06
So I'm gonna go ahead and put this on expert and then we'll hit delete. And there we go. Our test one is gone. And if we now right click, go down to inspect, open up our view depth tools again
16:15
and jump into our index page. If we now take a look at that last difficulty, which will be our expert one, take a look at its meta. It should now have a courses count of one
16:25
as we've now assigned that one course to this one instead of that test one, which is now deleted.