4

I'm completely new to factories and seeds, and until now I've been manually adding data to each project in Sequel Pro or Workbench an.

I'm fairly new to Laravel in general, but I think I've got the models setup correctly.

I've got a recipe that can have multiple ingredients, and the ingredients can belong to multiple recipes.

The pivot between the two has a quantity and unit column (with the latter being nullable).

Recipe.php

class Recipe extends Model
{
    public function user(){
    return $this->belongsTo('App\User');
    }

    public function ingredients(){
    return $this->belongsToMany('App\Ingredient');
    }

}

Ingredient.php

class Ingredient extends Model
{
    public function recipes(){
        return $this->belongsToMany('App\Recipe');
    }
}

IngredientFactory.php

$factory->define(App\Ingredient::class, function (Faker $faker) {
    return [
        'name'       => $faker->word,
        'created_at'  => Carbon::now()->format('Y-m-d H:i:s'),
        'updated_at'  => Carbon::now()->format('Y-m-d H:i:s'),
    ];
});

RecipeFactory.php

$factory->define(App\Recipe::class, function (Faker $faker) {
    $userIds = User::all()->pluck('id')->toArray();

    return [
        'title'       => $faker->text(30),
        'description' => $faker->text(200),
        'user_id'     => $faker->randomElement($userIds),
        'created_at'  => Carbon::now()->format('Y-m-d H:i:s'),
        'updated_at'  => Carbon::now()->format('Y-m-d H:i:s'),
    ];
});

IngredientTableSeeder

public function run()
{
    factory(App\Ingredient::class, 100)->create();
}

RecipeTableSeeder

public function run()
{
    factory(App\Recipe::class, 30)->create();
}

That all works fine, but I'm trying to work out how I can generate data for the pivot table.

I feel like it should be something like this:

RecipeIngredientsFactory.php

$factory->define(?::class, function (Faker $faker) {
    $recipeIds = Recipe::all()->pluck('id')->toArray();
    $ingredientIds = Ingredient::all()->pluck('id')->toArray();

    return [
        'recipe_id'     => $faker->randomElement($recipeIds),
        'ingredient_id' => $faker->randomElement($ingredientIds),        
    ];
});

But I'd be unsure what to put as the ? for the model::class?

I could be completely off base, but the logic seems right.

Please let me know in the comments if any more info is required.

n8udd
  • 657
  • 1
  • 9
  • 30

1 Answers1

9

Generally speaking, you are defining the mapping table that provides the link between a Recipe and its Ingredients. From a very simplistic view, you would not have a model for that link in a lot of many-many relationship because the relationship has no meaning other than the linkage. In this case, you might build a Recipe Seeder like this:

public function run()
{ 
    $recipes = factory(App\Recipe::class, 30)->create();
    $ingredients = factory(App\Ingredient::class, 20)->create();
    $recipes->each(function (App\Recipe $r) use ($ingredients) {
        $r->ingredients()->attach(
            $ingredients->random(rand(5, 10))->pluck('id')->toArray(),
            ['grams' => 5]
        );
    });
}

This will generate both the recipes and ingredients independently, and then you are just relating them in a standard way. You could also generate them each in a different seeder, and have a third to do the joins.

In your specific case, you are joining Recipes and Ingredients. The corresponding relationship would have its own attributes, such how many grams of an ingredient you need for the recipe. In this case, you may find a joining model useful. Have a look at https://laravel.com/docs/5.7/eloquent-relationships#many-to-many under the section Defining Custom Intermediate Table Models.

shaneparsons
  • 1,489
  • 1
  • 20
  • 24
Jason
  • 2,940
  • 2
  • 21
  • 34
  • Thanks! I was almost there having found this post: https://stackoverflow.com/questions/45269146/laravel-seeding-many-to-many-relationship but your answer works exactly. – n8udd Sep 08 '18 at 23:09
  • I also need to add a quantity to the pivot: `General error: 1364 Field 'quantity' doesn't have a default value (SQL: insert into 'ingredient_recipe' ('ingredient_id', 'recipe_id') values` I know the command for this using faker, but I'm unsure where to put it in the code above. – n8udd Sep 08 '18 at 23:11
  • 1
    I've edited the answer to add a second parameter to the attach function, which is an array of values to add to the intermediate table. The value of 'grams' could be any value you want (random or otherwise) – Jason Sep 08 '18 at 23:27
  • Thanks, works in laravel 9 as well. – Pratik149 Dec 22 '22 at 20:15