Skip to content
This repository has been archived by the owner on Dec 3, 2020. It is now read-only.

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

Open
booni3 opened this issue Jun 27, 2020 · 9 comments

Comments

@booni3
Copy link

booni3 commented Jun 27, 2020

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
Copy link
Author

booni3 commented Jun 27, 2020

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 booni3 closed this as completed Jun 27, 2020
@lukeraymonddowning
Copy link
Owner

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)?

@booni3
Copy link
Author

booni3 commented Jun 28, 2020

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;
    }
});

@designvoid
Copy link

Think I may be facing same issue...

@lukeraymonddowning
Copy link
Owner

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

@booni3
Copy link
Author

booni3 commented Jul 3, 2020

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.

@designvoid
Copy link

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

@booni3
Copy link
Author

booni3 commented Jul 14, 2020

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)
    )();

@lukeraymonddowning
Copy link
Owner

@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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants