diff --git a/app/Http/Controllers/RecipeController.php b/app/Http/Controllers/RecipeController.php index 63f2d4a..aa28c90 100644 --- a/app/Http/Controllers/RecipeController.php +++ b/app/Http/Controllers/RecipeController.php @@ -8,6 +8,7 @@ use App\Models\Recipe; use App\Models\RecipeStep; use App\Rules\ArrayNotEmpty; use App\Rules\StringIsDecimalOrFraction; +use App\Rules\UsesIngredientTrait; use App\Support\Number; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; @@ -83,6 +84,7 @@ class RecipeController extends Controller 'amount' => $old['amount'][$key], 'unit' => $old['unit'][$key], 'ingredient_id' => $ingredient_id, + 'ingredient_type' => $old['type'][$key], 'ingredient_name' => $old['name'][$key], 'detail' => $old['detail'][$key], ]; @@ -94,7 +96,8 @@ class RecipeController extends Controller 'original_key' => $key, 'amount' => $ingredientAmount->amount_formatted, 'unit' => $ingredientAmount->unit, - 'ingredient_id' => $ingredientAmount->ingredient->id, + 'ingredient_id' => $ingredientAmount->ingredient_id, + 'ingredient_type' => $ingredientAmount->ingredient_type, 'ingredient_name' => $ingredientAmount->ingredient->name, 'detail' => $ingredientAmount->detail, ]; @@ -160,12 +163,22 @@ class RecipeController extends Controller 'ingredients.detail.*' => 'nullable|string', 'ingredients.id' => ['required', 'array', new ArrayNotEmpty], 'ingredients.id.*' => 'required_with:ingredients.amount.*|nullable', + 'ingredients.type' => ['required', 'array', new ArrayNotEmpty], + 'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()], 'ingredients.original_key' => 'nullable|array', 'steps.step' => ['required', 'array', new ArrayNotEmpty], 'steps.step.*' => 'nullable|string', 'steps.original_key' => 'nullable|array', ]); + // Validate that no ingredients are recursive. + // TODO: refactor as custom validator. + foreach (array_filter($input['ingredients']['id']) as $key => $id) { + if ($input['ingredients']['type'][$key] == Recipe::class && $id == $recipe->id) { + return back()->withInput()->withErrors('To understand recursion, you must understand recursion. Remove this recipe from this recipe.'); + } + } + $recipe->fill([ 'name' => Str::lower($input['name']), 'description' => $input['description'], @@ -202,7 +215,7 @@ class RecipeController extends Controller 'weight' => $weight++, ]); $ingredient_amounts[$key]->ingredient() - ->associate(Food::where('id', $ingredient_id)->first()); + ->associate($input['ingredients']['type'][$key]::where('id', $ingredient_id)->first()); } $recipe->ingredientAmounts()->saveMany($ingredient_amounts); diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index a77575f..61f92e4 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -78,11 +78,6 @@ class Recipe extends Model 'sodiumPerServing', ]; - /** - * @inheritdoc - */ - protected $with = ['ingredientAmounts']; - /** * Get the steps for this Recipe. */ diff --git a/app/Models/Traits/Ingredient.php b/app/Models/Traits/Ingredient.php index 07e05ef..eecfbcd 100644 --- a/app/Models/Traits/Ingredient.php +++ b/app/Models/Traits/Ingredient.php @@ -8,6 +8,22 @@ use Illuminate\Support\Collection; trait Ingredient { + /** + * Add special `type` attribute to appends. + */ + public function initializeIngredient(): void { + $this->appends[] = 'type'; + } + + /** + * Gets the class name. + * + * This is necessary e.g. to provide data in ingredient picker responses. + */ + public function getTypeAttribute(): string { + return $this::class; + } + /** * Get all of the ingredient amounts associated with the ingredient. */ diff --git a/app/Rules/UsesIngredientTrait.php b/app/Rules/UsesIngredientTrait.php new file mode 100644 index 0000000..3f50ec1 --- /dev/null +++ b/app/Rules/UsesIngredientTrait.php @@ -0,0 +1,34 @@ + +
+ x-bind:data-type="result.type" + x-bind:data-name="result.name">
-
+
+ +
@@ -54,7 +61,10 @@ if ($event.target.value !== '') { fetch('{{ route('ingredient-picker.search') }}?term=' + $event.target.value) .then(response => response.json()) - .then(data => { this.results = data; this.searching = true; }); + .then(data => { + this.results = data; + this.searching = true; + }); } }, ['@focusout.debounce.200ms']() { @@ -65,8 +75,9 @@ ['@click']($event) { let selected = $event.target; if (selected.dataset.id) { - this.$refs.ingredients_name.value = selected.dataset.value; this.$refs.ingredients.value = selected.dataset.id; + this.$refs.ingredients_type.value = selected.dataset.type; + this.$refs.ingredients_name.value = selected.dataset.name; this.searching = false; } } diff --git a/resources/views/recipes/partials/ingredient-input.blade.php b/resources/views/recipes/partials/ingredient-input.blade.php index 86a8f9b..be77073 100644 --- a/resources/views/recipes/partials/ingredient-input.blade.php +++ b/resources/views/recipes/partials/ingredient-input.blade.php @@ -10,6 +10,7 @@