BelongsToMany pivot loans0:00
Now that we have a handle on simple relationships in Nova, I'd like to take a look at some more complex use cases. For example, let's take a look at our Book. Obviously, a Book is loaned out to multiple Customers, and we want to be able to track those loans in our administration panel. If we take a look at our database, you'll see I have a book_customer pivot table, where I define some intermediate columns like due_back_at and returned_at. So how can we represent this belongsToMany relationship inside Nova? Well, first of all, let's take a look at our Book model. And note that we have two relationships here. One is allLoans, which is every Book this Customer has ever loaned from the library. And the next is currentLoans, which is limited to basically Books that they have not yet returned. It's currentLoans that we're interested in. So let's jump into the Book Nova resource.
next is current loans, which is limited to basically books that they have not yet returned. It's current loans that we're interested in. So let's jump into the Book Nova resource. And down here at the bottom of fields, I'll bring in the belongsToMany relationship. And I want just the current loans. So that's what I'll name the first property I pass. I will also have to pass the resource property. And that's because current loans doesn't actually tell it which resource it's attached to. The resource we are actually attached to is a Customer. So I'll pull in the Nova Customer resource. And with no more code than that, I should actually see all of the current loans for this book 1984. If you take a look at the database again, note that we have these two intermediate columns due_back_at and returned_at. However, they don't appear inside our index here. But with a bit of tweaking, we can get them to appear inside Nova.
Adding pivot table fields1:43
note that we have these two intermediate columns dueBackAt and returnedAt. However, they don't appear inside our index here. But with a bit of tweaking, we can get them to appear inside Nova. All we have to do is chain on a fields method to the belongsToMany relationship, which receives a closure that returns an array of fields. These fields will be appended to the standard fields that your related resource defines. So in our case, we want to dateTime fields, right? So the first is going to be dueBackAt. And we'll want to specify a few rules for these. So the rules will be that dueBackAt is required, it has to be a date. And yeah, it has to be after or equal today, or I might even say, after or equal to now, it can't be due back in the past, we'll also add a dateTime field for returnedAt. The difference with this one is that it's nullable because obviously, you may not yet have returned the book, in which case, this would be a null
Cleaning up index display2:35
we'll also add a dateTime field for returnedAt. The difference with this one is that it's nullable because obviously, you may not yet have returned the book, in which case, this would be a null field. With our fields defined, we should now be able to see them inside our index. And sure enough we do. Our index is starting to become a little cluttered here. One thing to note though, is we don't necessarily need to see returnedAt. It's important that the returnedAt field is there because when we edit this resource, we want to be able to set the date the customer returned the book. But we don't need to see it in the index, because this is current loans, meaning returnedAt will always be null. So to clean this up a bit, we can chain on the hideFromIndex method. That's looking much cleaner. I also don't necessarily think we need to see the time that the book is due back at, it seems like too much information for a standard index.
That's looking much cleaner. I also don't necessarily think we need to see the time that the book is due back at, it seems like too much information for a standard index. So again, we can use a method we've used before display using undoBackAt in order to make that easier to read. Now forgive me, I'm going to use English date formatting. So I'll call format, and I'm going to say day, month, year. Don't kill me in the comments, please. With that small change, we now have a much cleaner index. Obviously, I may want to do the same with join that on the CustomerResource. But for now, I'll leave that as it is. Now obviously, we have very much the same thing on the customer side. So if I go to Mohammed, I want to be able to see Mohammed's books that he has currently loaned from the library. In order to do this, I'm going to copy and paste this belongsToMany field because it's basically going to be almost identical.
Mirroring on customer side4:10
books that he has currently loaned from the library. In order to do this, I'm going to copy and paste this belongsToMany field because it's basically going to be almost identical. I'll head into the Customer Nova resource. And I'll paste that field down here at the bottom of the fields array. Obviously, I need to change the resource here to be Book because we're dealing with the opposite side of the relationship. But the fields will remain exactly the same. Now if I refresh, we can see Mohammed hunts books that he currently has on loan from the library. And we see all of the book standard fields. And at the end here, we see doBackup. If I click edit, I again am taken straight here where I'm able to alter the return date once Mohammed returns the book. Now eagle eyed viewers may already have noticed an issue that may start cropping up as time goes by the fields that we've defined on the Book and Customer Nova resources are
Extracting fields to class4:57
the book. Now eagle eyed viewers may already have noticed an issue that may start cropping up as time goes by the fields that we've defined on the Book and Customer Nova resources are identical. This code is the same. And really, it will likely always be the same, it will stay in sync, which means that we're going to have to update these fields, these pivot table attributes in two places across our code base, that is going to be a little unmanageable as time goes on. Thankfully, Nova allows us to extract this information here to an invocable class. So I'm going to go ahead and copy this array. And you can place this invocable class anywhere, but I'm going to create a new directory under Nova called relationships. Under relationships, we'll create a new class, and I'm going to call this LoanFields. LoanFields should implement the invoke method. And all it has to do is return those fields that
Under relationships, we'll create a new class, and I'm going to call this LoanFields. LoanFields should implement the invoke method. And all it has to do is return those fields that we defined in each resource, the doBackupField field in this case, and the returnedAtField. If you need more information, the invoke will receive the Nova request, and it will also receive the related model as properties. So you can use those two properties to customize the fields that are shown and displayed. Now that we have our invocable class, I can replace this with a new LoanFields instance. I can also do the same for the BookResource, I'll get rid of all of this duplicated code. And I'll simply call new LoanFields. With that done, the front end should look exactly the same. And you can see that it does. If I go to 1984, I should see the exact same customers that I saw before. And I do. But now if I ever want to
Polymorphic address relationships6:38
With that done, the front end should look exactly the same. And you can see that it does. If I go to 1984, I should see the exact same customers that I saw before. And I do. But now if I ever want to change those fields, I have a single location to change them. I simply jump into loan fields, and I make the needed adjustments. So I would recommend wherever possible, extracting your pivot attributes to invocable classes, because it will make maintaining your code base a whole lot easier. There's another complex relationship type I'd like to tackle, and that's polymorphic relationships. In our application, we have addresses. Now we don't currently have any addresses in the database, but that needs to change, because a customer should have an address and a publisher should have an address. In fact, when we create a customer or publisher, we should be required to provide an address.
because a customer should have an address and a publisher should have an address. In fact, when we create a customer or publisher, we should be required to provide an address for those two entities. Well, let's start from the perspective of a customer or publisher. From our customer fields method, I'm going to use the morphOne relationship. And as usual, I just need to define the method name, in this case, address. With that defined, I can click to create a new customer. And I have the same field options as before. But I also have the option to create an address at the same time. And I'm able to fill this out in order to create a customer with an address from the get go. You'll note that it's not actually required. If I don't click this button, I don't have to fill address details out. But I can make it required using, you've got it, the required method that Nova provides. And now when I refresh,
required. If I don't click this button, I don't have to fill address details out. But I can make it required using, you've got it, the required method that Nova provides. And now when I refresh, the option to not include an address isn't there. If I tried to create a Customer, it would tell me that these fields have not been filled out. I have to provide an address for this customer. Obviously, it's just as simple for Publisher. So simple, in fact, that I'm going to copy and paste head into the Publisher Nova resource. And down here at the bottom of the fields method, I'll drop in morphOne make address required. And if we go to publishers, and I say edit hunter limited, I have to provide an address if I want to be able to move forward. So it's actually very simple to work with polymorphic relationships in Laravel Nova. What about the other side of it though, addresses? If I click to create an address,
to move forward. So it's actually very simple to work with polymorphic relationships in Laravel Nova. What about the other side of it though, addresses? If I click to create an address, I can fill in line one to the city, the county and the postcode. But what about the related model? If I attempted to submit this, I'll get a SQL error, because the fields for the related model have not been defined on the address. While it's fairly straightforward to implement the inverse, let's go into our Address Nova resource. And up here, I'm going to define a new morphTo relationship. And this is going to be called addressable, which if we jump into the Address model, you can see is the morphTo relationship method that we've defined. In order to say which types are actually allowed, we have to chain on a types method, which receives an array. And this is an array of Nova resources that we want to be able to attach to an address. So as we said,
types are actually allowed, we have to chain on a types method, which receives an array. And this is an array of Nova resources that we want to be able to attach to an address. So as we said, we want a customer, app\Nova\Customer, remember, it's the resource, not the model. And we also want publisher, app\Nova\Publisher. Once you define your types, you'll note that when we refresh the form, we now have a new field at the top, where we choose from a list of the defined types, in this case, a customer or a publisher. Let's click customer. And then we choose the model that we're actually interested in. And I can choose that from a list down here to select the correct model. And then I can fill out the address as normal. Let's quickly fill this out to make sure everything works. I'll say one test street in London. And we'll say the county of London as well. And then for the postcode, I'll just put something fake. Like so create address. Yeah. And
sure everything works. I'll say one test street in London. And we'll say the county of London as well. And then for the postcode, I'll just put something fake. Like so create address. Yeah. And now you can see I was able to create an address directly from the addresses resource for Jennifer Russell. So to recap, in order to define that morphTo relationship you call the method that you find in the morphTo relationship on your model. And then you define the types that you're actually allowed to attach to this particular resource. In our case, a Customer and a Publisher. We've almost come to the end of talking about relationships in Nova. I'm sure you'd agree they're actually pretty easy to use. But there are a few things that Nova allows us to do with relationships that would really spice them up for our administration panel. So in the next episode, I'd like to play around with a few of those options. Before we wrap up this section. I'll see you there.
relationships that would really spice them up for our administration panel. So in the next episode, I'd like to play around with a few of those options. Before we wrap up this section. I'll see you there.
