Recapping Nova Search0:00
Well, we've already discussed search in its basic form in earlier episodes, so here's a quick recap. On any resource, you can define a static search property, which is an array of strings, and each string represents a column on the resource you want to search on. We can search a Book, in this case, by its ID, its title, its blurb, and Nova even makes it easy to search on related columns, such as the Author who wrote the book. What if you want something even more powerful, though, or if your use case isn't covered by the search property? Well, in this episode, let's explore the lengths and breadth of search in Nova. In order to demonstrate search to its fullest extent, I've created a new model called the Review. Now, what can be reviewed? Well, a Book can be reviewed, an Author can be reviewed in our application, and it can be reviewed by a User or a Customer. So, it's a very polymorphic model in our database. Let me show you an example of a Review.
So, it's a very polymorphic model in our database. Let me show you an example of a Review. In this case, Luke Downing, the User, is reviewing Aidan Stewart, the author. It has a title, it has a body, and you can see we can rate it one out of five stars and verify the review as well. Now, I've already gone ahead inside the Review Nova resource and set up the search array to search on the ID, the title, and the stars. As before, we could easily add body to this list, and by doing so, I should be able to take a little bit of text from here and paste it into the search, and sure enough, it appears in the global search results. What if we wanted to support something more complex in searching something like the body, however? For example, I might not remember all of the words in exact order. I might just remember a few keywords, maybe Stewart and resonant and qualities.
Adding Full-Text Search1:52
For example, I might not remember all of the words in exact order. I might just remember a few keywords, maybe Stewart and resonant and qualities. So, Stewart and resonant and qualities. Well, now nothing is going to show, and that's because Nova, by default, will perform a like query in SQL, and like only works with subsequent characters. If we want to support full text, which MySQL does have support for, we'll need to make a few adjustments in our code base. Let's go do that now. The first thing I'll do is use php artisan make:migration to make a new migration in order to add full text to our database. Inside the created migration file, I'll update the table to reviews, and I can call table->fullText, and I'll pass in the column I want to turn into full text, which in this case is body, and then I can run php artisan migrate in order to execute that migration.
and I'll pass in the column I want to turn into full text, which in this case is body, and then I can run php artisan migrate in order to execute that migration. Now that our database has been migrated, I need to update our review resource in order to tell Nova that body is full text, and I can wrap body in a class called SearchableText to do this. So, new SearchableText, and you'll see, obviously, PHP is going to squawk because you can't instantiate a class inside a static property. So, for instances where your use case for search is more complex, you can override the searchableColumns method inside a Nova resource. If we take a quick look at the parent searchableColumns method, you'll see that it's simply deferring to that search property. So, in this case, we can completely remove the search property because we're going to replace it with the searchableColumns method instead, and we'll return those very same columns here. Now that PHP's stopped complaining, let's see if this works.
Improving Results With Subtitles3:41
and we'll return those very same columns here. Now that php's stopped complaining, let's see if this works. We can try our search query again now, and yeah, sure enough, we now have a full text search where we can search just keywords in the body of the review and have them return to us in the global search results. Although I'm sure you'd agree, it's not really obvious what that review is for. Is it for an Author? Is it for a Book? If this was in a list with three or four other reviews, we'd have no clue which was which. So, why don't we solve that once again with subtitles? I'll implement the subtitle method on our resource, and let's think about the code here. Perhaps we return a match statement, and the match statement can look at the reviewable class.
I'll implement the subtitle method on our resource, and let's think about the code here. Perhaps we return a match statement, and the match statement can look at the reviewable class. Inside that reviewable class, we want to check, well, are we looking at an Author here? If we're looking at an Author, we want to return the name of the Author as the subtitle. If, on the other hand, we're looking for a Book, we want to return the title of the Book. And if we're looking at something we haven't yet thought of, well, we'll just return null for now until we remember to add it later down the line. Let's see how that looks. I'll do the search once more. And now it's obvious that this review is for the Author Aidan Stewart. If, instead, we search another term, an old book with modern meaning, this review is for the Book 1984.
And now it's obvious that this review is for the author Aidan Stewart. If, instead, we search another term, an old book with modern meaning, this review is for the book 1984. Just before we move on, it's important to note that if you are accessing a relationship in subtitle, you should eager load that relationship to avoid the N+1 problem. As we've mentioned in other episodes, it's as easy as adding with, along with the relationship you want to load, in our case, reviewable. That will save you so much pain down the line when it comes to slow search queries. Whilst we're on the topic of a review for an author or a review for a book, what if you wanted to be able to search either by the author or by the book and see a list of reviews for that particular reviewable?
Searching Polymorphic Relations5:36
what if you wanted to be able to search either by the author or by the book and see a list of reviews for that particular reviewable? Well, that's a polymorphic search, and Nova makes it a breeze to implement those as well. Let's jump back into the resource, and I'll show you how. Underneath our searchable text, I'll implement a new searchable morphTo relation. The first thing we have to pass is the relationship we want to search, in this case, reviewable. The second parameter we pass is the column we want to look at. Let's tackle author first, so it will be an author's name. And the third parameter is an array of Nova resources to search on. So we want, in this case, to search for the author Nova resource.
And the third parameter is an array of Nova resources to search on. So we want, in this case, to search for the author Nova resource. We can then duplicate this line. We can use the book resource instead, and obviously we don't search a book name. We search a book title. With our searchable morph to relations in place, if I now search, let's say, 1984, I see all the reviews down here for 1984. Well, I say all the reviews. If you actually have a look, you'll see there are six reviews, and when I type 1984, I only see five results.
If you actually have a look, you'll see there are six reviews, and when I type 1984, I only see five results. But that's not because it didn't find six results. That's just because, by default, it limits global search to five results. You can change that very easily. Let's go to the top here, and I'm going to override a property called globalSearchResults, and I'll set that value to 10, limit it to 10 global search results. And now when we search 1984, you see we get all six reviews listed down here. I can just as easily search for an author, let's say Aidan Stewart, and here his review appears at the bottom.
Integrating Laravel Scout7:13
I can just as easily search for an author, let's say Aidan Stewart, and here his review appears at the bottom. So that's how simple it is to search on polymorphic relationships inside Laravel Nova. We've discussed some pretty cool power features here when it comes to search, but what if even these don't quite cut it? What if, for example, you wanted to be able to do fuzzy search on a review? Well, thankfully, Laravel already has an offering for you called Scout. Scout is an amazing free open-source product that integrates perfectly with your Laravel models and allows you to perform these complex search queries using a third-party provider. I already have a course on learning Laravel Scout,
and allows you to perform these complex search queries using a third-party provider. I already have a course on learning Laravel Scout, so go check that out if you want more information, because I'm not going to cover installation of Scout in this series. Suffice to say, once you have Scout installed, let's go to our Review model, and I'm going to add the Scout searchable trait. All I have to do is add that searchable trait, and once it's added, I can actually remove the searchableColumns method from the resource. I no longer need to define what I want to search inside the resource itself, because Nova is smart enough to understand that we want to use Scout instead.
I no longer need to define what I want to search inside the resource itself, because Nova is smart enough to understand that we want to use Scout instead. Let's import my reviews into Scout using php artisan scout:import, and I'll import my Review models. And with no more work from us as developers, we should see Scout working in our admin panel. For example, I'll spell Aidan Stewart wrong, and you'll see we still receive the review because it was smart enough to fuzzy search on Aidan Stewart and understand that's what we probably meant, so it returns the result for us.
and understand that's what we probably meant, so it returns the result for us. How cool and easy is that? Scout integration in Nova. Of course, I have to bear in mind, let's say I search for 1984 again, I only see a single review, because Scout is no longer searching across the polymorphic relationship as we were doing before. There are various ways to solve this. I'm just going to go for a simple one in this example.
There are various ways to solve this. I'm just going to go for a simple one in this example. Inside the Review model, not the resource, the model, I can override the makeAllSearchableUsing method, and I can return the query with the reviewable in place. Once I've done that, I'll need to flush out Scout and reimport my reviews, which we can do very easily from the command line, php artisan scout:flush, and then php artisan scout:import. We should now be able to search for 1984.
and then php artisan scout:import. We should now be able to search for 1984 and see all of the reviews once again. That's just one means of searching across relationships using Scout. There are many other ways and perhaps better ways for your use case, but again, this isn't a lesson about Scout. It's a lesson about search in Nova. I just think it's so cool, however, that we have such tight integration with Laravel Scout with basically zero configuration from us as developers.
Wrap-Up and Next Steps10:07
that we have such tight integration with Laravel Scout with basically zero configuration from us as developers. Well, I think that just about wraps up search in Nova. Very powerful, very simple to integrate, and it's just one of the ways in which Nova allows us to scope our data down to exactly what we're looking for. In the next episode, let's take a look at another way we can do that by making use of custom filters.
