laravel-shift / blueprint

A code generation tool for Laravel developers.
MIT License
2.89k stars 273 forks source link

Attach relationships to models in factory definitions #32

Closed axit-joost closed 4 years ago

axit-joost commented 4 years ago

As per the Laravel documentation located at https://laravel.com/docs/master/database-testing#relationships, paragraph "Relations & Attribute Closures".

Given the following draft.yaml, modelled after a schema.org/Person:

models:
  Country:
    name: string
    isoAlpha2: string:2
    isoAlpha3: string:3
  ContactPoint:
    contactType: string
    email: string
    faxNumber: string
    telephone: string
  PostalAddress:
    country_id: id:country
    addressLocality: string
    addressRegion: string
    postalOfficeBoxNumber: string
    postalCode: string
    streetAddress: text
    contact_point_id: id:contact_point
  Person:
    additionalName: string
    address_id: id:postal_address
    familyName: string
    givenName: string

The generated factories will currently insert a $faker->randomDigitNotNull, like so:

$factory->define(Person::class, function (Faker $faker) {
    return [
        'additionalName' => $faker->word,
        'address_id' => $faker->randomDigitNotNull,
        'familyName' => $faker->word,
        'givenName' => $faker->word,
    ];
});

As per the documentation, it could be generated like so:

$factory->define(Person::class, function (Faker $faker) {
    return [
        'additionalName' => $faker->word,
        'address_id' => factory(App\PostalAddress::class),
        'familyName' => $faker->word,
        'givenName' => $faker->word,
    ];
});

Subsequently, the other PostalAddressFactory could be like so:

$factory->define(PostalAddress::class, function (Faker $faker) {
    return [
        'country_id' => factory(App\Country::class),
        'addressLocality' => $faker->city,
        'addressRegion' => $faker->state,
        'postalOfficeBoxNumber' => $faker->randomNumber(4),
        'postalCode' => $faker->postcode,
        'streetAddress' => $faker->streetAddress,
        'contact_point_id' => factory(App\ContactPoint::class),
    ];
});

The benefit of this, is that when using the PersonFactory in a test, you will get a Person with a full set of related data:

factory(App\Person::class, 100)->create();
App\Person::with('address.country', 'address.contactPoint')->first();

could yield:

> App\Person {#1062
     id: 1,
     additionalName: "Serena",
     address_id: 1,
     familyName: "Medhurst",
     givenName: "Andreane",
     created_at: "2019-12-06 15:13:14",
     updated_at: "2019-12-06 15:13:14",
     address: App\PostalAddress {#1042
       id: 1,
       country_id: 1,
       addressLocality: "Daniellaville",
       addressRegion: "Texas",
       postalOfficeBoxNumber: "6424",
       postalCode: "68138",
       streetAddress: "65044 Gutkowski Heights Apt. 741",
       contact_point_id: 1,
       created_at: "2019-12-06 15:13:14",
       updated_at: "2019-12-06 15:13:14",
       country: App\Country {#1018
         id: 1,
         name: "Canada",
         isoAlpha2: "PF",
         isoAlpha3: "GRL",
         created_at: "2019-12-06 15:13:14",
         updated_at: "2019-12-06 15:13:14",
       },
       contactPoint: App\ContactPoint {#1026
         id: 1,
         contactType: "Logistics",
         email: "christian.robel@example.com",
         faxNumber: "941-910-7739 x572",
         telephone: "1-368-817-4568",
         created_at: "2019-12-06 15:13:14",
         updated_at: "2019-12-06 15:13:14",
       },
     },
   }
jasonmccreary commented 4 years ago

Thanks for reporting this. I agree with this change.

Should be able to add an if check around this line to see if the data type is id. If so, generate the factory call using the column prefix or supplied reference.

You want to try and open a PR for it?

joostjacobs commented 4 years ago

Sure. I'll try and find a time tonight, see if I can cook something up.