Skip to content

Commit

Permalink
feat: support cross-references factories
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 committed Sep 20, 2022
1 parent f3ec7db commit 1d53f01
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 13 deletions.
18 changes: 15 additions & 3 deletions packages/core/src/builder/builder.ts
Expand Up @@ -22,6 +22,13 @@ export class Builder<
this.statesManager = new StatesManager(factory)
}

/**
* If the builder is at its initial state
*/
// @ts-expect-error isReset is used in reset method.
// Take a look at the RelationshipMeta contract for more details about why.
private isReset = true

/**
* The attributes that will be merged for the next created models.
*/
Expand Down Expand Up @@ -84,12 +91,16 @@ export class Builder<
/**
* Reset the builder to its initial state.
*/
private resetBuilder() {
private reset() {
this.isReset = true
this.mergeInput = []
this.statesManager.reset()
this.relationshipBuilder.reset()

Object.values(this.factory.relations).forEach((relation) => relation.factory().resetBuilder())
Object.values(this.factory.relations).forEach((relation) => {
const factory = relation.factory()
if (factory.isReset === false) factory.reset()
})
}

/**
Expand Down Expand Up @@ -129,6 +140,7 @@ export class Builder<
* Create multiple models and persist them to the database.
*/
public async createMany(count: number): Promise<Model[]> {
this.isReset = false
this.ensureFactoryConnectionIsSet(factorioConfig.knex)
let models: Record<string, any>[] = []

Expand Down Expand Up @@ -175,7 +187,7 @@ export class Builder<
*/
const finalModels = this.relationshipBuilder.postHydrate(res)

this.resetBuilder()
this.reset()

return finalModels.map((model) => convertCase(model, factorioConfig.casing.return)) as Model[]
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/contracts.ts
Expand Up @@ -93,8 +93,14 @@ export interface RelationshipMeta {

/**
* Reference to the relation factory
*
* Note: Type is any because otherwise the circular dependency betweens
* two factories that are cross-referenced would totally break the type system.
*
* I don't know how to solve this problem yet. If you come up with a solution,
* or any ideas, please open a issue. Would be awesome to have this !
*/
factory: () => Builder<any, any, any>
factory: any
}

export type RelationshipMetaOptions = Optional<
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/model.ts
Expand Up @@ -43,8 +43,9 @@ export class FactoryModel<
type: RelationType,
meta?: RelationshipMetaOptions
) {
const foreignKey = type === RelationType.BelongsTo ? `${name}_id` : `${this.tableName}_id`
this.relations[name] = {
foreignKey: `${this.tableName}_id`,
foreignKey,
localKey: 'id',
factory,
...meta,
Expand Down
18 changes: 10 additions & 8 deletions tests-helpers/setup.ts
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { defineFactory } from '@julr/factorio'

export const ProfileFactory = defineFactory('profile', ({ faker }) => ({
Expand All @@ -14,22 +15,23 @@ export const PostFactory = defineFactory('post', ({ faker }) => ({
.state('nodeArticle', () => ({ title: 'NodeJS' }))
.build()

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

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

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

export const AccountFactory = defineFactory('account', ({ faker }) => ({
name: faker.commerce.productName(),
}))
.belongsTo('user', () => UserFactory, { foreignKey: 'user_id', localKey: 'id' })
.belongsTo('admin', () => AdminFactory, { foreignKey: 'admin_id', localKey: 'id' })
.belongsTo('user', () => UserFactory)
.belongsTo('admin', () => AdminFactory)
.build()
16 changes: 16 additions & 0 deletions tests/core.spec.ts
Expand Up @@ -2,6 +2,7 @@
import { test } from '@japa/runner'
import { defineFactory } from '@julr/factorio'
import { DatabaseUtils } from '@julr/japa-database-plugin'
import { AccountFactory, UserFactory as BaseUserFactory } from '../tests-helpers/setup.js'
import { setupDb } from '../tests-helpers/db.js'

const UserFactory = defineFactory<any>('user', ({ faker }) => ({
Expand Down Expand Up @@ -99,4 +100,19 @@ test.group('factorio', (group) => {
await userFactory.apply('admin').create()
await database.assertHas('user', { email: 'admin@admin.admin', password: 'topsecret' })
})

test('cross reference', async ({ database }) => {
const account = await AccountFactory.with('user').create()

await database.assertHas('user', { id: account.userId })
await database.assertHas('account', { id: account.id })

const user = await BaseUserFactory.with('account').create()

await database.assertHas('user', { id: user.id })
await database.assertHas('account', { id: user.account.id })

await database.assertCount('user', 2)
await database.assertCount('account', 2)
})
})

0 comments on commit 1d53f01

Please sign in to comment.