-
-
Notifications
You must be signed in to change notification settings - Fork 499
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
434 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
--- | ||
title: Seeding | ||
--- | ||
|
||
When initializing your application or testing it can be exhausting to create sample data for your database. The solution is to use seeding. Create factories for your entities and use them in the seed script or combine multiple seed scripts. | ||
|
||
## Seeders | ||
A seeder class contains one method `run`. This method is called when you use the command `mikro-orm db:seed`. In the `run` method you define how and what data you want to insert into the database. You can create entities using the [EntityManager](http://mikro-orm.io/docs/entity-manager) or you can use [Factories](#using-entity-factories). | ||
|
||
As an example we will look at a very basic seeder. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { Author } from './author' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
async run(em: EntityManager): Promise<void> { | ||
const author = em.create(Author, { | ||
name: 'John Snow', | ||
email: 'snow@wall.st' | ||
}); | ||
await em.persistAndFlush(author); | ||
em.clear(); | ||
} | ||
} | ||
``` | ||
|
||
### Using entity factories | ||
Instead of specifying all the attributes for every entity, you can also use [entity factories](#entity-factories). These can be used to generate large amounts of database records. Please read the [documentation on how to define factories](#entity-factories) to learn how to define your factories. | ||
|
||
As an example we will generate 10 authors. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { AuthorFactory } from '../factories/author.factory' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
async run(em: EntityManager): Promise<void> { | ||
await (new AuthorFactory(em)).count(10).create() | ||
em.clear(); | ||
} | ||
} | ||
``` | ||
|
||
### Calling additional seeders | ||
Inside the `run` method you can specify other seeder classes. You can use the `call` method to breakup the database seeder into multiple files to prevent a seeder file from becoming too large. The `call` method accepts an array of seeder classes. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { AuthorSeeder, BookSeeder } from '../seeders' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
run(em: EntityManager): Promise<void> { | ||
return this.call(em, [ | ||
AuthorSeeder, | ||
BookSeeder | ||
]); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "@mikro-orm/seeder", | ||
"version": "4.4.2", | ||
"description": "Seeder package for Mikro-ORM.", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git" | ||
}, | ||
"keywords": [ | ||
"orm", | ||
"mongo", | ||
"mongodb", | ||
"mysql", | ||
"mariadb", | ||
"postgresql", | ||
"sqlite", | ||
"sqlite3", | ||
"ts", | ||
"typescript", | ||
"js", | ||
"javascript", | ||
"entity", | ||
"ddd", | ||
"mikro-orm", | ||
"unit-of-work", | ||
"data-mapper", | ||
"identity-map", | ||
"seeder" | ||
], | ||
"author": "Wybren Kortstra", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/mikro-orm/mikro-orm/issues" | ||
}, | ||
"homepage": "https://mikro-orm.io", | ||
"engines": { | ||
"node": ">= 10.13.0" | ||
}, | ||
"scripts": { | ||
"build": "yarn clean && yarn compile && yarn copy", | ||
"clean": "rimraf ./dist", | ||
"compile": "tsc -p tsconfig.build.json", | ||
"copy": "ts-node -T ../../scripts/copy.ts" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"dependencies": { | ||
"@mikro-orm/core": "^4.4.2", | ||
"faker": "^4.1.0", | ||
"yargs": "^15.4.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import * as Faker from 'faker' | ||
import { EntityManager } from '@mikro-orm/core'; | ||
|
||
export abstract class Factory<C> { | ||
|
||
abstract readonly model: { new(): C }; | ||
private amount = 1; | ||
|
||
constructor(private em: EntityManager) { | ||
} | ||
|
||
protected abstract definition(faker: typeof Faker): Partial<C>; | ||
|
||
public make(overrideParameters?: Partial<C>): C | C[] { | ||
const objects = [...Array(this.amount)].map(() => { | ||
return this.em.create(this.model, Object.assign({}, this.definition(Faker), overrideParameters)); | ||
}); | ||
return this.amount === 1 ? objects[0] : objects; | ||
} | ||
|
||
public async create(overrideParameters?: Partial<C>): Promise<C | C[]> { | ||
const objects = this.make(overrideParameters); | ||
await this.em.persistAndFlush(objects); | ||
return objects; | ||
} | ||
|
||
public count(amount: number): Factory<C> { | ||
this.amount = amount; | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Factory } from './factory'; | ||
import { importFiles, loadFiles } from './utils/file.util'; | ||
|
||
export class HasFactory { | ||
|
||
public static async factory<C>(): Promise<Factory<C>> { | ||
if (!(global as any).imported) { | ||
(global as any).factories = new Map(); | ||
const factoryFiles = loadFiles(['./src/database/**/*.factory.ts']); | ||
(await importFiles(factoryFiles)).map((f: any) => { | ||
const factory: Factory<any> = new f(); | ||
(global as any).factories.set(factory.model.name, f); | ||
}); | ||
(global as any).imported = true; | ||
} | ||
if (!(global as any).factories.has(this.name)) { | ||
throw new Error(`Cannot get factory for ${this.name}`); | ||
} | ||
if (! (global as any).orm) { | ||
throw new Error(`MikroORM not found!`); | ||
} | ||
// Need some debugging for now | ||
// eslint-disable-next-line no-console | ||
console.log(`Getting factory for ${this.name}`); | ||
return new (global as any).factories.get(this.name)((global as any).orm); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* @packageDocumentation | ||
* @module seeder | ||
*/ | ||
export * from './seeder'; | ||
export * from './factory'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { MikroORM } from '@mikro-orm/core'; | ||
import { Seeder } from './seeder'; | ||
|
||
export const refreshDatabase = async (orm: MikroORM): Promise<void> => { | ||
const generator = orm.getSchemaGenerator(); | ||
await generator.dropSchema(); | ||
orm.config.getLogger().log('info', 'Dropped schema'); | ||
await generator.createSchema(); | ||
orm.config.getLogger().log('info', 'Recreated schema'); | ||
}; | ||
|
||
export const seed = async (orm: MikroORM, seeder: Seeder): Promise<void> => { | ||
await seeder.run(orm); | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { EntityManager } from '@mikro-orm/core'; | ||
|
||
export abstract class Seeder { | ||
|
||
public abstract run(em: EntityManager): Promise<void>; | ||
|
||
protected call(em: EntityManager, seeders: {new(): Seeder}[]): Promise<void> { | ||
return new Promise<void>((resolve, reject) => { | ||
Promise.all(seeders.map(s => { | ||
return (new s()).run(em.fork()); | ||
})).then(() => { | ||
resolve(); | ||
}).catch((e) => { | ||
reject(e); | ||
}) | ||
}) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import * as glob from 'glob'; | ||
import * as path from 'path'; | ||
|
||
export const importFiles = async (filePaths: string[]): Promise<any> => { | ||
return await Promise.all(filePaths.map(filePath => import(filePath))); | ||
}; | ||
|
||
export const loadFiles = (filePattern: string[]): string[] => { | ||
return filePattern | ||
.map(pattern => glob.sync(path.resolve(process.cwd(), pattern))) | ||
.reduce((acc, filePath) => acc.concat(filePath), []); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../../tsconfig.build.json", | ||
"compilerOptions": { | ||
"outDir": "./dist" | ||
}, | ||
"include": ["src/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"include": ["src/**/*"] | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Entity, PrimaryKey, Property } from '@mikro-orm/core'; | ||
|
||
@Entity() | ||
export class Project { | ||
@PrimaryKey() | ||
id!: number; | ||
|
||
@Property() | ||
name!: string; | ||
|
||
@Property() | ||
owner!: string; | ||
|
||
@Property() | ||
worth!: number; | ||
|
||
@Property() | ||
createdAt = new Date(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Entity, PrimaryKey, Property } from '@mikro-orm/core'; | ||
|
||
@Entity() | ||
export class User { | ||
@PrimaryKey() | ||
id!: number; | ||
|
||
@Property() | ||
name!: string; | ||
|
||
@Property() | ||
email!: string; | ||
|
||
@Property() | ||
password!: string; | ||
|
||
@Property() | ||
createdAt = new Date(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import * as Faker from 'faker'; | ||
import { MikroORM } from '@mikro-orm/core'; | ||
import { Factory } from '@mikro-orm/seeder'; | ||
import { SqliteDriver } from '@mikro-orm/sqlite'; | ||
import { Project } from './entities/project.entity'; | ||
|
||
class ProjectFactory extends Factory<Project> { | ||
model = Project; | ||
|
||
definition(faker: typeof Faker): Partial<Project> { | ||
return { | ||
name: 'Money vault', | ||
owner: faker.name.lastName(), | ||
worth: 120000 | ||
}; | ||
} | ||
|
||
} | ||
|
||
describe('Factory', () => { | ||
|
||
let orm: MikroORM<SqliteDriver>; | ||
let projectCountBefore: number; | ||
|
||
beforeAll(async () => { | ||
orm = await MikroORM.init({ | ||
entities: [Project], | ||
type: 'sqlite', | ||
dbName: ':memory:', | ||
}); | ||
await orm.getSchemaGenerator().createSchema(); | ||
}); | ||
|
||
afterAll(() => orm.close(true)); | ||
|
||
beforeEach(async () => { | ||
projectCountBefore = (await orm.em.findAndCount(Project, {}))[1]; | ||
orm.em.clear(); | ||
}) | ||
|
||
test('that a factory can make a single instance of an entity without saving it in the database', async () => { | ||
const project = (new ProjectFactory(orm.em)).make() as Project; | ||
expect(project).toBeInstanceOf(Project); | ||
expect(project.id).toBeUndefined(); | ||
|
||
const projectCountAfter = (await orm.em.findAndCount(Project, {}))[1]; | ||
expect(projectCountAfter).toEqual(projectCountBefore); | ||
}); | ||
|
||
test('that a factory can create a single instance of an entity and save it in the database', async () => { | ||
const projectSaved = (await (new ProjectFactory(orm.em)).create()) as Project; | ||
expect(projectSaved).toBeInstanceOf(Project); | ||
expect(projectSaved.id).toBeDefined(); | ||
const projectCountAfter = (await orm.em.findAndCount(Project, {}))[1]; | ||
expect(projectCountAfter).toEqual(projectCountBefore + 1); | ||
}); | ||
|
||
test('that a factory can make multiple instances of an entity without saving them in the database', async () => { | ||
const projects = (new ProjectFactory(orm.em)).count(5).make() as Project[]; | ||
expect(projects).toBeInstanceOf(Array); | ||
expect(projects.length).toBe(5); | ||
const projectCountAfter = (await orm.em.findAndCount(Project, {}))[1]; | ||
expect(projectCountAfter).toEqual(projectCountBefore); | ||
}); | ||
|
||
test('that a factory can create multiple instances of an entity and save them in the database', async () => { | ||
const projectSaved = (await (new ProjectFactory(orm.em)).count(5).create()) as Project[]; | ||
expect(projectSaved).toBeInstanceOf(Array); | ||
expect(projectSaved.length).toBe(5); | ||
const projectCountAfter = (await orm.em.findAndCount(Project, {}))[1]; | ||
expect(projectCountAfter).toEqual(projectCountBefore + 5); | ||
}); | ||
}); |
Oops, something went wrong.