Kfir

Laravel Scout - Conditionally Index Model Instances

Laravel Scout is a simple, driver-based solution for adding full-text search to Eloquent models. With a recent update to the master branch, we can now determine at runtime which model instances should be made "searchable".

Current State

The current latest version of Scout, at the time of writing this post, does not offer a way to determine at runtime which model instances should be made "searchable".

Let's say we have an application that has Post entities that can be in one of two states: "draft" or "published":

use  Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    public function isPublished()
    {
        return $this->published_at !== null;
    }
}

We only want to make the "published" posts searchable as the drafts may never actually make it to production and should remain private until their publication. If we used Scout to add full-text search to the Post model, we will need to somehow tell it to only sync the posts that have been published.

The way we can somehow achieve this is by returning an empty array from the toSearchableArray() method on some condition:

use  Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    public function isPublished()
    {
        return $this->published_at !== null;
    }

    public function toSearchableArray()
    {
        if (! $this->isPublished()) {
            return [];
        }

        return $this->toArray();
    }
}

While this ensures that no data for a "draft" state post will ever be pushed to the full-text index service, (Algolia, ElasticSearch, etc.), there is one caveat to that solution. Scout will take those empty arrays and actually attempt to sync them, resulting in unnecessary requests to our indexing service. This means that we may potentially make useless HTTP requests and slow down our app or bloat our queue for no reason. Also, if we use a service like Algolia, it also means using an "indexing operation" which you are billed for per usage.

Better Solution

Laravel Scout has added a new method which we can implement on our model to determine at runtime if the model instance should be synced: shouldBeSearchable().

By default, that method returns true so if you don't deal with a situation where you need to conditionally sync your models, you don't have to do anything. Scout will work just the same as before.

Let's see how we can use this new method to tell Scout not to sync our "draft" posts.

use  Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    public function isPublished()
    {
        return $this->published_at !== null;
    }

    public function shouldBeSearchable()
    {
        return $this->isPublished();
    }
}

The code above also reads pretty nicely: "Post should be searchable if it is published".

Since we no longer use this "empty array" hack and we don't return any modified version of our Post from the toSearchableArray(), we can completely remove it.

Now when Scout will look at a model instance, it won't attempt to sync it unless the shouldBeSearchable() returns true, saving us the unnecessary HTTP calls and not bloating our queue.

This is a welcomed and much-needed addition to Scout in my opinion. Hopefully, it will get tagged soon so we can get a stable release of Scout with that nice addition.

Happy coding!