bernar.do
Home

Laravel Scout index operations only when updating searchable fields.

3 April 2020

One of the great solutions that comes with Laravel is Laravel Scout. Coupled with the free Community plan that Algolia offers, you'll have a pretty sweet deal right out of the box.

But imagine you have a Topic model which is searchable by title, just title. It would be easy to assume that that when other fields on the model are updated, you won't have an index operation sent to Algolia, since those fields are not searchable. But that is not the case, every time the model is updated an index operation is sent. So having a post_count or view_count field that is updated very frequently on the Topic model might cause you to use more than the maximum of 50.000 index operations per month on Algolia's free plan.

Fixing the searchable model

To change this behaviour we have to override the bootSearchable method in the Searchable trait. We then change the default ModelObserver into a custom one; OnlySearchableModelObserver.

<?php

namespace App;

use App\OnlySearchableModelObserver;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
use Laravel\Scout\SearchableScope;

class Topic extends Model
{
    use Searchable;
    
    public static function bootSearchable(): void
    {
        static::addGlobalScope(new SearchableScope);

        static::observe(new OnlySearchableModelObserver());

        (new static)->registerSearchableMacros();
    }
    
    public function toSearchableArray(): array
    {
        return [
            'title' => (string) $this->title,
        ];
    }
}

Our custom OnlySearchableModelObserver will simply extend the default ModelObserver and override the saved method. In there we add a check to see if any of the updated - dirty - fields intersect with the searchable fields we defined on our model.

<?php

namespace App;

use Laravel\Scout\ModelObserver;

class OnlySearchableModelObserver extends ModelObserver
{
    public function saved($model): void
    {
        if (static::syncingDisabledFor($model)) {
            return;
        }

        if (! $model->shouldBeSearchable()) {
            $model->unsearchable();

            return;
        }

        // Check if any searchable fields have changed
        if (empty(array_intersect_key($model->toSearchableArray(), $model->getDirty()))) {
            return;
        }

        $model->searchable();
    }
}

That's it! Since I couldn't find anything about this myself, I deciced it was a great excuse to write a blog post about it. Hopefully it comes in handy, or maybe there is a better solution, let me know on Twitter!

Another solution

Instead of fixed this issue like we did just now, there is an alternative, at least if you are using Algolia. The open source package Scout Extended by Algolia fixes this issue and has many more options and improvements. It features searching in multiple models, index configuration in version control, zero downtime re-imports, and many other features.

hosted by digitalocean.comtripix.nlrss