Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues with 'with()' #19

Closed
rtrochim opened this issue Jul 27, 2020 · 14 comments
Closed

Issues with 'with()' #19

rtrochim opened this issue Jul 27, 2020 · 14 comments

Comments

@rtrochim
Copy link

I have models: Customers, Invoices and DeliveryAddresses

CustomersTable.php:

 public function initialize(array $config): void
    {
        $this->hasMany('Invoices', [
            'foreignKey' => 'customer_id',
        ]);
    }

InvoicesTable.php:

    public function initialize(array $config): void
    {
        $this->belongsTo('Customers', [
            'foreignKey' => 'customer_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('DeliveryAddresses', [
            'foreignKey' => 'delivery_address_id',
        ]);
    }

DeliveryAddressesTable.php

    public function initialize(array $config): void
    {
        $this->hasOne('Invoices', [
            'foreignKey' => 'delivery_address_id',
        ]);
    }

I baked my factories as in the docs

In one of my test cases, when i do:

CustomerFactory::make()->withInvoices()->persist();

I get both models saved correctly

However if i do:

InvoiceFactory::make()->withCustomers()->persist();

I get the following error:

CakephpFixtureFactories\Error\PersistenceException : Error in Factory App\Test\Factory\InvoiceFactory.
Message: SQLSTATE[HY000]: General error: 1364 Field 'customer_id' doesn't have a default value

What am i doing wrong?

Another issue is when i try to make a deeper association.
As in the tutorial

CustomerFactory::make()->with('Invoices.DeliveryAddresses')->persist();

The Customer and Invoice are correctly saved. However delivery_address_id can be NULL, so the DeliveryAddress record is not created, and delivery_address_id in Invoice is NULL. When i force it to NOT NULL in database, i get the following error:

CakephpFixtureFactories\Error\PersistenceException : Error in Factory App\Test\Factory\CustomerFactory.
Message: SQLSTATE[HY000]: General error: 1364 Field 'delivery_address_id' doesn't have a default value

How do i make this work?
Thanks in advance!

@rtrochim rtrochim changed the title Issues with associated data Issues with 'with()' Jul 27, 2020
@pabloelcolombiano
Copy link
Collaborator

Hi,

A question before I further dig: did you modify the factories after baking them? If yes, please kindly share the code of all 3 factories. This will help me provide you a solution to your two issues.

@rtrochim
Copy link
Author

rtrochim commented Jul 27, 2020

InvoicesFactory was not modified, I only added some default data to CustomersFactory using faker such as username, password etc. I will add the exact code later this day

@pabloelcolombiano
Copy link
Collaborator

  1. Helpful would be the output of
    InvoiceFactory::make()->withCustomers()->getEntity()->toArray();
    and
    CustomerFactory::make()->with('Invoices.DeliveryAddresses')->getEntity()->toArray();

  2. Are there some required fields in the invoices table, that you did not populate?

The saving of belongsTo associations is covered by the package, so I do not see for the moment where the issue comes from.

The package's tests cover a similar case here:
https://github.com/pakacuda/cakephp-fixture-factories/blob/master/tests/TestApp/src/Model/Table/AddressesTable.php

https://github.com/pakacuda/cakephp-fixture-factories/blob/master/tests/Factory/AddressFactory.php

Where the AddressesTable belongs to a City.

Tests pass when calling AddressFactory::make()->withCity()->persist();

For example in this test:
https://github.com/pakacuda/cakephp-fixture-factories/blob/master/tests/TestCase/Factory/BaseFactoryTest.php#L690

  1. The only difference I see, is that the associated model is singular cased. This should not be an issue, but you may try calling your association in the InvoicesTable Customer instead of Customers, and thus in the InvoiceFactory name the method withCustomer instead of withCustomers. This reflects the model's logic a bit clearer. But again, it should work without doing that.

@rtrochim
Copy link
Author

Here are my three factories:

InvoiceFactory.php

class InvoiceFactory extends CakephpBaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'Invoices';
    }
    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function(Generator $faker) {
            return [];
        });
    }
    public function withCustomers(array $parameter = null): InvoiceFactory
    {
        return $this->with(
            'Customers',
            \App\Test\Factory\CustomerFactory::make($parameter)
        );
    }
    public function withDeliveryAddresses(array $parameter = null): InvoiceFactory
    {
        return $this->with(
            'DeliveryAddresses',
            \App\Test\Factory\DeliveryAddressFactory::make($parameter)
        );
    }
}

