Skip to content

Commit

Permalink
refactor!: change defineFactory api
Browse files Browse the repository at this point in the history
- first argument must be the table name
- callback must only return the fields

See readme changes for concrete example
  • Loading branch information
Julien-R44 committed Sep 20, 2022
1 parent 2e135a8 commit 58e1f69
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 94 deletions.
43 changes: 16 additions & 27 deletions README.md
Expand Up @@ -81,15 +81,13 @@ This is useful when you want to cleanly disconnect from the database after all t
```ts
import type { User } from './my-user-interface.js'

const UserFactory = defineFactory<Partial<User>>(({ faker }) => ({
tableName: 'user',
fields: {
email: faker.internet.email(),
referralCode: faker.random.alphaNumeric(6)
},
const UserFactory = defineFactory<Partial<User>>('user', ({ faker }) => ({
email: faker.internet.email(),
referralCode: faker.random.alphaNumeric(6)
})).build()
```

The first parameter must be the table name.
Make sure that you return an object with all the required properties, otherwise the database will raise not null exceptions.

## Using factories
Expand Down Expand Up @@ -133,13 +131,10 @@ In the above example
Factory states allow you to define variations of your factories as states. For example: On a Post factory, you can have different states to represent published and draft posts.

```ts
export const PostFactory = defineFactory<Partial<Post>>(({ faker }) => ({
tableName: 'post',
fields: {
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(4),
status: 'DRAFT',
}
export const PostFactory = defineFactory<Partial<Post>>('post', ({ faker }) => ({
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(4),
status: 'DRAFT',
}))
.state('published', (attributes) => ({
status: 'PUBLISHED', // 馃憟
Expand All @@ -159,26 +154,20 @@ await PostFactory.createMany(3)
Model factories makes it super simple to work with relationships. Consider the following example:

```ts
export const PostFactory = defineFactory<Partial<Post>>(({ faker }) => ({
tableName: 'post',
fields: {
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(4),
status: 'DRAFT',
}
export const PostFactory = defineFactory<Partial<Post>>('post', ({ faker }) => ({
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(4),
status: 'DRAFT',
}))
.state('published', (attributes) => ({
status: 'PUBLISHED', // 馃憟
}))
.build()

export const UserFactory = defineFactory<Partial<User>>(({ faker }) => ({
tableName: 'user',
fields: {
username: faker.internet.userName(),
email: faker.internet.email(),
password: faker.internet.password(),
}
export const UserFactory = defineFactory<Partial<User>>('user', ({ faker }) => ({
username: faker.internet.userName(),
email: faker.internet.email(),
password: faker.internet.password(),
}))
.hasMany('posts', { foreignKey: 'user_id', localKey: 'id', factory: () => PostFactory }) // 馃憟
.build()
Expand Down
8 changes: 2 additions & 6 deletions packages/core/src/builder/builder.ts
Expand Up @@ -131,16 +131,12 @@ export class Builder<
*/
public async createMany(count: number): Promise<Model[]> {
this.ensureFactoryConnectionIsSet(factorioConfig.knex)

let models: Record<string, any>[] = []
const { tableName } = this.factory.callback({ faker })

/**
* Generate fields for each row by calling the factory callback
*/
for (let i = 0; i < count; i++) {
models.push(this.factory.callback({ faker }).fields)
}
models = Array.from({ length: count }).map(() => this.factory.callback({ faker }))

/**
* Apply merge attributes
Expand All @@ -167,7 +163,7 @@ export class Builder<
*/
const res = await factorioConfig
.knex!.insert(decamelizeKeys(models))
.into(tableName)
.into(this.factory.tableName)
.returning('*')

/**
Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/contracts.ts
Expand Up @@ -9,10 +9,7 @@ type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
* Callback that must be passed to the `defineFactory` function.
*/
export type DefineFactoryCallback<T> = (args: { faker: typeof faker }) => {
tableName: string
fields: {
[K in keyof T]: T[K] | (() => T[K] | Promise<T[K]>)
}
[K in keyof T]: T[K] | (() => T[K] | Promise<T[K]>)
}

/**
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/index.ts
Expand Up @@ -7,6 +7,12 @@ export { defineFactorioConfig }
/**
* Define a new factory.
*/
export function defineFactory<T extends Record<string, any>>(cb: DefineFactoryCallback<T>) {
return new FactoryModel<T>(cb) as Omit<FactoryModel<T>, 'callback' | 'states' | 'relations'>
export function defineFactory<T extends Record<string, any>>(
table: string,
cb: DefineFactoryCallback<T>
) {
return new FactoryModel<T>(table, cb) as Omit<
FactoryModel<T>,
'callback' | 'states' | 'relations'
>
}
13 changes: 8 additions & 5 deletions packages/core/src/model.ts
@@ -1,4 +1,3 @@
import { faker } from '@faker-js/faker'
import { Builder } from './builder/builder'
import { RelationType } from './contracts'
import type {
Expand All @@ -24,15 +23,19 @@ export class FactoryModel<Model extends Record<string, any>, States extends stri
*/
public relations: Record<string, RelationshipMeta> = {}

constructor(callback: DefineFactoryCallback<Model>) {
/**
* The SQL table name for the model.
*/
public tableName: string

constructor(tableName: string, callback: DefineFactoryCallback<Model>) {
this.tableName = tableName
this.callback = callback
}

private addRelation(name: string, type: RelationType, meta: RelationshipMetaOptions) {
const { tableName } = this.callback({ faker })

this.relations[name] = {
foreignKey: `${tableName}_id`,
foreignKey: `${this.tableName}_id`,
localKey: 'id',
...meta,
type,
Expand Down
29 changes: 11 additions & 18 deletions tests-helpers/setup.ts
@@ -1,41 +1,34 @@
import { defineFactory } from '@julr/factorio'

export const ProfileFactory = defineFactory(({ faker }) => ({
tableName: 'profile',
fields: {
age: faker.datatype.number(),
email: faker.internet.email(),
},
export const ProfileFactory = defineFactory('profile', ({ faker }) => ({
age: faker.datatype.number(),
email: faker.internet.email(),
}))
.state('old', () => ({ age: '150' }))
.state('admin', () => ({ email: 'admin@admin.com' }))
.build()

export const PostFactory = defineFactory(({ faker }) => ({
tableName: 'post',
fields: { title: faker.lorem.sentence() },
export const PostFactory = defineFactory('post', ({ faker }) => ({
title: faker.lorem.sentence(),
}))
.state('nodeArticle', () => ({ title: 'NodeJS' }))
.build()

export const UserFactory = defineFactory<any>(({ faker }) => ({
tableName: 'user',
fields: { id: faker.datatype.number() },
export const UserFactory = defineFactory<any>('user', ({ faker }) => ({
id: faker.datatype.number(),
}))
.state('easyPassword', () => ({ password: 'easy' }))
.state('easyEmail', () => ({ email: 'easy@easy.com' }))
.hasOne('profile', { foreignKey: 'user_id', localKey: 'id', factory: () => ProfileFactory })
.hasMany('posts', { foreignKey: 'user_id', localKey: 'id', factory: () => PostFactory })
.build()

export const AdminFactory = defineFactory(({ faker }) => ({
tableName: 'admin',
fields: { id: faker.datatype.number() },
export const AdminFactory = defineFactory('admin', ({ faker }) => ({
id: faker.datatype.number(),
})).build()

export const AccountFactory = defineFactory(({ faker }) => ({
tableName: 'account',
fields: { name: faker.commerce.productName() },
export const AccountFactory = defineFactory('account', ({ faker }) => ({
name: faker.commerce.productName(),
}))
.belongsTo('user', { foreignKey: 'user_id', localKey: 'id', factory: () => UserFactory })
.belongsTo('admin', { foreignKey: 'admin_id', localKey: 'id', factory: () => AdminFactory })
Expand Down
33 changes: 12 additions & 21 deletions tests/core.spec.ts
Expand Up @@ -4,9 +4,9 @@ import { defineFactory } from '@julr/factorio'
import { DatabaseUtils } from '@julr/japa-database-plugin'
import { setupDb } from '../tests-helpers/db.js'

const UserFactory = defineFactory<any>(({ faker }) => ({
tableName: 'user',
fields: { email: faker.internet.email(), password: faker.random.alphaNumeric(6) },
const UserFactory = defineFactory<any>('user', ({ faker }) => ({
email: faker.internet.email(),
password: faker.random.alphaNumeric(6),
})).build()

test.group('factorio', (group) => {
Expand Down Expand Up @@ -61,20 +61,14 @@ test.group('factorio', (group) => {
})

test('create entity with nested inline relationship', async ({ expect, database }) => {
const userFactory = defineFactory(({ faker }) => ({
tableName: 'user',
fields: {
email: faker.internet.email(),
password: faker.random.alphaNumeric(6),
},
const userFactory = defineFactory('user', ({ faker }) => ({
email: faker.internet.email(),
password: faker.random.alphaNumeric(6),
})).build()

const postFactory = defineFactory(({ faker }) => ({
tableName: 'post',
fields: {
title: faker.company.bs(),
userId: () => userFactory.create(),
},
const postFactory = defineFactory('post', ({ faker }) => ({
title: faker.company.bs(),
userId: () => userFactory.create(),
})).build()

const post = await postFactory.create()
Expand All @@ -85,12 +79,9 @@ test.group('factorio', (group) => {
})

test('factory with state', async ({ database }) => {
const userFactory = defineFactory<any>(({ faker }) => ({
tableName: 'user',
fields: {
email: 'bonjour',
password: faker.random.alphaNumeric(6),
},
const userFactory = defineFactory<any>('user', ({ faker }) => ({
email: 'bonjour',
password: faker.random.alphaNumeric(6),
}))
.state('businessUser', (attributes) => ({
email: 'business@admin.com',
Expand Down
16 changes: 5 additions & 11 deletions tests/has_many.spec.ts
Expand Up @@ -81,19 +81,13 @@ test.group('HasMany', (group) => {
})

test('auto detect foreign and primary keys', async ({ database }) => {
const postFactory = defineFactory<any>(({ faker }) => ({
tableName: 'post',
fields: {
title: faker.company.bs(),
},
const postFactory = defineFactory<any>('post', ({ faker }) => ({
title: faker.company.bs(),
})).build()

const userFactory = defineFactory<any>(({ faker }) => ({
tableName: 'user',
fields: {
email: faker.internet.email(),
password: faker.random.alphaNumeric(6),
},
const userFactory = defineFactory<any>('user', ({ faker }) => ({
email: faker.internet.email(),
password: faker.random.alphaNumeric(6),
}))
.hasMany('post', { factory: () => postFactory })
.build()
Expand Down

0 comments on commit 58e1f69

Please sign in to comment.