poser icon indicating copy to clipboard operation
poser copied to clipboard

BelongsTo "Field 'product_id' doesn't have a default value" with UUID Primary Key

Open booni3 opened this issue 4 years ago ā€¢ 9 comments

I am having an issue with BelongsTo where no matter what I try, the product_id field is always missing, even alongside other identical relationships which are working. The only difference I can see is that one is an integer primary key and the other is a UUID primary key.

The magic of this package is great but it makes it really hard for me to work out what is going on here šŸ˜•

Relationships on OrderItem

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

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

Running the below always fails with product_id not being found, yet order_id fills fine, even though all relationships seem to be the same.

OrderItemFactory::new()
    ->forOrder(OrderFactory::new())
    ->forProduct(ProductFactory::new())
    ->create();

For completeness here are the migrations (stripped down):

Schema::create('products', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('title');
    $table->timestamps();
});

Schema::create('order_items', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->foreignId('order_id')->constrained();
    $table->uuid('product_id')->references('id')->on('products');
    $table->timestamps();
});

Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->foreignId('shipping_address_id')->constrained('order_addresses');
    $table->foreignId('billing_address_id')->nullable()->constrained('order_addresses');
});

booni3 avatar Jun 27 '20 17:06 booni3

OK I fixed it... really no idea what was causing it but I think it may have had something to do with a default method failing.

I just deleted all factories and started again and it worked.

Probably a typo or something but the magic of this package made it super hard to debug. I am not sure if there is any way that more specific exceptions can be thrown to help?

booni3 avatar Jun 27 '20 17:06 booni3

Hi! Thanks so much for the feedback. Iā€™m glad you were able to resolve it šŸ‘

I did have it in the roadmap to add better exceptions. Do you have any that come readily to mind (other than with defaults)?

lukeraymonddowning avatar Jun 27 '20 18:06 lukeraymonddowning

I may have actually spoken too soon on the above, but I don't think its a bug but rather poor understanding of how the package works behind the scenes.

In the above example, I was able to get that working by rewriting the classes (again not sure of the exact issue but probably a typo of some sorts).

However, if I go one level deeper I get the same issue with product_id not being found. I think this might be due to limitations on factories in general but your comments would be welcome.

This works:

OrderItemFactory::new()
    ->forOrder(OrderFactory::new())
    ->forProduct(ProductFactory::new())
    ->create();

These do not work:

OrderFactory::new()
    ->withOrderItems(3)
    ->create();

OrderFactory::new()
    ->withOrderItems(OrderItemFactory::times(3))
    ->create();

// All defaults removed from OrderItemFactory class
OrderFactory::new()
    ->withOrderItems(OrderItemFactory::times(3)->forProduct())
    ->create();

// I noticed in the docs at one point you say that inner belongsTo relationships must be saved, but this does not work either.
OrderFactory::new()
    ->withOrderItems(OrderItemFactory::times(3)->forProduct()->create())
    ->create();

In this last example, I would expect 1 order to be created, along with 3 order items, then each of these orderItems should have a product as in the first working example.

The only way I have been able to resolve this so far is by adding an afterMaking closure to the orderItem laravel factory

$factory->afterMaking(OrderItem::class, function (OrderItem $orderItem, $faker) {
    if (! $orderItem->product_id) {
        $orderItem->product_id = factory(Product::class)->create()->id;
    }
});

booni3 avatar Jun 28 '20 10:06 booni3

Think I may be facing same issue...

designvoid avatar Jul 02 '20 21:07 designvoid

Okay, I'm going to reopen this issue. I'll try and take a look over the weekend.

lukeraymonddowning avatar Jul 03 '20 07:07 lukeraymonddowning

For me, it's just the confusion of the nested relationships.

i.e.

// This does not work when calling ModelAFactory::new();
ModelA -> hasMany -> ModelB's -> belongsTo -> ModelC
// but extracting out the last relationship which fails above, and it works
// ModelBFactory::new()
ModelB -> belongsTo -> ModelC

When trying to create a factory to deal with the whole lot, it does not seem to like creating ModelC and linking it to ModelB. I imagine this is due to the ordering that things happen and that this model needs to be created with a row and an ID in the table before the next level up can be made.

Possibly in the same way as if you tried to use an afterCreating vs. afterMaking callback in a traditional factory. You have to use the after making callback or its already too late.

booni3 avatar Jul 03 '20 09:07 booni3

agreed @booni3 I saw the same behaviour, deeply nested fails but extracting the failing portion and running in isolation works aok.

designvoid avatar Jul 03 '20 09:07 designvoid

To add a little more context:

// This works
OrderFactory::new()
    ->withOrderItems(
        OrderItemFactory::times(10)
    )();

// This works
$product = ProductFactory::new()->create();
OrderFactory::new()
    ->withOrderItems(
        OrderItemFactory::new()->make(['product_id' => $product->id])
    )();

// This does not work
$product = ProductFactory::new()->create();
OrderFactory::new()
    ->withOrderItems(
        OrderItemFactory::new()->forProduct($product)
    )();

booni3 avatar Jul 14 '20 10:07 booni3

@booni3 @designvoid do either of you have the capacity to take a look at this? I'm a little worked up this week. If not, no worries, I can take it, but it'll have to be next week.

lukeraymonddowning avatar Jul 14 '20 15:07 lukeraymonddowning