thoughtbot/fishery

Factory extensions should provide generatorOptions as a parameter

JohnKulp opened this issue · 1 comments

This would be extremely helpful for building a shared factory passed between an objection-using client and a non-objection using client. The database one would have certain associations or transient params relating to the relational properties defined by its objection model and used within create hooks that tell the database how to save the built object.

For example, say you have a rectangle factory and a canvasFactory that contains rectangles. This factory takes max and min values for its boundaries.

const rectangleFactory = Factory.define<Rectangle, RectangleTransientParams>(({transientParams, associations}) => {
	const min = transientParams.min || 0;
	const max = transientParams.max || 5;
	const x = faker.datatype.number({min, max});
	const y = faker.datatype.number({min, max});
	const x2 = faker.datatype.number({min, max});
	const y2 = faker.datatype.number({min, max});
	return {
		x: Math.min(x, x2),
		y: Math.min(y, y2),
		w: Math.abs(x2 - x),
		h: Math.abs(y2 - y),
		canvasId: associations.canvas ? associations.canvas.id : undefined,
	}
});

Now, lets say that a rectangle has a canvasId that references its parent canvas. It must have a parent canvas. However, some things that utilize this rectangle don't have database access to save these values for the test runner. For those, we extend the factory with a nice .withFakeDatabaseValues() hook that fills in those values, and we're good to go.

However, in database land, we have an issue. We want to extend the factory like this to allow us to create rectangles with their dependency trees in tact without having to create every member of the dependency tree explicitly beforehand. So, we want something like this:

rectangleFactory.onCreate(async rect => {
	if(!rect.canvasId){
		const newCanvas = await canvasFactory.create({transientParams: {max: transientParams.max, min: Math.max(rect.x+rect.w, rect.y+rect.h)}});
		rect.canvasId = newCanvas.id;
	}
	return await Rectangle.query().insert(rect).returning('*');
})

Our canvas has transient params for a maximum dimension size and for a minimum dimension size just like the rect does. The minimum size is determined by the rect, since it has to contain the rect, but we don't want it always wrapping exactly at the edge of the rect. So the maximum is based on the original maximum fed to the rect.

Currently, there's no way to get that transientParam outside of the original define() call for rectangle factory, making this database code have to be dependency injected into a factory generator function that ends up being really weird and ugly. I personally think that the generatorOptions that are given to define should just be passed to the extensions.

rectangleFactory.onCreate(async (rect, {transientParams}) => {
	if(!rect.canvasId){
		const newCanvas = await canvasFactory.create({transientParams: {max: transientParams.max, min: Math.max(rect.x+rect.w, rect.y+rect.h)}});
		rect.canvasId = newCanvas.id;
	}
	return await Rectangle.query().insert(rect).returning('*');
})

I was also surprised today that transientParams were not available in onCreate. 😞