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

Deeply saving factory #209

Open
matiasgarcia opened this issue Feb 25, 2022 · 7 comments
Open

Deeply saving factory #209

matiasgarcia opened this issue Feb 25, 2022 · 7 comments

Comments

@matiasgarcia
Copy link

I have the following factories:

question.factory.ts

import Faker from '@faker-js/faker';
import { define, factory } from 'typeorm-seeding';
import { Answer } from '../../entities/answer.entity';
import { Question } from '../../entities/question.entity';

define(Question, (faker: typeof Faker) => {
  const question = new Question();
  question.question = faker.lorem.sentence() + '?';
  question.answers = factory(Answer)().makeMany(5) as any;
  question.private = faker.random.boolean();
  return question;
});

answer.factory.ts

import Faker from '@faker-js/faker';
import { define, factory } from 'typeorm-seeding';
import { Answer } from '../../entities/answer.entity';
import { User } from '../../entities/user.entity';

define(Answer, (faker: typeof Faker) => {
  const answer = new Answer();
  answer.answerer = factory(User)() as any;
  answer.answer = faker.lorem.sentence();
  return answer;
});

user.factory.ts

import Faker from '@faker-js/faker';
import { define } from 'typeorm-seeding';
import { User } from '../../entities/user.entity';

define(User, (faker: typeof Faker) => {
  const user = new User();
  const firstName = faker.name.firstName();
  const lastName = faker.name.lastName();
  user.name = `${firstName} ${lastName}`;
  return user;
});

And also I have a seed:

import { Factory, Seeder } from 'typeorm-seeding';
import { Connection } from 'typeorm';
import { Question } from '../../entities/question.entity';

export default class InitialDatabaseSeed implements Seeder {
  public async run(factory: Factory, connection: Connection): Promise<void> {
    const questions = await factory(Question)().createMany(15);
    console.log({ questions });
  }
}

When I run this, only the Questions records are stored, the Answers and User are ignored.

How can I persist them?

@jorgebodega
Copy link
Collaborator

This is related on how the related entities are being processed:

  if (isPromiseLike(entity[attribute])) {
    entity[attribute] = await entity[attribute]
  }
if (isSeeding) {
	entity[attribute] = await (subEntityFactory as any).create()
} else {
	entity[attribute] = await (subEntityFactory as any).make()
}

In your definition, Question is using makeMany to create Answers[], that uses the first code snippet, not the second one, and make operations does not persist info in database. And then, in Answer definition, a User is created using the second code snippet, but in this case, coming from makeMany operation, isSeeding is false.

The only way this could work is changing that makeMany to createMany or relating all the entities with cascade (I do not like to work with cascades on ORMs)

Also, please try to avoid using @faker-js/faker to type the faker param. They are completely different packages, and this could lead to some troubles. I recommend to use either one of the following:

import faker from '@faker-js/faker';

define(Answer, () => {
  const answer = new Answer();
  ...
  answer.answer = faker.lorem.sentence();
  ...
});
import Faker from 'faker';

define(Answer, (faker: typeof Faker) => {
  const answer = new Answer();
  ...
  answer.answer = faker.lorem.sentence();
  ...
});

@matiasgarcia
Copy link
Author

@jorgebodega Thanks for taking your time to answer.

I am wondering if there is any way to force the persistence of the Entity and its relations at the moment of running the seed and not before. I am trying to avoid forcing my factories to introduce side effects in the database until its strictly necessary (this is, replacing the make() calls with create()). Also I dont want to modify the Entity schema to have cascade: true.

Is that possible?

@jorgebodega
Copy link
Collaborator

I'm not understanding your problem, could you try to explain with code?

@matiasgarcia
Copy link
Author

matiasgarcia commented Feb 25, 2022

@jorgebodega Let's assume that I don't change the factories. I want them to NOT store values in the database. Just build the entities and its associations, nothing else.

However, in the seed, I would like to call my factory and tell it "save the entity and all its related records".

import { Factory, Seeder } from 'typeorm-seeding';
import { Connection } from 'typeorm';
import { Question } from '../../entities/question.entity';

export default class InitialDatabaseSeed implements Seeder {
  public async run(factory: Factory, connection: Connection): Promise<void> {
    const questions = await factory(Question)().createMany(15); // Is there a way to tell the ORM, save the Question and its associations?
  }
}

I would like to avoid adding cascade: true to the Entity, because that's a global side-effect I would like to avoid.

@matiasgarcia
Copy link
Author

I come from a Rails background, heavily using this factory gem called FactoryBot so maybe this is not possible in TypeORM nor with this lib.

@jorgebodega
Copy link
Collaborator

Hi, sorry about the delay, let me check this in the next few days and if is already fixed on the forked package

@matiasgarcia
Copy link
Author

matiasgarcia commented Mar 2, 2022

@jorgebodega no worries.

Just to clarify, in my codebase I temporarily replaced

define(Answer, (faker: typeof Faker) => {
  const answer = new Answer();
  answer.answerer = factory(User)() as any;
  answer.answer = faker.lorem.sentence();
  return answer;
});

with

  answer.answerer = factory(User)().create()

But I think it would be ideal that rather than doing that, I can just do

factory(Answer)().createWithAssociations()

And that somehow "hacks" TypeORM to store the associations first rather than being forced to add cascade: true to the Entity.

As mentioned, I am taking inspiration on https://github.com/thoughtbot/factory_bot that uses Rails' ActiveRecord and maybe this is something that cannot be done with TypeORM or requires a lot of work. I am a newbie in the JS backend world but maybe if you could guide me a little bit on what this would require I can try to make a proof of concept.

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

2 participants