CustomerFactory.php

class CustomerFactory extends CakephpBaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'Customers';
    }
    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function(Generator $faker) {
            return [
                'name' => $faker->name,
                'login' => $faker->userName,
                'password' => $faker->password
            ];
        });
    }
    public function withInvoices(array $parameter = null, int $n = 1): CustomerFactory
    {
        return $this->with(
            'Invoices',
            \App\Test\Factory\InvoiceFactory::make($parameter, $n)->without('Customers')
        );
    }
}

DeliveryAddressFactory.php

class DeliveryAddressFactory extends CakephpBaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'DeliveryAddresses';
    }
    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function(Generator $faker) {
            return [
                'address' => $faker->address
            ];
        });
    }
    public function withInvoices(array $parameter = null): DeliveryAddressFactory
    {
        return $this->with(
            'Invoices',
            \App\Test\Factory\InvoiceFactory::make($parameter)
        );
    }
}

Here are requested outputs:

InvoiceFactory::make()->withCustomers()->getEntity()->toArray();

results in an empty array

[]

The line below

CustomerFactory::make()->with('Invoices.DeliveryAddresses')->getEntity()->toArray();

results in

   [
     "name" => "John Doe",
     "login" => "bernhard.gilda",
     "password" => "$2y$10$5snFBm8XALfTU6AKCQxy4eZ6KXD0NqLGdCEnNwQpOesfKZTG3PmH2",
     "invoices" => [
       [],
     ],
   ]

My invoices table for now only has the following fields: id, customer_id, delivery_address_id, created, modified. Only the first two are required.

I do not really understand your third point. CakePHP conventions are clear that association names are plural and so are their table names. If i singularize my association from Customers to Customer, Cake will look for the table called customer instead of customers which produces errors.

@pabloelcolombiano
Copy link
Collaborator

Regarding the third point, you simply have to set the key className to customers, and the association Customer will work. This is illustrated in the examples I sent you.

@rtrochim
Copy link
Author

All right i managed to resolve the first issue with renaming the association as you pointed out. Now it gets correctly saved.
However singularizing the associations in InvoicesTable and DeliveryAddressesTable did not work as expected, the DeliveryAddress record is still not saved when using.

CustomerFactory::make()->with('Invoices.DeliveryAddresses')->persist();

@rtrochim
Copy link
Author

I found out that when using

DeliveryAddressFactory::make()->with('Invoices.Customer')->persist();

All three entities are correctly saved. I have no idea why it does not work the other way.

@pabloelcolombiano
Copy link
Collaborator

How about if you singularize the association DeliveryAddresses to DeliveryAddress?

There might be an issue in the package, the saving should work regardless of the name of the association. But if this worked, you could at least keep on working for the moment. I'll be able to search for a bug only by next week.

@rtrochim
Copy link
Author

Tried that, no effect

@rtrochim
Copy link
Author

rtrochim commented Jul 29, 2020

I found out that association saving really does depend heavily on association name. Especially whether it's plural or singular. After setting all properties in my entities to accessible with

    protected $_accessible = [
        '*'=> true
    ]

i noticed that sometimes the association ends as an array istead of an entity instance with incorrect property name.

I would seem that FixtureFactory does not extract the correct property name from association, especially hasOne/belongsTo.

@dreamingmind
Copy link
Contributor

I have found that the native singularization/pluralization of 'Address/Addresses' is un predictable. I've had to add this rule:

Inflector::rules('singular', ['/^(.*)(address)$/i' => '\1\2']);

and make sure it gets run in both my app and the testing bootstrap

@pabloelcolombiano
Copy link
Collaborator

@rtrochim Do you have a concrete example illustrating that?

I agree with dreamingmind.

@pabloelcolombiano
Copy link
Collaborator

I have fixed the issue of non saved plural toOne association.

As described in the documentation here:

https://book.cakephp.org/3/en/orm/saving-data.html#saving-belongsto-associations

the associated data should be singular, which the package did not consider.

This is now fixed, you may update the package with its latest version.

I personally prefer naming my toOne associations singular, but if you don't, feel free rename your associations plural, and please let me know if it worked for you.

I will close this issue in three days, if this alright for you.

@rtrochim
Copy link
Author

rtrochim commented Aug 3, 2020

Everything now works correctly.
Thank you for your time and contribution. This plugin really does ease handling test data.
Cheers!

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

No branches or pull requests

3 participants