Skip to content

Commit

Permalink
Merge pull request #49 from Quramy/create_list
Browse files Browse the repository at this point in the history
feat: Add createList and buildList method
  • Loading branch information
Quramy committed Nov 27, 2022
2 parents 9a359d4 + 07bd7a2 commit ffc6116
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 71 deletions.
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Prisma generator for model factories.
- [Usage of factories](#usage-of-factories)
- [Field default values](#field-default-values)
- [Use sequence for scalar fields](#use-sequence-for-scalar-fields)
- [Shorthand for create list](#shorthand-for-create-list)
- [Required relation](#required-relation)
- [Connection helper](#connection-helper)
- [Build input data only](#build-input-data-only)
Expand Down Expand Up @@ -153,6 +154,24 @@ import { resetSequence } from "./__generated__/fabbrica";
beforeEach(() => resetSequence());
```

### Shorthand for create list

Each factory provides `.createList` method to insert multiple records.

```ts
await UserFactory.createList(3);

// The above code is equivalent to the following

await Promise.all([0, 1, 2].map(() => UserFactory.create()));
```

You can also pass list data assignable to `Partial<Prisma.UserCreateInput>[]` :

```ts
await UserFactory.createList([{ id: "user01" }, { id: "user02" }]);
```

### Required relation

Sometimes, creating a model requires other model existence. For example, the following model `Post` belongs to other model `User`.
Expand Down Expand Up @@ -230,17 +249,17 @@ console.log(posts.length); // -> 2

### Build input data only

`.buildCreateInput` method in factories provides data set to create the model, but never insert.
`.build` method in factories provides data set to create the model, but never insert.

```ts
await UserFactory.create();

// The above code is equivalent to the bellow:
const data = await UserFactory.buildCreateInput();
const data = await UserFactory.build();
await prisma.user.create({ data });
```

For example, you can use `.buildCreateInput` method in other model's factory definition:
For example, you can use `.build` method in other model's factory definition:

```ts
const UserFactory = defineUserFactory();
Expand All @@ -252,7 +271,7 @@ const PostFactory = definePostFactory({
where: {
id: "user001",
},
create: await UserFactory.buildCreateInput({
create: await UserFactory.build({
id: "user001",
}),
},
Expand All @@ -266,21 +285,23 @@ await PostFactory.create();
console.log(await prisma.user.count()); // -> 1
```

Like `createList`, `buildList` is also available.

### has-many / has-one relation

Sometimes, you may want a user data whose has post record. You can use `PostFactory.buildCreateInput` too.
Sometimes, you may want a user data whose has post record. You can use `PostFactory.build` or `PostFactory.buildList` .

```ts
await UserFactory.create({
posts: {
create: [await PostFactory.buildCreateInput()],
create: await PostFactory.buildList(2),
},
});

console.log(await prisma.post.count()); // -> 1
console.log(await prisma.post.count()); // -> 2
```

Note: In the above example, `PostFactory.buildCreateInput()` resolves JSON data such as:
Note: In the above example, `PostFactory.build()` resolves JSON data such as:

```ts
{
Expand Down
8 changes: 7 additions & 1 deletion examples/example-prj/src/__generated__/fabbrica/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 29 additions & 7 deletions examples/example-prj/src/__generated__/fabbrica/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions examples/example-prj/src/connectOrCreate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const PostFactory = definePostFactory({
where: {
id: "user001",
},
create: await UserFactory.buildCreateInput({
create: await UserFactory.build({
id: "user001",
}),
},
Expand All @@ -27,9 +27,7 @@ describe("factories", () => {
});

it("creates related user at most one", async () => {
await PostFactory.create();
await PostFactory.create();
await PostFactory.create();
await PostFactory.createList(3);
await expect(prisma.user.count()).resolves.toBe(1);
});
});
Expand Down
4 changes: 2 additions & 2 deletions examples/example-prj/src/sample.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ describe("factories", () => {
it("creates record with children relation", async () => {
await UserFactory.create({
posts: {
create: [await PostFactory.buildCreateInput()],
create: await PostFactory.buildList(2),
},
});
const created = await prisma.user.findFirst({ include: { posts: true } });
expect(created?.posts.length).toBe(1);
expect(created?.posts.length).toBe(2);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFac
const seqKey = {};
const getSeq = () => getSequenceCounter(seqKey);
const screen = createScreener("User", modelFieldDefinitions);
const buildCreateInput = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const build = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const seq = getSeq();
const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq });
const resolveValue = normalizeResolver<UserFactoryDefineInput, BuildDataOptions>(defaultDataResolver ?? {});
Expand All @@ -48,19 +48,30 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFac
const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData };
return data;
};
const buildList = (inputData: number | Partial<Prisma.UserCreateInput>[]) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => build(data)));
};
const pickForConnect = (inputData: User) => ({
id: inputData.id
});
const create = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const data = await buildCreateInput(inputData).then(screen);
const data = await build(inputData).then(screen);
return await getClient<PrismaClient>().user.create({ data });
};
const createList = (inputData: number | Partial<Prisma.UserCreateInput>[]) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => create(data)));
};
const createForConnect = (inputData: Partial<Prisma.UserCreateInput> = {}) => create(inputData).then(pickForConnect);
return {
_factoryFor: "User" as const,
buildCreateInput,
build,
buildList,
buildCreateInput: build,
pickForConnect,
create,
createList,
createForConnect,
};
}
Expand Down
28 changes: 22 additions & 6 deletions packages/prisma-fabbrica/src/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ export const modelBelongsToRelationFactory = (fieldType: DMMF.SchemaArg, model:
return template.statement<ts.TypeAliasDeclaration>`
type ${() => ast.identifier(`${model.name}${fieldType.name}Factory`)} = {
_factoryFor: ${() => ast.literalTypeNode(ast.stringLiteral(targetModel.type))};
buildCreateInput: () => PromiseLike<Prisma.${() =>
ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>;
build: () => PromiseLike<Prisma.${() => ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>;
};
`();
};
Expand Down Expand Up @@ -272,7 +271,7 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
const getSeq = () => getSequenceCounter(seqKey);
const screen = createScreener(${() => ast.stringLiteral(model.name)}, modelFieldDefinitions);
const buildCreateInput = async (
const build = async (
inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}
) => {
const seq = getSeq();
Expand All @@ -286,7 +285,7 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
field.name,
template.expression`
IS_MODEL_BELONGS_TO_RELATION_FACTORY(defaultData.FIELD_NAME) ? {
create: await defaultData.FIELD_NAME.buildCreateInput()
create: await defaultData.FIELD_NAME.build()
} : defaultData.FIELD_NAME
`({
IS_MODEL_BELONGS_TO_RELATION_FACTORY: ast.identifier(`is${model.name}${field.name}Factory`),
Expand All @@ -300,6 +299,13 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
return data;
};
const buildList = (
inputData: number | Partial<Prisma.MODEL_CREATE_INPUT>[]
) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => build(data)));
}
const pickForConnect = (inputData: ${() => ast.typeReferenceNode(model.name)}) => (
${() =>
ast.objectLiteralExpression(
Expand All @@ -313,17 +319,27 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
const create = async (
inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}
) => {
const data = await buildCreateInput(inputData).then(screen);
const data = await build(inputData).then(screen);
return await getClient<PrismaClient>().MODEL_KEY.create({ data });
};
const createList = (
inputData: number | Partial<Prisma.MODEL_CREATE_INPUT>[]
) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => create(data)));
}
const createForConnect = (inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}) => create(inputData).then(pickForConnect);
return {
_factoryFor: ${() => ast.stringLiteral(model.name)} as const,
buildCreateInput,
build,
buildList,
buildCreateInput: build,
pickForConnect,
create,
createList,
createForConnect,
};
}
Expand Down
Loading

0 comments on commit ffc6116

Please sign in to comment.