Transactions on Factories problem (V5)

In the User Model

@afterSave()
  public static async createRelatedUserBankHour(user: User) {
    await UserBankHour.create({ userCpf: user.cpf })
  }

UserFactory

export default Factory.define(User, async ({ faker }) => {
  return {
    cpf: generateCpf(), // This is the primary key
    name: faker.name.findName(),
    password: faker.internet.password(8),
    isActive: faker.random.boolean(),
  }
})
  .build()

When I try to use this:

const user = await UserFactory.create()
console.log(user)

An error appears insert into "user_bank_hours" ("created_at", "updated_at", "user_cpf") values ($1, $2, $3) returning "user_cpf" - insert or update on table "user_bank_hours" violates foreign key constraint "user_bank_hours_user_cpf_foreign".

The error is happening because the userBankHour foreign key is userCpf, however it does not exist in the bank (or at least it was not persisted), even using the @aftersave() decorator.

When I create a user out of factories, userBankHour is created normally. But when I do this through factories, it is done on transactions and that is why the error occurs.

If I make this change everything works.

@afterSave()
  public static async createRelatedUserBankHour(user: User) {
    await user.$trx?.commit() // this change
    await UserBankHour.create({ userCpf: user.cpf })
  }

But this way the code is very ugly, is there another way to commit the transaction to work in factories? Would that be a bug on Lucid?

Have you tried something like this?

const user = await UserFactory.make(); //make method creates the model instance without persisting it
await user.save(); //manually persist the user

I am not sure if it’s going to work, but you can give it a shot. However, if I were in your shoes, I would have approached things differently. I don’t think it’s a great idea to persist related objects with hooks, such as @afterSave(). What if the code to create a userBankHour fails after you have created a user already, how would you handle that?

I think relationships which are not mutually exclusive are best persisted with a single transaction, so that if anything goes wrong everything fails. Assuming that’s the case, you could have something like:

const user = new User()
const userBankHour  = new UserBankHour()
await user.related('user_bank_hours').save(userBankHour); 

And you could write your factories as follows:

const UserBankHourFactory = Factory.define(UserBankHour,({faker})=>{
return {}
}).build();

const UserFactory = Factory.define(User,({faker})=>{
return {}
}).relation('userBankHour',()=>UserBankHour)
.build();

And use it as follows:

await UserFactory.with('userBankHour').create();
1 Like

But in the controller, the creation of users is protected by transactions. This means that if afterSave fails, the user will be rolled back. Right?

I am not sure about that, but you can confirm it by throwing an error in the afterSave hook and check if there’s a rollback.