radu's avatar
Level 3

Proper way to set relations for a model that is made of two types of models

Hi all, Long story short I am building a meal planner and I can't figure out how to properly implement the relationships. A Meal (ex: breakfast, lunch, dinner, snack) can be made of:

  • Ingredients (rice, salad, beans etc);
  • Dish (ex: Easy Skillet Chicken Thighs, Pasta Bolognese etc).

I am not sure if that should be a many to many polymorphic relationship or there is a better approach. I thought I should have a pivot table, meal_components, between Meals and Ingredients/Dishes. Because a meal can have multiple Ingredients/Dishes. And an Ingredient/Dish belongs to multiple meals.

// database/migrations/create_meals_table.php
Schema::create('meals', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->text('description')->nullable();
    $table->unsignedTinyInteger('sort_order');
    $table->timestamps();
});

// database/migrations/create_meal_components_table.php
Schema::create('meal_components', function (Blueprint $table) {
    $table->id();
    $table->foreignId('meal_id')->constrained()->onDelete('cascade');
    $table->morphs('mealable');
    $table->integer('quantity')->nullable();
    $table->timestamps();
});

I also have no idea how to define the relationships in my models. So any help would be greatly appreciated :)

Thanks!

0 likes
8 replies
LaryAI's avatar
Level 58

To implement the relationships for your meal planner, you can use a polymorphic many-to-many relationship. This allows a Meal to have multiple Ingredients and Dishes, and vice versa. Here's how you can set it up:

Database Migrations

You already have the meals and meal_components tables set up. You'll also need tables for ingredients and dishes.

// database/migrations/create_ingredients_table.php
Schema::create('ingredients', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

// database/migrations/create_dishes_table.php
Schema::create('dishes', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

Models

Define the relationships in your models using the morphToMany and morphedByMany methods.

Usage

To associate an Ingredient or Dish with a Meal, you can use the attach method:

$meal = Meal::find(1);
$ingredient = Ingredient::find(1);
$dish = Dish::find(1);

// Attach an ingredient
$meal->components()->attach($ingredient, ['quantity' => 2]);

// Attach a dish
$meal->components()->attach($dish, ['quantity' => 1]);

Explanation

  • Polymorphic Many-to-Many: This setup allows a Meal to have multiple Ingredients and Dishes through a single meal_components table.
  • Morphs and MorphedByMany: These methods are used to define the polymorphic relationship. morphToMany is used in the Meal model to define the relationship with MealComponent, while morphedByMany is used in Ingredient and Dish models to define the inverse relationship.

This setup should give you the flexibility to manage meals composed of both ingredients and dishes efficiently.

martinbean's avatar

@radu Are you not over-complicating things here? Surely a “meal” is made up dishes, and dishes are made up ingredients?

radu's avatar
Level 3

Hi @martinbean, indeed a dish is made up of ingredients. But it also includes cooking instructions and some other details. When someone is creating a meal plan, should have the option to chose between simple ingredients, like toast, butter and honey for breakfast. And bolognese pasta for lunch. If I would leave only the option to create the meal plan based on ingredients, then he should manually add them all (pasta, tomato sauce, meat, spices, salt).

Do you think of another way I should approach this? Thank you :)

martinbean's avatar

@radu “Pasta bolognese” to me would still be a “dish” though, as it’s made up of multiple ingredients: the pasta (spaghetti) and then the ingredients of the bolognese (minced beef, tomatoes, etc). Your Dish model could then optionally have some sort of related “recipe” data that instructs the user how to prepare that dish.

radu's avatar
Level 3

@martinbean Indeed, the Dish has some extra fields and a many to many relationship with Ingredient model. That's the easy part :) The only issue is if I should have a polymorphic many to many relationship between Meal on a side, and Dish and Ingredient on the other side. Because a Meal can be made of Dish + Ingredient, Dish or just Ingredient. Just to give some context, the app I am working on allows nutritionists to create meal plans.

martinbean's avatar

@radu You’re completely over-complicating this.

A dish is a combination of ingredients prepared some how. Even something as simple as carrots and hummus would be a “dish” made up of the two ingredients: the carrots and the hummus.

Meals are made up of one or more dishes. Dishes are made up of one or more ingredients. Done.

Snapey's avatar

It seems over complicated.

Why can't you have two, separate many to many relationships with a dish_meal pivot and a ingredient_meal pivot table?

radu's avatar
Level 3

Hi @Snapey, thanks for your reply. You suggest it would be better to have two many to many relationships? And when fetching the meal from DB, I should eager load both relationships? That doesn't sound bad :) My initial plan was to have a polymorphic many to many relationship between a Meal, on a side, and a Dish and Ingredient on the otherside, where meal_components is the pivot table. I haven't used polymorphic relationships in the past too much. Do you see any pros/cons to this approach vs having 2 separate many to many relationships? Thanks a lot!

Please or to participate in this conversation.