Seeding
Fill the local database with sample or default rows using Seeder classes and model Factory classes, and run them with Seeding.run.
On this page
Basic Usage#
import 'package:magic/magic.dart';
import 'package:magic_local_storage/magic_local_storage.dart';
class Post extends Model with InteractsWithPersistence {
@override
String get table => 'posts';
@override
String get resource => 'posts';
@override
List<String> get fillable => ['title'];
@override
bool get useLocal => true;
@override
bool get useRemote => false;
}
class PostFactory extends Factory<Post> {
@override
Factory<Post> newFactory() => PostFactory();
@override
Post newInstance() => Post();
@override
Map<String, dynamic> definition() => {'title': faker.lorem.sentence()};
}
class PostSeeder extends Seeder {
@override
Future<void> run() async {
await PostFactory().count(10).create();
}
}
Future<void> main() async {
// After Magic.init(...) and Migrator().run(...).
await Seeding.run([PostSeeder()]);
}
Quick Reference#
| Call | Returns | Meaning |
|---|---|---|
Seeding.run(seeders) |
Future<void> |
Runs each seeder in order and logs the start and the end. |
Seeder.run() |
Future<void> |
The body of a seeder; you implement it. |
Seeder.call(seeders) |
Future<void> |
Runs other seeders from inside one. |
Factory.count(n) |
Factory<T> |
A copy that builds n models. |
Factory.state(map) |
Factory<T> |
A copy with map merged over the definition. |
Factory.create() |
Future<List<T>> |
Builds and saves the models. |
Factory.make() |
List<T> |
Builds the models without saving. |
Factory.raw() |
List<Map<String, dynamic>> |
The attribute maps only. |
Running Seeders#
Seeding.run(List<Seeder>) is the entry point, and it replaces core's removed Magic.seed. Call it from main() after the migrations, and guard it so a restart does not seed twice:
import 'package:flutter/foundation.dart';
import 'package:magic_local_storage/magic_local_storage.dart';
class DatabaseSeeder extends Seeder {
@override
Future<void> run() async {}
}
Future<void> seedOnce() async {
if (!kDebugMode) return;
if (await DB.table('posts').count() == 0) {
await Seeding.run([DatabaseSeeder()]);
}
}
Writing Seeders#
Generate one with dart run magic:dowel make:seeder PostSeeder, which writes lib/database/seeders/post_seeder.dart and adds the Seeder suffix when you leave it off. A seeder has one method, run(). A DatabaseSeeder that fans out uses call:
import 'package:magic_local_storage/magic_local_storage.dart';
class UserSeeder extends Seeder {
@override
Future<void> run() async {}
}
class PostSeeder extends Seeder {
@override
Future<void> run() async {}
}
class DatabaseSeeder extends Seeder {
@override
Future<void> run() async {
await call([
UserSeeder(),
PostSeeder(),
]);
}
}
Order matters: a seeder that needs rows from another runs after it.
Writing Factories#
Generate one with dart run magic:dowel make:factory Post, which writes lib/database/factories/post_factory.dart with the Factory suffix added. A factory extends Factory<T> and implements three methods:
| Method | Returns | Meaning |
|---|---|---|
definition() |
Map<String, dynamic> |
The default attributes. |
newInstance() |
T |
An empty model. |
newFactory() |
Factory<T> |
A fresh factory of the same class; count and state copy through it. |
faker is a getter on the base class, so faker.person.name(), faker.internet.email() and faker.lorem.sentence() need no import. The generated stub types the factory as Factory<Model> with TODO markers; replace those with your model.
Like Laravel, a factory fills its models unguarded: id, timestamps and keys missing from fillable are kept, so a factory can build a model exactly as an API returns it. Outside a factory fill() still honours fillable and guarded. The same switch is available directly, and it needs a synchronous callback:
import 'package:magic/magic.dart';
class User extends Model {
@override
String get table => 'users';
@override
String get resource => 'users';
}
void main() {
final User user = Model.unguarded(() => User()..fill({'id': 1, 'name': 'Taylor'}));
print(user.id);
}
Using Factories#
import 'package:magic/magic.dart';
import 'package:magic_local_storage/magic_local_storage.dart';
class Post extends Model with InteractsWithPersistence {
@override
String get table => 'posts';
@override
String get resource => 'posts';
@override
List<String> get fillable => ['title'];
@override
bool get useLocal => true;
@override
bool get useRemote => false;
}
class PostFactory extends Factory<Post> {
@override
Factory<Post> newFactory() => PostFactory();
@override
Post newInstance() => Post();
@override
Map<String, dynamic> definition() => {'title': faker.lorem.sentence()};
}
Future<void> main() async {
final Post one = (await PostFactory().create()).first; // saved
final List<Post> fifty = await PostFactory().count(50).create();
final List<Post> drafts = await PostFactory().state({'title': 'Draft'}).count(5).create();
final List<Post> unsaved = PostFactory().count(10).make(); // in memory only
final List<Map<String, dynamic>> payloads = PostFactory().count(3).raw(); // attribute maps
print('${one.id} ${fifty.length} ${drafts.length} ${unsaved.length} ${payloads.length}');
}
state() and count() never change the factory they are called on; they return a copy built through newFactory(), so a shared base factory can branch without one branch leaking into the other. make() leaves each model unsaved with every attribute dirty, and raw() returns one map per model.
create() saves through the model's save(), so the model needs InteractsWithPersistence. A refused save throws a StateError carrying the model's validation errors instead of returning a half-built list.
Factory States#
Write reusable states as an extension on Factory<Post>. state() and count() return Factory<Post>, so an extension method chains before or after either of them:
import 'package:magic/magic.dart';
import 'package:magic_local_storage/magic_local_storage.dart';
class Post extends Model {
@override
String get table => 'posts';
@override
String get resource => 'posts';
}
extension PostFactoryStates on Factory<Post> {
Factory<Post> published() => state({'is_published': true});
Factory<Post> draft() => state({'is_published': false});
}
Future<void> seedPosts(Factory<Post> posts) async {
await posts.published().count(3).create();
await posts.count(10).draft().create();
}
A method declared on PostFactory itself would be unreachable after the first state() or count() call, because their result is typed Factory<Post>.
Gotchas#
| Mistake | Fix |
|---|---|
Calling Magic.seed |
Removed from core; call Seeding.run([...]). |
| Seeding on every launch | Guard with a count() check or run it behind kDebugMode. |
Factory.create() on a model without InteractsWithPersistence |
The factory calls save() dynamically; add the mixin or use make(). |
| Naming a state method on the factory subclass | Declare it as an extension on Factory<T>; the subclass method is lost after count(). |
Expecting create() to return one model |
It returns a list; take .first for a single model. |
| Seeding before the migrations | Run Migrator().run first; a missing table drops every attribute. |
Related Documentation#
- Models: the
useLocalmodels a factory builds. - Migrations: the tables a seeder fills.
- Generators:
make:seederandmake:factory. - Testing: factories in tests.