nova-issues icon indicating copy to clipboard operation
nova-issues copied to clipboard

Using Select within BelongsToMany fields()

Open Norgul opened this issue 1 year ago • 6 comments

  • Laravel Version: v11.15.0
  • Nova Version: v4.34.3
  • PHP Version: 8.2.20
  • Database Driver & Version:
  • Operating System and Version: MacOS Sonoma 14.5 (23F79)
  • Browser type and version: Chrome Version 127.0.6533.90 (Official Build) (arm64)

Description:

Not sure if this is a bug or I implemented something wrong. I have a pivot table with additional FK (unit_id). If I use Select in fields() method pointing to that additional FK, saving the relation will fail even though unit_id is present in the payload in the failing request.

// migration
Schema::create('recipe_composition', function (Blueprint $table) {
    $table->id();
    $table->foreignId('parent_id')->constrained('products');
    $table->foreignId('child_id')->constrained('products');

    $table->foreignId('unit_id')->constrained();
    ...
});

// Product.php
public function children(): BelongsToMany
{
    return $this->belongsToMany(self::class, 'recipe_composition', 'parent_id', 'child_id')
        ->withPivot(RecipeComposition::WITH_PIVOT)
        ->using(RecipeComposition::class)
        ->with('children')
        ->withTimestamps();
}

// RecipeComposition.php pivot model
class RecipeComposition extends Pivot
{
    public const WITH_PIVOT = [
        'unit_id',
        'net_amount',
        'gross_amount',
        'note',
        'removable_on_sale',
    ];

    public function parent(): BelongsTo
    {
        return $this->belongsTo(Product::class, 'parent_id');
    }

    public function child(): BelongsTo
    {
        return $this->belongsTo(Product::class, 'child_id');
    }

    public function unit(): BelongsTo
    {
        return $this->belongsTo(Unit::class);
    }
}

// Nova relation
BelongsToMany::make('Recipe elements', 'children', Product::class)
    ->fields(fn() => [
        Select::make('Unit', 'unit_id')
            ->dependsOn('children', function (Select $field, NovaRequest $request, FormData $formData) {
                $productId = $formData->get('children');

                /** @var \App\Models\Tenant\Product $product */
                $product = \App\Models\Tenant\Product::query()->with('unit')->find($productId);

                if (!$product) {
                    $field->readonly();
                    return;
                }

                $field->options(fn() => collect()
                    ->merge(Collection::wrap($product->unit->ancestors))
                    ->merge(Collection::wrap($product->unit))
                    ->merge(Collection::wrap($product->unit->grandchildren))
                    ->pluck('label', 'id')
                );

                $field->value = $product->unit->id;
            }),
        ...
    ])
    ->singularLabel('Recipe or Goods'),

image

If however I replace Select with a hardcoded Text or Number field, it will have the same payload, but will go through.

Norgul avatar Aug 28 '24 17:08 Norgul

I found what is the issue (though this still might be a bug).

In dependsOn() this part is causing the issue:

if (!$product) {
    $field->readonly();
    return;
}

Even if I later set it as $field->readonly(false) it doesn't work. To solve it, I needed to remove that part so that I have this:

if (!$product) {
    return;
}

Is this normal behavior?

Norgul avatar Aug 28 '24 17:08 Norgul

Hi @Norgul 👋 Thanks for reaching out to us. What you describe here is expected behavior, inputs for fields marked as readonly are discarded in the backend for safety reasons. I am unsure what you are trying to achieve here, if you need some more directions in settings things up please describe your goal and I'll do my best 👌

jeremynikolic avatar Sep 02 '24 07:09 jeremynikolic

@jeremynikolic thanks for the answer.

I know readonly fields are discarded. What I think is a bug there though is the fact that even after I canceled readonly status for a field with $field->readonly(false), it would still treat it as such.

Norgul avatar Sep 02 '24 07:09 Norgul

Ah got you ! My bad, I missed that extra disabling readonly step 👍 I believe you are right, updating the readonly after initial setup may not be picked up.

I'll look into it 🕵️

jeremynikolic avatar Sep 02 '24 08:09 jeremynikolic

@Norgul I've been investigating this and the readonly change is actually well received and accounted for on the frontend 🤔

Something to check on your side, the screenshot you provided indicates your unit_id is not nullable therefore the form failing when not having value for it therefore your schema most likely needs adjusting

Looking at the code samples I am wondering if the models/resources setup may be the cause here. To go further, please provide a sample reproducing repository 🙏

jeremynikolic avatar Sep 06 '24 09:09 jeremynikolic

Not sure really. As you can see from payload submitted to the API, unit_id does get forwarded. So I assume once the request is submitted and gets to the controller, Nova discards the field. I will see if I catch anything else. Thanks

Norgul avatar Sep 11 '24 11:09 Norgul

I believe this is now solved with Immutable Field: https://nova.laravel.com/docs/v5/resources/fields#immutable-fields

crynobone avatar Jan 06 '25 05:01 crynobone