Models wrap your database and allow you to define relationships.
Define models by adding files under /models
. These are automatically registered with schema
, which is how you’ll access your model classes in your route handlers.
Note - if you’re using Ember Data and version 0.3.3 of Mirage or later, models will be detected and auto-generated for you in-memory, so you don’t need to define the files yourself.
To define a model, use the generator:
ember g mirage-model blog-post
This creates a file under /mirage/models
:
// mirage/models/blog-post.js
import { Model } from 'ember-cli-mirage';
export default Model;
Access a model class using the schema
object injected into your route handlers. For example, given the blog-post
file above, you’d access the BlogPost
class via schema.blogPosts
:
this.get('/blog_posts', (schema, request) => {
return schema.blogPosts.all();
});
You can then invoke the model’s class methods.
Use ES6 destructuring to add some sugar:
this.get('/blog_posts', ({ blogPosts }, request) => {
return blogPosts.all();
});
Create a new unsaved model instance with attributes attrs.
let post = blogPosts.new({ title: 'Lorem ipsum' });
post.title; // Lorem ipsum
post.id; // null
post.isNew(); // true
Create a new model instance with attributes attrs, and insert it into the database.
let post = blogPosts.create({title: 'Lorem ipsum'});
post.title; // Lorem ipsum
post.id; // 1
post.isNew(); // false
Return all models in the database.
let posts = blogPosts.all();
// [post:1, post:2, ...]
Return one or many models in the database by id.
let post = blogPosts.find(1);
let posts = blogPosts.find([1, 3, 4]);
Returns the first model in the database that matches the key-value pairs in the query
object. Note that a string comparison is used.
let post = blogPosts.findBy({ published: true });
Returns the first model in the database.
let post = blogPosts.first();
Return an array of models in the database matching the key-value pairs in query. Note that a string comparison is used.
let posts = blogPosts.where({ published: true });
These methods are available on your model instances.
Returns the attributes of your model.
let post = blogPosts.find(1);
post.attrs; // {id: 1, title: 'Lorem Ipsum', publishedAt: '2012-01-01 10:00:00'}
Create or saves the model.
let post = blogPosts.new({ title: 'Lorem ipsum' });
post.id; // null
post.save();
post.id; // 1
post.title = 'Hipster ipsum'; // db has not been updated
post.save(); // ...now the db is updated
Updates the record in the db.
let post = blogPosts.find(1);
post.update('title', 'Hipster ipsum'); // the db was updated
post.update({
title: 'Lorem ipsum',
created_at: 'before it was cool'
});
Destroys the db record.
let post = blogPosts.find(1);
post.destroy(); // removed from the db
Boolean, true if the model has not been persisted yet to the db.
let post = blogPosts.new({title: 'Lorem ipsum'});
post.isNew(); // true
post.id; // null
post.save(); // true
post.isNew(); // false
post.id; // 1
Boolean, opposite of isNew
.
Reload a model’s data from the database.
let post = blogPosts.find(1);
post.attrs; // {id: 1, title: 'Lorem ipsum'}
post.title = 'Hipster ipsum';
post.title; // 'Hipster ipsum';
post.reload(); // true
post.title; // 'Lorem ipsum'
Simple string representation of the model and id.
let post = blogPosts.find(1);
post.toString(); // "model:blogPost:1"
You can also define associations by using the belongsTo
and hasMany
helpers. Each helper adds some dynamic methods to your model.
belongsTo
// mirage/models/blog-post.js
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
author: belongsTo()
});
This adds an authorId
property to your blogPost
model, as well as some methods for working with the associated author
model:
blogPost.authorId; // 1
blogPost.authorId = 2; // updates the relationship
blogPost.author; // Author instance
blogPost.author = anotherAuthor;
blogPost.newAuthor(attrs); // new unsaved author
blogPost.createAuthor(attrs); // new saved author (updates blogPost.authorId in memory only)
Note that when a child calls child.createParent
, the new parent is immediately saved to the db
, but the child’s foreign key is updated on this instance only, and is not immediately persisted to the database.
In other words, blogPost.createAuthor
will create a new author
record, insert it into the db
, and update the blogPost.authorId
in memory, but if you were to fetch the blogPost
from the db
again, the relationship would not be persisted.
To persist the new foreign key, you would call blogPost.save()
after creating the new author.
hasMany
// mirage/models/blog-post.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
comments: hasMany()
});
This adds a commentIds
property to the blogPost
model, as well as some methods for working with the associated comments
collection:
blogPost.commentIds; // [1, 2, 3]
blogPost.commentIds = [2, 3]; // updates the relationship
blogPost.comments; // array of related comments
blogPost.comments = [comment1, comment2]; // updates the relationship
blogPost.newComment(attrs); // new unsaved comment
blogPost.createComment(attrs); // new saved comment (comment.blogPostId is set)
modelName
If your associations model has a different name than the association itself, you can specify the modelName
on the association.
For example,
// mirage/models/blog-post.js
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
author: belongsTo('user'),
comments: hasMany('annotation')
});
would add all the named author
and comment
methods as listed above, but use user
and annotation
models for the actual relationships.
inverse
Sometimes a hasMany
relationship can point to a model with multiple associations of the same type. You can use the inverse
option to specify which property on the related model is the inverse of the given relationship:
// app/models/talk.js
export default Model.extend({
primaryEvent: belongsTo('event'),
secondaryEvent: belongsTo('event'),
});
// app/models/event.js
export default Model.extend({
talks: hasMany('talk', { inverse: 'primaryEvent' }),
});
polymorphic
You can specify whether an association is a polymorphic association by passing { polymorphic: true }
as an option.
For example, say you have a Comment
that can belong to a BlogPost
or a Picture
. Here’s how the model definitions would look:
// app/models/comment.js
export default Model.extend({
commentable: belongsTo({ polymorphic: true })
});
// app/models/blog-post.js
export default Model.extend({
comments: hasMany()
});
// app/models/picture.js
export default Model.extend({
comments: hasMany()
});
Note that commentable
doesn’t need a type (there’s no validation done on which types of models can exist on that association).
Polymorphic associations have slightly different method signatures for their foreign keys and build/create methods.
let comment = schema.comments.create({ text: "foo" });
comment.buildCommentable('post', { title: 'Lorem Ipsum' });
comment.createCommentable('post', { title: 'Lorem Ipsum' });
// getter
comment.commentableId; // { id: 1, type: 'blog-post' }
// setter
comment.commentableId = { id: 2, type: 'picture' };
Has-many asssociations can also be polymorphic:
// app/models/user.js
export default Model.extend({
things: hasMany({ polymorphic: true })
});
// app/models/car.js
export default Model.extend({
});
// app/models/watch.js
export default Model.extend({
});
let user = schema.users.create({ name: "Sam" });
user.buildThing('car', { attrs });
user.createThing('watch', { attrs });
// getter
user.thingIds; // [ { id: 1, type: 'car' }, { id: 3, type: 'watch' }, ... ]
// setter
user.thingIds = [ { id: 2, type: 'watch' }, ... ];
To create objects via factories that come with relationships (and related models) already built, you can use the afterCreate()
hook when defining your factory. For details, check out the factories documentation.