Skip to content

Commit

Permalink
feat: implement core of the factories
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jun 20, 2020
1 parent 3d50f77 commit 5c65731
Show file tree
Hide file tree
Showing 16 changed files with 3,045 additions and 0 deletions.
216 changes: 216 additions & 0 deletions adonis-typings/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare module '@ioc:Adonis/Lucid/Factory' {
import { LucidRow, LucidModel } from '@ioc:Adonis/Lucid/Model'
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database'
import { ExtractModelRelations, RelationshipsContract, ModelRelations } from '@ioc:Adonis/Lucid/Relations'

export interface FactoryStateContract {
faker: any,
isStubbed: boolean,
$trx: TransactionClientContract | undefined
}

/**
* Function that create a new instance of a Lucid model with
* the given attributes.
*/
export type NewUpModelFunction<Model extends LucidModel, Attributes extends any> = (
state: FactoryStateContract,
attributes?: Attributes,
) => Promise<InstanceType<Model>> | InstanceType<Model>

/**
* Unwraps promise
*/
export type UnwrapPromise<T> = T extends PromiseLike<infer U> ? U : T

/**
* Extracts the model for a factory by inspecting the ReturnType of
* the `newUp` method
*/
export type ExtractFactoryModel<
T extends FactoryModelContract<LucidModel, any>
> = UnwrapPromise<ReturnType<T['newUp']>>

/**
* Extracts the attributes accepted by the newUp method of a factory
*/
export type ExtractFactoryAttributes<
T extends FactoryModelContract<LucidModel, any>
> = Parameters<T['newUp']>[1]

/**
* Callback to define a new model state
*/
export type ModelStateCallback<Model extends LucidRow> = (
model: Model,
state: FactoryStateContract,
) => any | Promise<any>

/**
* Factory builder uses the factory model to create/make
* instances of lucid models
*/
export interface FactoryBuilderContract<FactoryModel extends FactoryModelContract<LucidModel, any>> {
/**
* Apply pre-defined state
*/
apply<K extends keyof FactoryModel['states']> (...states: K[]): this

/**
* Create/make relationships for explicitly defined related factories
*/
with<K extends keyof FactoryModel['relations']> (
relation: K,
count?: number,
callback?: (
/**
* Receives the explicitly defined factory
*/
factory: FactoryModel['relations'][K] extends () => FactoryBuilderContract<any>
? ReturnType<FactoryModel['relations'][K]>
: never
) => void,
): this

/**
* Define custom set of attributes. They are passed to the `newUp` method
* of the factory.
*
* For `createMany` and `makeMany`, you can pass an array of attributes mapped
* according to the array index.
*/
fill (
attributes: ExtractFactoryAttributes<FactoryModel> | ExtractFactoryAttributes<FactoryModel>[]
): this

/**
* Create model instance.
*/
make (
state?: FactoryStateContract,
callback?: (
model: ExtractFactoryModel<FactoryModel>,
state: FactoryStateContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>>

/**
* Create and persist model instance
*/
create (
state?: FactoryStateContract,
callback?: (
model: ExtractFactoryModel<FactoryModel>,
state: FactoryStateContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>>

/**
* Create more than one model instance
*/
makeMany (
count: number,
state?: FactoryStateContract,
callback?: (
model: ExtractFactoryModel<FactoryModel>,
state: FactoryStateContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>[]>

/**
* Create and persist more than one model instance
*/
createMany (
count: number,
state?: FactoryStateContract,
callback?: (
model: ExtractFactoryModel<FactoryModel>,
state: FactoryStateContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>[]>
}

/**
* Factory model exposes the API to defined a model factory with states
* and relationships
*/
export interface FactoryModelContract<Model extends LucidModel, Attributes extends any> {
/**
* Reference to the underlying lucid model used by the factory
* model
*/
model: Model

/**
* Mainly for types support. Not used at runtime to derive any
* logic. Sorry, at times have to hack into typescript to
* get the desired output. :)
*/
states: unknown
relations: unknown

/**
* Returns the callback method for the state
*/
getState (state: string): ModelStateCallback<InstanceType<Model>>

/**
* Returns the relationship and its factory.
*/
getRelation (relation: string): {
factory: FactoryBuilderContract<FactoryModelContract<LucidModel, any>>
relation: RelationshipsContract,
}

/**
* Creates an instance of lucid model by invoking callback passed
* to `Factory.define` method.
*/
newUp: NewUpModelFunction<Model, Attributes>

/**
* Define custom state for the factory. When executing the factory,
* you can apply the pre-defined states
*/
state<K extends string> (
state: K,
callback: ModelStateCallback<InstanceType<Model>>,
): this & { states: { [P in K]: ModelStateCallback<InstanceType<Model>> } }

/**
* Define a relationship on another factory
*/
related<K extends ExtractModelRelations<InstanceType<Model>>, Relation extends any> (
relation: K,
callback: Relation,
): this & { relations: { [P in K]: Relation } }

/**
* Build model factory. This method returns the factory builder, which can be used to
* execute model queries
*/
build (): FactoryBuilderContract<this>
}

/**
* Factory manager to define new factories
*/
export interface FactoryManager {
define<Model extends LucidModel, Attributes extends any> (
model: Model,
callback: NewUpModelFunction<Model, Attributes>
): FactoryModelContract<Model, Attributes>
}

const Factory: FactoryManager
export default Factory
}
22 changes: 22 additions & 0 deletions example/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BaseModel, HasOne, hasOne, scope } from '@ioc:Adonis/Lucid/Orm'
import Factory from '@ioc:Adonis/Lucid/Factory'

class Profile extends BaseModel {
public id: string
Expand Down Expand Up @@ -30,3 +31,24 @@ User.fetchOrCreateMany('id', [{ id: '1', username: 'virk' }])
User.create({ id: '1', username: 'virk' })
User.create({ id: '1', username: 'virk' })
User.create({ id: '1' })

const F = Factory.define(User, (state) => {
const user = new User()
user.username = state.faker.username
return user
})

const P = Factory.define(Profile, () => new Profile())

const ProfileF = P
.state('social', () => {})
.build()

const UserF = F
.state('active', (user) => {
user.username = 'virk'
})
.related('profile', () => ProfileF)
.build()

UserF.with('profile', 1)
Loading

0 comments on commit 5c65731

Please sign in to comment.