#BeforeInsert

9 messages · Page 1 of 1 (latest)

glad condor
#

currently i got a bit of a janky work around but i wanted to do things proper and use BeforeInsert
i think its best here to just post the account.entity and let it speak for its self

@Entity()
export class Account {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ type: 'enum', enum: Status, default: Status.ACTIVATED })
  status: Status

  @ManyToMany((type) => Role, (role) => role.users, {
    cascade: true,
  })
  @JoinTable()
  roles: Role[]
  // inject a default into the account
  @BeforeInsert()
  async setDefaultRole() {
    /* TODO: create the default role (if not exist already)
    add the "default" role too the account*/
  }
  // other unrelated columns
}

as you see in the BeforeInsert i'm trying to make sure the "default" role is created and the role is given before the account is created
the documentation isn't really the most helpful as it doesn't document what can and cant be done inside the BeforeInsert

dusty atlas
#

So the pretty cool thing is almost anything you want could be done inside @Before Insert.

Something I get great use out of is creating an Event Subscriber file to hold any functions that I want run on a specific entity. Take a look at the official docs -> https://typeorm.io/docs/advanced-topics/listeners-and-subscribers.

Also take a look at the TypeOrm source code docs for "EntitySubscriberInterface".

Here's an example that might help:

Say you've got your Account entity per your example.

Lets make an account.subscriber.ts file like so:

export class AccountSubscriber implements EntitySubscriberInterface<Account> {
  constructor(
    @InjectDataSource() private readonly dataSource: DataSource,
     private readonly userRoleService: UserRoleService
  ) {
    this.dataSource.subscribers.push(this)
  }
  listenTo() {
    return Account
  }
  
  async beforeInsert(event: InsertEvent<Account>): Promise<void> {
    try {
      /*
      * Any functionality you want!
      *
      * Example usage: 
      * 1. Retrieve the Account entity from the event.
      * 2. If the entity is missing, exit early (nothing to do).
      * 3. Use a service (e.g., userRoleService) to get one or more default roles.
      * 4. If no roles are found, exit early.
      * 5. Ensure the roles property is always an array, even if only one role is returned.
      * 6. Assign the roles to the entity before it is inserted.
      * 
      * Example implementation:
      * 
      *   const entity = event.entity as Account;
      *   if (!entity) return;
      *   const userRoles = await this.userRoleService.getDefaultUserRole();
      *   if (!userRoles) return;
      *   entity.roles = Array.isArray(userRoles) ? userRoles : [userRoles];
      * 
      * Any errors thrown in this method will prevent the insert from completing.
      * 
      */
    } catch (error) {
      throw error
    }
  }```
#

I personally think the EntitySubscriberInterface gives a lot more options than the @decorators.

#

Also, it means we can isolate our events in a specific file and not have to throw them in with the entity files.

glad condor
dusty atlas
# glad condor sorry for taking so long on a response i just got home the `BeforeInsert` should...

The TypeOrm docs pretty explicitly recommend against making db calls inside of listeners and instead recommend the approach I suggested of using an event subscriber if you want to use other services which make db calls.

I'm afraid I can't give any advice on performance here mainly because I have no idea what your DB structure/schema/indexing approach looks like. I guess it would depend on whether you're getting data by an indexed column, if you're using a connection pool, etc... If you're still at an early stage do try not to go down the 'premature optimisation' rabbit role. Just focus on getting it to work as a minimum viable product and then worry about optimising it.

Perhaps a better question to ask is do you really need to leverage event listeners/subscribers? If all you're doing is ensuring that each new Account record has at least one default role, then I'm not seeing any reason why you wouldn't put it into your AccountService create function by simply calling the RoleService that you'd export out of your RoleModule.

For instance:

export class AccountService {
  constructor(
    @InjectRepository(Account) private accountRepo: Repository<Account>,
    private readonly roleService: RoleService
  ) {}

  async createOne(createDto: CreateOneAccountDto, queryRunner?: QueryRunner): Promise<Account> {
      const qrAccountRepo = queryRunner ? queryRunner.manager.getRepository(Account) : this.accountRepo
      try {
      const userRoles = await this.roleService.getDefaultRoles()
      const recordToSave = qrAccountRepo.create({
      ...createDto,
      roles: userRoles
      })
      return await qrAccountRepo.save(recordToSave)
    } catch (error) {
      if(error instanceof BadRequestException){
        throw error
      } else {
        throw new InternalServerErrorException(error.message)
      }
    }
  }
}```

The main benefit of using event listeners/subscribers is to move any code bloat out of specific services when it makes sense.
dusty atlas
#

To add, if you want to create a "default" role that doesn't already exist then the same logic still applies. You can just call the roleService.createOne() function from your Account Service with the relevant params.

export class AccountService {
  constructor(
    @InjectRepository(Account) private accountRepo: Repository<Account>,
    private readonly roleService: RoleService
  ) {}

  async createOne(createDto: CreateOneAccountDto, queryRunner?: QueryRunner): Promise<Account> {
      const qrAccountRepo = queryRunner ? queryRunner.manager.getRepository(Account) : this.accountRepo
      try {
      const createRoleDto: CreateOneRoleDto = {
      roleName: "New Role Here",
      roleOtherDetail: "Other Detail Here".
      }
      const userRoles = await this.roleService.createOne(createDto: createRoleDto, queryRunner)
      const recordToSave = qrAccountRepo.create({
      ...createDto,
      roles: userRoles
      })
      return await qrAccountRepo.save(recordToSave)
    } catch (error) {
      if(error instanceof BadRequestException){
        throw error
      } else {
        throw new InternalServerErrorException(error.message)
      }
    }
  }
}```
#

But the above would definitely not need to be put into an event subscriber because then it would try to run, and thus create a default role record, each time a record is about to be inserted into the Account entity.

glad condor
#

Ok thanks for the response then i'm keeping what i currently got