Search code examples
phplaraveleloquentalgolialaravel-scout

Laravel Scout/Algolia: Model relationships not pulling through to index


I have 3 models—Products, Users, and UserProducts. A UserProduct is an instance of a particular product with a User’s specific price, product code etc for that item. Consequently one Product can have many UserProducts, and through those UserProducts, many Users. These relationships are defined within the Product model:

public function userProducts() {
       return $this->hasMany(UserProduct::class);
}
 
public function users() {
return $this->belongsToMany(User::class, 'user_products');
}

I know that these relationships are working, as in Artisan Tinker I get results when calling either of these as a property.

In my Algolia products index, I want results to contain information about related Users and UserProducts for filtering. Consequently, I’m loading these relationships as recommended in both the Scout Extended and Algolia documentation:

public function toSearchableArray() {
    $this->users;
    $this->userProducts;
    $array = $this->toArray();
     // Apply Scout Extended default transformations:
    $array = $this->transform($array);
    return $array;
}

Everything is pulling through, but for some reason ‘users’ and ‘user_products’ are coming up empty.

Screenshot of Algolia products index

This is made all the more confusing by the fact that UserProducts, which have ‘user’ and 'product' relationships, are successfully loading those relationships prior to them being pulled into my user_products index:

Screenshot of Algolia index

From this, I can only conclude that the issue must lie with how I’ve set up the factory files that populate the database. In the case of UserProducts I’ve used a simple factory:

<?php
 
/** @var \Illuminate\Database\Eloquent\Factory $factory */
 
use App\UserProduct;
use Faker\Generator as Faker;
 
$factory->define(UserProduct::class, function (Faker $faker) {
   return [
       'code' => 'EAN' . $faker->ean8,
       'price' => $faker->randomFloat(2, 5, 1000),
       'product_id' => App\Product::all()->random()->id,
       'user_id' => App\User::where('type', 'supplier')->inRandomOrder()->first()->id
   ];
});

… whereas what happens in the User factory is a bit more complex:

$factory->define(User::class, function (Faker $faker) {
   return [
       'display_name' => $faker->name,
       'email' => $faker->unique()->safeEmail,
       'phone' => $faker->phoneNumber,
       'type'  => array_rand(['supplier', 'buyer']),
       'profile_completed' => true,
       'email_verified_at' => now(),
       'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
       'remember_token' => Str::random(10),
   ];
});
 
$factory->afterCreating(User::class, function ($user, $faker) {
 
   $user->assignRole($user->type);
 
   if($user->type==='supplier') {
       // create five products for them
       $products = factory(App\Product::class, 5)->create();
 
       $products->each(function($product) use ($user) {
 
           $user_product = factory(App\UserProduct::class)->create([
               'product_id' => $product->id,
               'user_id' => $user->id
           ]);
 
       })->each(function($product){
           $product->setUserProductCount();
           $product->update();
       });
 
   }
});

All the changes made in the afterCreating callback are performed successfully. However, no amount of fiddling with this code, adding $product->searchable() etc seems to bring the necessary data through to my products index. Any helpful suggestions would be greatly appreciated.


Solution

  • It can be that you forgot to use the touch feature of Laravel on your Product model. See https://www.algolia.com/doc/framework-integration/laravel/indexing/configure-searchable-data/?language=php#updating-relations-when-parentchild-change

    Let me know if this solves your issue