Skip to content

Commit

Permalink
improvement: improve overall factories API
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jun 20, 2020
1 parent 934c33d commit 7d8398c
Show file tree
Hide file tree
Showing 18 changed files with 754 additions and 477 deletions.
174 changes: 109 additions & 65 deletions adonis-typings/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,103 @@
*/

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

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

/**
* Unwraps promise
* Extracts the attributes accepted by the lucid model set on a
* factory
*/
export type UnwrapPromise<T> = T extends PromiseLike<infer U> ? U : T
export type ExtractFactoryAttributes<
T extends FactoryModelContract<LucidModel>
> = Partial<ModelAttributes<InstanceType<T['model']>>>

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

/**
* Extracts the attributes accepted by the newUp method of a factory
* Function to return the model attributes.
*/
export type ExtractFactoryAttributes<
T extends FactoryModelContract<LucidModel, any>
> = Parameters<T['newUp']>[1]
export type DefineCallback<Model extends LucidModel> = (
ctx: FactoryContextContract,
) => Promise<Partial<ModelAttributes<InstanceType<Model>>>> | Partial<ModelAttributes<InstanceType<Model>>>

/**
* Function to initiate a model instance. It will receive the
* attributes returned by the `define` method
*/
export type NewUpCallback<T extends FactoryModelContract<LucidModel>> = (
attributes: ExtractFactoryAttributes<T>,
ctx: FactoryContextContract,
) => InstanceType<T['model']>

/**
* Function to merge attributes defined during runtime
*/
export type MergeCallback<T extends FactoryModelContract<LucidModel>> = (
model: InstanceType<T['model']>,
attributes: ExtractFactoryAttributes<T>,
ctx: FactoryContextContract,
) => void

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

/**
* ------------------------------------------------------
* Runtime context
* ------------------------------------------------------
*/

/**
* The runtime context of the factory builder. A new state is constructed
* for each `create/make` operation and passed down to relationships
* as well.
*/
export interface FactoryContextContract {
faker: any,
faker: {
lorem: {
sentence (count?: number): string,
},
},
sequence: {
username: string,
email: string,
},
isStubbed: boolean,
$trx: TransactionClientContract | undefined
}

/**
* ------------------------------------------------------
* Relationships
* ------------------------------------------------------
*/

/**
* Callback accepted by the `with` method and relationships
* `create` and `make` methods
*/
export type RelationCallback = (
factory: FactoryBuilderContract<FactoryModelContract<LucidModel>>
) => void

/**
* Shape of the factory relationships. To keep relationships slim, we will have
* a common interface for relationships vs fine tuning API for each type of
Expand All @@ -75,32 +120,30 @@ declare module '@ioc:Adonis/Lucid/Factory' {
* Pass context to the relationship. Must be done everytime, so that
* relationships uses the same transaction as the parent model
*/
withCtx (ctx: FactoryContextContract): this
useCtx (ctx: FactoryContextContract): this

/**
* Create and persist
*/
create (
parent: LucidRow,
callback?: (factory: FactoryBuilderContract<FactoryModelContract<LucidModel, any>>) => void,
count?: number,
): Promise<void>
create (parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>

/**
* Create and stub
*/
make (
parent: LucidRow,
callback?: (factory: FactoryBuilderContract<FactoryModelContract<LucidModel, any>>) => void,
count?: number,
): Promise<void>
make (parent: LucidRow, callback?: RelationCallback, count?: number): Promise<void>
}

/**
* ------------------------------------------------------
* Runtime builder
* ------------------------------------------------------
*/

/**
* Factory builder uses the factory model to create/make
* instances of lucid models
*/
export interface FactoryBuilderContract<FactoryModel extends FactoryModelContract<LucidModel, any>> {
export interface FactoryBuilderContract<FactoryModel extends FactoryModelContract<LucidModel>> {
/**
* Apply pre-defined state
*/
Expand All @@ -123,15 +166,13 @@ declare module '@ioc:Adonis/Lucid/Factory' {
): this

/**
* Define custom set of attributes. They are passed to the `newUp` method
* of the factory.
* Merge custom set of attributes. They are passed to the merge method of
* the model 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
merge (attributes: OneOrMany<ExtractFactoryAttributes<FactoryModel>>): this

/**
* Define custom runtime context. This method is usually called by
Expand All @@ -141,56 +182,62 @@ declare module '@ioc:Adonis/Lucid/Factory' {
* Do not define a custom context, unless you know what you are really
* doing.
*/
withCtx (ctx: FactoryContextContract): this
useCtx (ctx: FactoryContextContract): this

/**
* Create model instance.
*/
make (
callback?: (
model: ExtractFactoryModel<FactoryModel>,
model: InstanceType<FactoryModel['model']>,
ctx: FactoryContextContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>>
): Promise<InstanceType<FactoryModel['model']>>

/**
* Create and persist model instance
*/
create (
callback?: (
model: ExtractFactoryModel<FactoryModel>,
model: InstanceType<FactoryModel['model']>,
ctx: FactoryContextContract,
) => void
): Promise<ExtractFactoryModel<FactoryModel>>
): Promise<InstanceType<FactoryModel['model']>>

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

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

/**
* ------------------------------------------------------
* Factory model
* ------------------------------------------------------
*/

/**
* Factory model exposes the API to defined a model factory with states
* and relationships
*/
export interface FactoryModelContract<Model extends LucidModel, Attributes extends any> {
export interface FactoryModelContract<Model extends LucidModel> {
/**
* Reference to the underlying lucid model used by the factory
* model
Expand All @@ -206,29 +253,20 @@ declare module '@ioc:Adonis/Lucid/Factory' {
relations: unknown

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

/**
* Returns the relationship and its factory.
*/
getRelation (relation: string): FactoryRelationContract

/**
* Creates an instance of lucid model by invoking callback passed
* to `Factory.define` method.
* Optionally define a custom method to instantiate the model
* instance
*/
newUp: NewUpModelFunction<Model, Attributes>
newUp (callback: NewUpCallback<this>): this
merge (callback: MergeCallback<this>): this

/**
* 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>> } }
callback: StateCallback<InstanceType<Model>>,
): this & { states: { [P in K]: StateCallback<InstanceType<Model>> } }

/**
* Define a relationship on another factory
Expand All @@ -245,17 +283,23 @@ declare module '@ioc:Adonis/Lucid/Factory' {
build (): FactoryBuilderContract<this>
}

/**
* ------------------------------------------------------
* Manager to register new factories
* ------------------------------------------------------
*/

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

const Factory: FactoryManager
Expand Down
1 change: 1 addition & 0 deletions adonis-typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
/// <reference path="./schema.ts" />
/// <reference path="./migrator.ts" />
/// <reference path="./relations.ts" />
/// <reference path="./factory.ts" />
/// <reference path="./validator.ts" />
12 changes: 7 additions & 5 deletions example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ 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
return {
username: state.sequence.username,
}
})

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

const ProfileF = P
.state('social', () => {})
Expand All @@ -51,4 +53,4 @@ const UserF = F
.related('profile', () => ProfileF)
.build()

UserF.with('profile', 1)
UserF.with('profile', 1).merge({})
Loading

0 comments on commit 7d8398c

Please sign in to comment.