Handling changes on belongsToMany relations with Laravel Scout Extended

smknstd
code16
Published in
2 min readFeb 26, 2020
Photo by Hannah Gibbs on Unsplash

Scout extended is a great package, letting you use many awesome features provided by algolia directly in your laravel app.

But while using some belongsToMany relationships with pivot tables, thing can get a bit tricky sometimes. Here I’d like to show you how I implemented some filtering on some blog posts written by multiple authors with a nice facet filter like this:

Let’s begin with my two models, linked by belongsToMany relation as a pivot table:

class Blogpost extends Model
{
use Searchable;
public function authors()
{
return $this->belongsToMany(Authors::class);
}
}
class Author extends Model
{
public function blogposts()
{
return $this->belongsToMany(Blogpost::class);
}
}

As you may know, Scout Extended will transform your model into Algolia records with the toSearchableArray method. As we’d like to search blog posts for one or multiple authors, we’ll need to send algolia the authors list for each blog post :

public function toSearchableArray()
{
return [
...
"authors" => $this->authors->pluck('name'),
];}

Using touch feature

Now comes the question of updating algolia records when a blog post’s authors are updated. Laravel has a built-in feature to let the parent relationship know that one of its children has changed, so we’ll use it here:

class Author extends Model
{
protected $touches = ['blogposts'];
...

Now everytime, an new author is added or the name of an author changes, algolia records will be automatically synced. Nice !

Link removal

Unfortunately, this won’t work when a link is removed between two related items because the hook is not triggered in this specific case. For example when you do $blogpost->authors()->detach($author) or $blogpost->authors()->sync([...]), algolia won’t be aware of the removal. So you'll need to handle this additionally.

One way to do this, is to define a custom intermediate table model and hook on delete event to trigger the change:

class Author extends Model
{
protected $touches = ['blogposts'];

public function blogposts()
{
return $this
->belongsToMany(Blogpost::class)
->using(BlogpostAuthor::class);
}
}

class BlogpostAuthor extends Pivot
{
public static function boot()
{
parent::boot();
static::deleted(function ($item) {
Blogpost::find($item->blogpost_id)->touch();
});
}
}

Now when removing a previously attached author from a blog post, search results will be “up to date”. Happy indexing y’all !

--

--