jorgebodega / typeorm-factory

:seedling: A delightful way to use factories in your code.
https://www.npmjs.com/package/@jorgebodega/typeorm-factory
MIT License
29 stars 10 forks source link

Override Params to influence SubFactory #88

Closed flowluap closed 1 year ago

flowluap commented 1 year ago

We have the following (simplified) case:

@Entity()
export class MProductVariant extends TimestampedEntity implements ProductVariantData {
    @PrimaryColumn(UuidPrimaryColumn)
    public id!: Uuid;

    @Column({ nullable: true })
    public title?: string;

    @Column(UuidColumn)
    public productId!: Uuid;

    @ManyToOne(() => MProduct, (product) => product.variants)
    @JoinColumn({
        name: 'productId'
    })
    public product?: MProduct;
}
@Entity()
export class MProduct extends TimestampedEntity implements ProductData {
    @PrimaryColumn(UuidPrimaryColumn)
    public id!: Uuid;

    @Column()
    public title?: string;

    @OneToMany(() => MProductVariant, (variant) => variant.product, {
    })
    public variants?: MProductVariant[];
}

In the past we had the following factory, based on the context:

define(MProduct, () => {
    return new MProduct({
        id: generateTestUuid(),
        title: '[TEST] ' + faker.commerce.product()
    });
});
define(MProductVariant, (_, context: { baseProduct: MProduct } | undefined) => {
    return new MProductVariant({
        id: generateTestUuid(),
        title: productName,
        productId: context?.baseProduct.id
    });
});

If i want to replicate this with your lib, i see the possibility to use a SubFactory. Is there a way to input parameters into the factory, that then will let you decide to form a SubFactory or use the passed parameters like that:

export class ProductVariantFactory extends Factory<MProductVariant> {
    protected entity = MProductVariant;
    protected dataSource = CoreDataSource;

    protected attrs(context): FactorizedAttrs<MProductVariant> {

        const product = context.product ? context.product : new SingleSubfactory(ProductFactory,{})

        return {
            id: generateTestUuid(),
            productId: product.id
        };
    }
};

Thanks for your efforts!

flowluap commented 1 year ago

@jorgebodega At first look the instance looks good, but there is no way to execute (neiter eager, nor lazy) before execution of self.

So how would you propose to build this the intended way?

jorgebodega commented 1 year ago

Hi! Some points:

  1. attrs is a protected function, so it cannot be accessed from an instance. I suppose you know, but just in case.
  2. attrs is just to set the default values for mandatory or desired values.
  3. A Subfactory class is just a helper, is not mandatory to use. Just helps to avoid to take control of create/make.
  4. The second parameter of a Subfactory is optional.

I removed context because I see no use case. Just some different cases:

If you need to pass the context in every execution, just use them as parameters:

new ProductVariantFactory().create({ productId: product.id })

If you need a context for evey execution you can just override the factory constructor and use like:

export class ProductVariantFactory extends Factory<MProductVariant> {
    protected entity = MProductVariant;
    protected dataSource = CoreDataSource;

    constructor(private context: YourContext) {}

    protected attrs(context): FactorizedAttrs<MProductVariant> {

        const product = this.context.product ? this.context.product : new SingleSubfactory(ProductFactory,{})

        return {
            id: generateTestUuid(),
            productId: product.id
        };
    }
};

Or maybe superset the functions:

export class ProductVariantFactory extends Factory<MProductVariant> {
    ...

    public customCreate(...originalParams, context: YourContext) {
        return this.create({
            ...
        })
    }
};

But I would not reccomend neither of those. I think the best way is to use a InstanceAttribute, but if you comment me more, I could help better.

Hope it helps!