Skip to content

Commit

Permalink
feat(seeder): add seeder package
Browse files Browse the repository at this point in the history
closes #251
  • Loading branch information
Langstra committed May 13, 2021
1 parent 1db1a63 commit 55a0234
Show file tree
Hide file tree
Showing 34 changed files with 1,275 additions and 35 deletions.
12 changes: 10 additions & 2 deletions docs/docs/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ npx mikro-orm migration:up # Migrate up to the latest version
npx mikro-orm migration:down # Migrate one step down
npx mikro-orm migration:list # List all executed migrations
npx mikro-orm migration:pending # List all pending migrations
npx mikro-orm migration:fresh # Drop the database and migrate up to the latest version
```

For `migration:up` and `migration:down` commands you can specify `--from` (`-f`), `--to` (`-t`)
Expand All @@ -86,12 +87,19 @@ and `--only` (`-o`) options to run only a subset of migrations:
```sh
npx mikro-orm migration:up --from 2019101911 --to 2019102117 # the same as above
npx mikro-orm migration:up --only 2019101923 # apply a single migration
npx mikro-orm migration:down --to 0 # migratee down all migrations
npx mikro-orm migration:down --to 0 # migrate down all migrations
```

> To run TS migration files, you will need to [enable `useTsNode` flag](installation.md)
> To run TS migration files, you will need to [enable `useTsNode` flag](installation.md)
> in your `package.json`.
For the `migration:fresh` command you can specify `--seed` to seed the database after migrating.
```shell
npx mikro-orm migration:fresh --seed # seed the database with the default database seeder
npx mikro-orm migration:fresh --seed=UsersSeeder # seed the database with the UsersSeeder
```
> You can specify the default database seeder in the orm config with the key `config.seeder.defaultSeeder`
## Using the Migrator programmatically

Or you can create a simple script where you initialize MikroORM like this:
Expand Down
12 changes: 11 additions & 1 deletion docs/docs/schema-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ You can use it via CLI:
```sh
npx mikro-orm schema:create --dump # Dumps create schema SQL
npx mikro-orm schema:update --dump # Dumps update schema SQL
npx mikro-orm schema:drop --dump # Dumps drop schema SQL
npx mikro-orm schema:drop --dump # Dumps drop schema SQL
```

> You can also use `--run` flag to fire all queries, but be careful as it might break your
Expand All @@ -36,6 +36,16 @@ well as column dropping.
`schema:drop` will by default drop all database tables. You can use `--drop-db` flag to drop
the whole database instead.

```shell
npx mikro-orm schema:fresh --run # !WARNING! Drops the database schema and recreates it
```
This command can be run with the `--seed` option to seed the database after it has been created again.
```shell
npx mikro-orm schema:fresh --run --seed # seed the database with the default database seeder
npx mikro-orm schema:fresh --run --seed=UsersSeeder # seed the database with the UsersSeeder
```
> You can specify the default database seeder in the orm config with the key `config.seeder.defaultSeeder`
## Using SchemaGenerator programmatically

Or you can create simple script where you initialize MikroORM like this:
Expand Down
194 changes: 194 additions & 0 deletions docs/docs/seeding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
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 `npx mikro-orm seeder:run`. 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).

You can create your own seeder classes using the following CLI command:
```shell
npx mikro-orm seeder:create DatabaseSeeder
```
This creates a new seeder class. By default it will be generated in the `./database/seeder/` directory. You can configure the directory in the config with the key `seeder.path`.

As an example we will look at a very basic seeder.
```typescript
// ./database/seeder/database.seeder.ts

import { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { Author } from './author'

export class DatabaseSeeder extends Seeder {

async run(em: EntityManager): Promise<void> {
const author = em.create(Author, {
name: 'John Snow',
email: 'snow@wall.st'
});
}
}
```
> Running a seeder from the command line or programmatically will automatically call `flush` and `clear` after the `run` method has completed.
### 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'

export class DatabaseSeeder extends Seeder {

async run(em: EntityManager): Promise<void> {
new AuthorFactory(em).count(10).make()
}
}
```

### 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'

export class DatabaseSeeder extends Seeder {

run(em: EntityManager): Promise<void> {
return this.call(em, [
AuthorSeeder,
BookSeeder
]);
}
}
```

## Entity factories
When testing you may insert entities in the database before starting a test. Instead of specifying every attribute of every entity by hand, you could also use a `Factory` to define a set of default attributes for an entity using entity factories.

Lets have a look at an example factory for an [Author entity](http://mikro-orm.io/docs/defining-entities).
```typescript
import { Factory } from '@mikro-orm/seeder';
import Faker from 'faker';
import { Author } from './entities/author.entity';

export class AuthorFactory extends Factory<Author> {
model = Author;

definition(faker: typeof Faker): Partial<Author> {
return {
name: faker.person.findName(),
email: faker.internet.email(),
age: faker.random.number(18, 99)
};
}
}
```
Basically you extend the base `Factory` class, define a `model` property and a `definition` method. The `model` defines for which entity the factory generates entity instances. The `definition` method returns the default set of attribute values that should be applied when creating an entity using the factory.

Via the faker property, factories have access to the [Faker library](https://github.com/marak/Faker.js/), which allows you to conveniently generate various kinds of random data for testing.

### Creating entities using factories
Once you defined your factories you can use them to generate entities. Simply import the factory, instantiate it and call the `makeOne` method.
```typescript
const author: Author = new AuthorFactory(orm.em).makeOne();
```

#### Generate multiple entities
Generate multiple entities by calling the `make` method. The parameter of the `make` method is the number of entities you generate.
```typescript
// Generate 5 authors
const authors: Author[] = new AuthorFactory(orm.em).make(5);
```

#### Overriding attributes
If you would like to override some of the default values of your factories, you may pass an object to the make method. Only the specified attributes will be replaced while the rest of the attributes remain set to their default values as specified by the factory.
```typescript
const author: Author = new AuthorFactory(orm.em).make({
name: 'John Snow'
});
```

### Persisting entities
The `create` method instantiates entities and persists them to the database using the `persistAndFlush` method of the EntityManager.
```typescript
// Make and persist a single author
const author: Author = await new AuthorFactory(orm.em).createOne();

// Make and persist 5 authors
const authors: Author[] = await new AuthorFactory(orm.em).create(5);
```
You can override the default values of your factories by passing an object to the `create` method.
```typescript
// Make and persist a single author
const author: Author = await new AuthorFactory(orm.em).createOne({
name: 'John Snow'
});

// Make and persist a 5 authors
const authors: Author[] = await new AuthorFactory(orm.em).create(5, {
name: 'John Snow'
});
```
### Factory relationships
It is nice to create large quantities of data for one entity, but most of the time we want to create data for multiple entities and also have relations between these. For this we can use the `each` method which can be chained on a factory. The `each` method can be called with a function that transforms output entity from the factory before returning it. Lets look at some examples for the different relations.

#### ManyToOne and OneToOne relations
```typescript
const books: Book[] = new BookFactory(orm.em).each(book => {
book.author = new AuthorFactory(orm.em).makeOne();
}).make(5);
```

#### OneToMany and ManyToMany
```typescript
const books: Book[] = new BookFactory(orm.em).each(book => {
book.owners.set(new OwnerFactory(orm.em).make(5));
}).make(5);
```

## Use with CLI
You may execute the `seeder:run` MikroORM CLI command to seed your database. By default, the `seeder:run` command runs the DatabaseSeeder class, which may in turn invoke other seed classes. However, you may use the `--class` option to specify a specific seeder class to run individually:
```shell script
npx mikro-orm seeder:run

npx mikro-orm seeder:run --class=BookSeeder
```

You may also seed your database using the [`migrate:fresh`](migrations.md#using-via-cli) or [`schema:fresh`](schema-generator.md) command in combination with the `--seed` option, which will drop all tables and re-run all of your migrations or generate the database based on the current entities. This command is useful for completely re-building your database:
```shell script
npx mikro-orm migration:fresh --seed

npx mikro-orm schema:fresh --seed
```


## Use in tests
Now we know how to create seeders and factories, but how can we effectively use them in tests. We will show an example how it can be used.

```typescript
beforeAll(async () => {
// Get seeder from MikroORM
const seeder = orm.getSeeder();

// Refresh the database to start clean
await seeder.refreshDatabase();

// Seed using a seeder defined by you
await seeder.seed(DatabaseSeeder);
})

test(() => {
// Do tests
});

afterAll(async () => {
// Close connection
await orm.close();
})
```
12 changes: 9 additions & 3 deletions packages/cli/src/CLIConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import yargs, { Argv } from 'yargs';

import { ConfigurationLoader, Utils } from '@mikro-orm/core';
import { ClearCacheCommand } from './commands/ClearCacheCommand';
import { GenerateEntitiesCommand } from './commands/GenerateEntitiesCommand';
import { SchemaCommandFactory } from './commands/SchemaCommandFactory';
import { MigrationCommandFactory } from './commands/MigrationCommandFactory';
import { DatabaseSeedCommand } from './commands/DatabaseSeedCommand';
import { DebugCommand } from './commands/DebugCommand';
import { GenerateCacheCommand } from './commands/GenerateCacheCommand';
import { GenerateEntitiesCommand } from './commands/GenerateEntitiesCommand';
import { ImportCommand } from './commands/ImportCommand';
import { MigrationCommandFactory } from './commands/MigrationCommandFactory';
import { SchemaCommandFactory } from './commands/SchemaCommandFactory';
import { CreateSeederCommand } from './commands/CreateSeederCommand';
import { CreateDatabaseCommand } from './commands/CreateDatabaseCommand';

/**
Expand Down Expand Up @@ -35,14 +37,18 @@ export class CLIConfigurator {
.command(new GenerateEntitiesCommand())
.command(new CreateDatabaseCommand())
.command(new ImportCommand())
.command(new DatabaseSeedCommand())
.command(new CreateSeederCommand())
.command(SchemaCommandFactory.create('create'))
.command(SchemaCommandFactory.create('drop'))
.command(SchemaCommandFactory.create('update'))
.command(SchemaCommandFactory.create('fresh'))
.command(MigrationCommandFactory.create('create'))
.command(MigrationCommandFactory.create('up'))
.command(MigrationCommandFactory.create('down'))
.command(MigrationCommandFactory.create('list'))
.command(MigrationCommandFactory.create('pending'))
.command(MigrationCommandFactory.create('fresh'))
.command(new DebugCommand())
.recommendCommands()
.strict();
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/commands/CreateSeederCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import c from 'ansi-colors';
import { Arguments, Argv, CommandModule } from 'yargs';
import { CLIHelper } from '../CLIHelper';
import { MikroORM } from '@mikro-orm/core';
import { AbstractSqlDriver } from '@mikro-orm/knex';

export class CreateSeederCommand<T> implements CommandModule<T, { seeder: string }> {

command = 'seeder:create <seeder>';
describe = 'Create a new seeder class';
builder = (args: Argv) => {
args.positional('seeder', {
describe: 'Seeder class to create (use PascalCase and end with `Seeder` e.g. DatabaseSeeder)',
});
args.demandOption('seeder');
return args as Argv<{ seeder: string }>;
};

/**
* @inheritdoc
*/
async handler(args: Arguments<{ seeder: string }>) {
const orm = await CLIHelper.getORM(undefined) as MikroORM<AbstractSqlDriver>;
const seeder = orm.getSeeder();
const path = await seeder.createSeeder(args.seeder);
CLIHelper.dump(c.green(`Seeder ${args.seeder} successfully created at ${path}`));
await orm.close(true);
}

}
33 changes: 33 additions & 0 deletions packages/cli/src/commands/DatabaseSeedCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import c from 'ansi-colors';
import { Arguments, Argv, CommandModule } from 'yargs';
import { CLIHelper } from '../CLIHelper';
import { MikroORM } from '@mikro-orm/core';
import { AbstractSqlDriver } from '@mikro-orm/knex';

export class DatabaseSeedCommand<T> implements CommandModule<T, { class: string }> {

command = 'seeder:run';
describe = 'Seed the database using the seeder class';
builder = (args: Argv) => {
args.option('c', {
alias: 'class',
type: 'string',
desc: 'Seeder class to run',
default: '',
});
return args as Argv<{ class: string }>;
};

/**
* @inheritdoc
*/
async handler(args: Arguments<{ class?: string }>) {
const orm = await CLIHelper.getORM(undefined) as MikroORM<AbstractSqlDriver>;
const seeder = orm.getSeeder();
const seederClass = args.class || orm.config.get('seeder').defaultSeeder;
await seeder.seedString(seederClass);
CLIHelper.dump(c.green(`Seeder ${seederClass} successfully seeded`));
await orm.close(true);
}

}
4 changes: 2 additions & 2 deletions packages/cli/src/commands/ImportCommand.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Arguments, CommandModule } from 'yargs';
import c from 'ansi-colors';
import { MikroORM } from '@mikro-orm/core';
import { AbstractSqlDriver } from '@mikro-orm/knex';
import c from 'ansi-colors';
import { Arguments, CommandModule } from 'yargs';
import { CLIHelper } from '../CLIHelper';

export class ImportCommand implements CommandModule {
Expand Down
Loading

0 comments on commit 55a0234

Please sign in to comment.