Thanks to Precision Nutrition for sponsoring some of Mirage’s recent work!

In the last few weeks there’s been two important releases of Ember CLI Mirage. Version 0.3.2 bought along auto-discovery of Ember Data models, and version 0.3.3 (which I released over the weekend) added support for Polymorphic associations.

Taken together, these two features greatly improve the ergonomics of the library.

Auto-discovery of Ember Data models

In 0.3.2, Offir Golan shipped the auto-discovery feature.

Models in Mirage are used by the ORM to set up collections and define relationships (associations). With this new feature, if you happen to be using Ember Data, Mirage will now auto-discover your Ember Data models (and their associations), and use those for its own schema.

This is quite a convenient step, because if you’re using Ember Data your Mirage models should match your Ember Data models anyway. Local files in mirage/models will “win” against auto-generated definitions, but once you delete your local files Mirage will fall back to Ember Data’s schema.

So, if you’re using Ember Data, upgrade to the latest version of Mirage, nuke your mirage/models folder, and you never have to worry about running ember g mirage-model again!

Polymorphic associations

Polymorphic associations were the last big missing piece of Mirage’s ORM. Now, Mirage can represent any schema that Ember Data can.

Reaching parity with Ember Data’s flexible data-modeling story makes the auto-discovery feature that much more valuable. Now, no matter how you’re using Ember Data, you should be able to rely on the autogenerated model classes.

If you aren’t using Ember Data, you can define has-many or belongs-to polymorphic associations on your own Mirage models:

// mirage/models/user.js
export default Model.extend({
  collectibles: hasMany({ polymorphic: true })
});

// mirage/models/car.js
export default Model.extend({
  user: belongsTo()
});

// mirage/models/watch.js
export default Model.extend({
  user: belongsTo()
});

schema.users.create({
  collectibles: [
    schema.cars.create({ make: "Infiniti", model: "J30t" }),
    schema.watches.create({ make: "Citizen", model: "Men's Chronograph" }),
  ]
});

Just like all other associations in Mirage’s ORM, they can be one-way (null inverse), one-to-one, one-to-many or many-to-many. Check out the docs on polymorphic associations for more details.

Upgrading

These past few releases are all backwards-compatible with the 0.3.x series. If you’re still on 0.2.x, be sure to check out the upgrade guide so you can get on the 0.3 series.

Roadmap

Now that the majority of the work on the ORM is complete, there’s a few things I’ll be focusing on:

  • Real network requests. There’s a PR that needs a bit of attention, but soon Mirage will have an option to boot in Ember CLI’s Express server and send real HTTP responses back to your Ember app. This will enable several things:

    1. You’ll be able to use the network inspector to view your responses
    2. Mirage’s database state will persist across page refreshes of your application, and
    3. You’ll be able to test out features that depend on a real server, like verifying polling updates across two browser tabs

    Mirage will always be a tool focused on improving the development experience. Even though it will be able to run as a true node server, it will never make compromises to the developer experience in order to accommodate the needs of an actual production server.

  • Landing a version 1.0. I’m happy with Mirage’s API and I’d like to land a version 1.0 soon. The only possible breaking change I’m considering at the moment has to do with the database API.

    Before the ORM you would interact with Mirage’s db directly, doing things like db.users to fetch users and db.users.insert to insert new records.

    When designing this API I wanted db.users to return a JavaScript array but I also wanted insert, update and destroy to live off of this collection, e.g. db.users.insert(), db.users.update() and db.users.destroy().

    Having db.users return the underlying database records made it way too easy to inadvertently mutate the database, so I made an early decision to have db.users return a copy of the records using JSON.stringify. This is quite costly in terms of performance and some users have hit limits when trying to seed their dummy data.

    Now that the primary interface to the database is schema (Mirage’s ORM), the database’s API isn’t as important, and we could change db.users to db.users.all(). That way the other db methods (db.insert, db.update and db.delete) wouldn’t incur the expensive cost of copying the database each time they ran.

    Other than that, I think we can lock down the API, ship 1.0 and move forward with prioritizing other features that are on the horizon.

How you can help

Test your apps on 0.3.3 and report any issues that you run into, especially if your app has complex Ember Data schemas.

And as always, if you’d like to contribute code or documentation to Mirage, jump on the #ec-mirage Slack channel and reach out. There’s always more work to be done! :)

I’ve started the beta series of Mirage v0.3.0. You can install the latest release (0.3.0-beta.4 as of this writing) with

ember install ember-cli-mirage@beta

Recent updates to the ORM required some breaking changes, which is why I’m bumping Mirage’s “major” version from 0.2 to 0.3. I’m hoping this is the last release series before landing a 1.0 final.

Motivation

The ORM that was added in 0.2 has proved useful, and recent factory enhancements (the afterCreate hook along with traits and associations) have really improved the ergonomics of creating complex object graphs.

The biggest challenge since the 0.2 release has been the ORM’s lack of support for several relationship types:

  • one-way
  • one-to-one
  • many-to-many
  • reflexive
  • polymorphic

I went ahead with the 0.2.0 release anyway, because the serialization layer made working with JSON:API so much more pleasant. However, ever since the release users have been forced to write custom code in order to deal with these missing relationship types.

When I began work on these missing types several months ago, I expected it to be relatively simple. Mirage’s ORM was based off of ActiveRecord, and I felt I had a good understanding of the APIs that needed to be implemented. I started with one-to-one relationships, and nearly finished before I encountered a fundamental problem.

In Rails, relationships are mapped on top of databases. Databases have fixed, known schemas, and ActiveRecord’s APIs are designed to work with these known quantities. Questions like which records have foreign keys and whether two models are related via has-one or has-many are unambiguous, so ActiveRecord’s API doesn’t need to account for this.

Modern HTTP APIs, however, are quite different. For example, take the following totally valid JSON:API response:

{
  "data": {
    "id": "1",
    "type": "authors",
    "attributes": {
      "name": "Martin Fowler"
    },
    "relationships": {
      "books": {
        "data": [
          {
            "id": "1",
            "type": "books"
          }
        ]
      }
    }
  },
  "included": [{
    "type": "books",
    "id": "1",
    "attributes": {
      "title": "Refactoring: Improving the Design of Existing Code"
    }
  }]
}

We can see that an author has many books. But what about the relationship from books to authors? In the response it’s ambiguous. We might assume it’s many-to-one - but we could be wrong. Perhaps our app has the book Refactoring, which four authors. So the relationship between authors and book could turn out to be many-to-many.

This is just one example of the ambiguity inherent in many HTTP responses. Trying to make assumptions about the underlying schema can make Mirage’s abstractions even more complicated. In the 0.2 ORM, the hasMany and belongsTo helpers always assumed one side of the relationship was “belongs to”, and added a foreign key to it. In the above example, this would mean books would get an authorId key. But with the need for many to many relationships, this turns out to be wrong. What to do?

We could have kept the authorId as a default assumption, and then changed it when the user specified both sides. But what if the relationship turned out to be only one-way? You often come across this as well. Even if your backend has the relationship mapped out unambiguously, your API may choose to expose only one side. So, more assumptions like this give rise to even more indirection and unnecessary complexity.

Further, keeping a foreign key on the belongs-to side at the database level but adding an ids array to the has-many side at the ORM level is an abstraction, and something else developers need to learn. Understanding Mirage’s database structure is still useful for seeding test data and writing test assertions. And the abstractions needed to support all relationship types would be even more complex.

Putting the authorId foreign key on a book when the user only ever needed an author to have many books (and therefore a bookIds: [] array) turned out to be too confusing, and too much magic. If the user specifies that an author has many books, I decided that an author should simply get a bookIds: [] array to manage the foreign keys — and nothing more. This decision automatically allows for relationships to be one-way only, and it also expands to support the other relationship types. In the event that there is a bidirectional relationship, the keys would now need to be kept in sync on both sides - which is precisely what I’ve been working on, and is now handled in the 0.3 series. Further, giving models an id or ids property that corresponds directly to their relationships more closely matches Ember Data’s approach. Overall, it feels like the right decision.

Usage

The hasMany and belongsTo helpers are still present in 0.3, but they work a bit differently.

Say we have the following models:

// mirage/models/author.js
import { Model, hasMany } from 'ember-cli-mirage';

export default Model.extend({

  books: hasMany()

});
// mirage/models/book.js
import { Model } from 'ember-cli-mirage';

export default Model.extend({

});

The hasMany helper adds a bookIds array to each author model that it uses for bookkeeping. If we have an author instance

let author = schema.authors.find(1);

then the helper method author.books will use the author.bookIds property to find the related books.

Creating related books updates the ids property

let steinbeck = schema.authors.create({ name: 'John Steinbeck' });

steinbeck.createBook({ title: 'Of Mice and Men' });
steinbeck.createBook({ title: 'The Grapes of Wrath' });

steinbeck.bookIds; // [ 1, 2 ]

as does associating new books

let hemingway = schema.authors.create({ name: 'Ernest Hemingway' });
let oldMan = schema.books.create({ title: 'The Old Man and the Sea' });

hemingway.books = [ oldMan ];
hemingway.save();

hemingway.bookIds; // [ 3 ]

Notice that so far, books themselves don’t have any knowledge of this relationship. This is the biggest change in the ORM. Before, the book would automatically get an authorId, and so this would be available in tests, and it would also be sent over in responses as a relationship on the book. But in the case of 0.3, the relationship helpers are one-way. Basically, it works more like Ember Data does.

This means if you want a book to have an authorId, you’ll need to also define the relationship on the book:

// mirage/models/book.js
import { Model, belongsTo } from 'ember-cli-mirage';

export default Model.extend({

  author: belongsTo()

});

This helper will add an authorId to the book, and, like Ember Data, look for an implicit inverse on the author. If it can find one, the ORM will keep the ids on both sides of the relationship in sync.

Examples

Here are some Twiddles showcasing various relationship configurations:

  • One-Way Has Many. Notice how the author’s keys are updated when you delete a book.
  • One-Way Belongs To. Deleting the author will ensure existing book’s keys are nulled out (i.e. they become orphans so that the database is kept consistent).
  • One to Many. The keys on both sides of the relationship are kept in sync. If you delete the author, the books become orphaned records with null keys.
  • One to One. Another bidirectional relationship with keys managed on both sides.
  • Many to Many. And another.

Roadmap

Here are my plans for Mirage’s next steps. First, after enough folks try out the beta series we can land 0.3. Then I’ll be able to add polymorphic relationships, which will round out the ORM.

At this point I’d like to move towards a 1.0 release, barring any glaring issues in the API. Mirage has been around for nearly two years and plenty of people are using it. It’s past time we hit an official major version and lock down the API.

After 1.0, I’d like to move forward on an Ember Data integration layer, which is now possible since Mirage’s ORM is able to represent Ember Data’s possible schemas. The layer would simply read in your application’s Ember Data schema and reproduce it in-memory for Mirage’s ORM to use. This would yield big ergonomic gains for users of the library, as you’d no longer need to duplicate your Ember Data models for Mirage, and also lower the learning curve for new users.

There are several more features I want to move forward on now that the core API is stabilizing. Getting Mirage to be able to run in Node in an Express server would be great, since responses would be real HTTP responses, developers could use the network tab and more.

My primary goal in 2017 is delegation. For too long my personal availability has been a bottleneck for Mirage’s development. I am going to focus on finding contributors and planning instead of actual implementation. It should help move the library forward faster while getting more folks knowledgeable about the internals, while also freeing up my time to focus more on my business.

If you’d like to help, join #ec-mirage on Ember’s slack community and reach out! Also be sure to drop a message there or open an issue if you have any feedback on 0.3.

I’m so happy to be part of such an awesome community and look forward to seeing you at SoEmber and EmberConf. Here’s to an exciting 2017!

Mirage v0.2.0 has been released! Check out the release notes for the breaking changes and enhancements from 0.2.0-beta.9. Also see Adolfo’s script to help with some of the breaking changes from beta.7 to beta.9.

If you’re upgrading an app from 0.1.x to 0.2, be sure to read through the 0.2.x docs. When you’re ready to upgrade, consult the upgrade guide, and open an issue if the guide left something out.


Even though there’s more work I want to do to smooth out the API in a few places, it’s time to get 0.2 released. Most (if not all) of the planned changes should be backwards compatible with 0.2, so users should start using 0.2 today.

Next, I’m hoping to address two of the biggest pain points I saw during the beta series:

  1. Creating object graphs in tests. Currently, seeding Mirage with a graph of related data looks something like this:

     let author = server.create('author');
     let post1 = server.create('post', { author });
     server.createList('comment', 10, { post: post1 });
    
     let post2 = server.create('post', { author });
     server.createList('comment', 5, { post: post2 });
    

    Two features planned for the factory layer will help with this: an afterCreate hook, and traits.

  2. Responding with has-one or many-to-many relationships in the Serializer. Originally I was going to add a hasAndBelongsToMany helper to solve this, but now I think ad hoc Serializer methods is a better short-term solution, and something I wanted to add anyway. Some folks are keen on getting this working, so hopefully it will land soon.


Thanks to everyone who braved the beta series, you were crucial to all the iterations on the API, and to all the amazing contributors who helped push it through!

Happy coding everyone!

Mirage v0.2.0-beta.9 has been released. Check out the release notes for the breaking changes and enhancements.

Update on a 0.2 stable release

I wanted to jot down some of my recent thoughts while putting this release together. Pardon the rambling.

Originally I had planned for beta.9 to be the last beta release before cutting 0.2. Since folks have started using the 0.2 beta series, however, pain points around data modeling have been cropping up. In particular, dealing with has-one and many-to-many associations is quite difficult with the current set of abstractions.

My first thought was to ship 0.2 as is, and then work on adding helpers for hasAndBelongsToMany and hasOne. After some more thought & discussions with various users, I realized that the ORM’s abstractions might be a touch off. Let me explain.

Mirage’s ORM was built to support features like JSON:API includes and the planned factory relationships. The ORM is a way to encode association information, since there was no good place to do this in v0.1. My approach was to largely mimic server-side frameworks like Rails, since they’ve already solved this problem. After all, your models already live on the backend in database tables, so why not use the same concepts? Tables with foreign keys are familiar to backend devs, so let’s just emulate those concepts in Mirage.

So, that’s been the plan so far. The ORM has working hasMany and belongsTo associations, and those take care of many cases. The manyToMany case is interesting, though, because there’s not necessarily a standard conventional way that all Ember developers or servers approach this problem.

Take a simple belongsTo, like a post that belongs to an author. The post probably owns the foreign key, something like author_id. Persisting this relationship, then, is as simple as saving any other attribute on the post:

PUT /api/posts/1

{
  post: {
    id: 1,
    title: 'Hipster ipsum',
    author_id: 24
  }
}

author_id is all that’s needed to tell both Ember Data and the server about the new relationship.

hasMany is where things start to get tricky. Let’s assume we’ve also defined the inverse of the above relationship, so an author has many posts. If you updated an author with new posts, how would you persist those new relationships?

As above, the foreign key on each post is all that’s needed to tell both the frontend and the backend about the new relationship, so typically I would handle it like this:

post1.set('author', author);
post1.save();

post2.set('author', author);
post2.save();

and so on. You can write code that batches these requests, but the point here is that this is pretty straightforward stuff. Persisting a relationship is just boring old CRUD on a resource.

Interestingly, the Ember Data guides show code that suggests persisting a hasMany relationship by calling save on the parent, which looks something like this:

author.get('posts').pushObjects([post1, post3]);
author.save();

Now, Ember Data can understand this, and in fact this is how some teams work. But what does the request/response look like? Maybe something like

PUT /authors/1
{
  author: {
    id: 1,
    name: 'Frank',
    post_ids: [1, 3]
  }
}

This is a request to update a single author resource - but behind the scenes, is your backend actually updating the foreign keys on two different post records? If so, we’ve kind of moved out of the realm of doing boring CRUD on resources, because now our server is doing something different or more than what we asked of it: we asked it to update the author:1 resource, and in reality it’s updating two other post resources. Interestingly I’ve asked several folks in the community how they deal with this issue, and the response varies.

The story gets even trickier with many-to-many relationships. Sometimes people model the join record in their Ember apps, sometimes they don’t. If they don’t, a PUT to an author resource could actually be creating multiple server resources behind the scenes, via a join table.

This obviously has implications for Mirage, which works best with conventional server endpoints. A PUT to a resource updates that resource, and so on. But plenty of people write their servers this way, and it got me thinking: perhaps database tables and foreign keys are the wrong abstraction for Mirage to emulate. Perhaps transport of HTTP resources is a bit more generic and abstract than that.

Take, for example, the following Ember Data model definitions:

// models/post.js
export default DS.Model.extend({

  categories: DS.hasMany();

});

// models/category.js
export default DS.Model.extend({

  name: DS.attr()

});

That is a perfectly valid and legitimate domain model. By looking at those two models, can you tell me which entity owns the foreign key? Nope. In fact, you don’t even know if this is a one-to-many or many-to-many relationship. And yet, from the perspective of HTTP resources (including a valid implementation of a JSON:API server), this is totally valid.

Here’s the JSON:API response, for example:

GET /posts/1?include=categories

{
  data: {
    type: 'posts',
    id: 1,
    relationships: {
      categories: [
        {
          data: {
            type: 'categories',
            id: '2'
          }
        },
        {
          data: {
            type: 'categories',
            id: '5'
          }
        }
      ]
    }
  },
  included: [
    {
      type: 'categories',
      id: '2',
      attributes: {
        name: 'Economics'
      }
    },
    {
      type: 'categories',
      id: '5',
      attributes: {
        name: 'Programming'
      }
    }
  ]
}

Totally valid, and also impossible to ascertain whether this is a one-to-many or many-to-many relationship.

Basing Mirage’s ORM on database tables and foreign keys makes some things really easy and familiar, but for these situations it’s a pain. If your actual Ember app and your actual server can handle a request like

PUT /posts/1
{
  post: {
    id: 1,
    tag_ids: [1, 4]
  }
}

just fine, you shouldn’t have to add extra logic or models to make your fake Mirage server work.

The solution I have in mind for this problem is to replace the foreign key implementation with an associations map. This map will be a singleton that all models will have a reference to, and it will be used to persist model relationships.

The external API of Mirage’s ORM won’t change. For example, say you had a author that has many posts:

// mirage/models/author.js
export default Model.extend({
  posts: hasMany()
});

// mirage/models/post.js
export default Model.extend({
});

Currently (in 0.2.0-beta.9), Mirage makes an assumption here that the post resource has an author_id foreign key. As we’ve just shown, this is potentially a false assumption. My previous plan was to write a hasAndBelongsToMany helper for many-to-many relationships. Then, if this relationship turned out to be a many-to-many, the user would need to do something like the following:

// mirage/models/author.js
export default Model.extend({
  posts: hasAndBelongsToMany()
});

This would tell Mirage to transparently create a author-post join table, and deal with the persistence there.

Again, this now feels like the wrong abstraction, and it also introduces concepts that aren’t necessarily appropriate for the domain of the frontend. Instead, the original domain model with the hasMany declaration will add an author.posts key to the singleton associations map, where all the relationship data can be stored. This has an added benefit of simplifying Mirage’s interface for creating relationships in factories and elsewhere, as now developers will be able to do things like

let author = server.create('author', {
  categoryIds: [1, 3]
});

in their tests, similar to what they’re used to doing in Ember Data. We can also make Mirage’s shorthands understand both forms of saving hasMany relationships, since the ids on the models will just be pointers to the associations map. Serializers can be used to customize which ids are sent along with the response.

This change will also make it easier to ascertain all model information from an existing set of Ember Data models in the future. I’m confident the overall learning curve will be easier and resulting code will be cleaner.

While this will take a bit more time to get right, I think it’s important. I also feel like I have a better grasp of something important, namely that Mirage as an HTTP faking layer should not necessarily emulate various server abstractions, but rather focus on concepts that come from HTTP. It turns out that single-owner foreign keys is not one of those concepts, and therefore this abstraction does not belong in Mirage.

I’ll probably release 0.2, and work the associations map into a 0.3 release. Still thinking this through, though.

My closing thought is that these HTTP concepts are crucial to understand if you’re going to write an Ember app, and they can’t just be left to the backend team. It’s true that a frontend developer doesn’t need to know that Rails has a has_and_belongs_to_many method that abstracts away join tables on many-to-many relationships; however, the developer does need to understand how her Ember app will retrieve and persist many-to-many relationships across the network. Domain modeling and HTTP transport is a central part of Ember development and unfortunately at the moment, many parts of it are still non-standard and unconventional.

Here’s a quick update on Mirage 0.2.

When I started working on the ORM/Serializer layer, I knew we were going to need to bump Mirage to 0.2. However, I thought it was worth keeping the library completely backwards compatible. I wanted users to be able to update to 0.2 without breaking any of their existing route handlers.

The more I learned while developing, the more I realized some of the assumptions I made in 0.1 were simply bad. For example, using the formatting of fixture files (including their names) to determine the format of JSON responses from mocked routes.

Now that we have a proper ORM and serializer layer, I’d like the story for how to set up your mock server to be clear, especially to newcomers.

In 0.1, route handlers receive the db as the first argument:

this.get('/users', (db, request) => {
  return db.users;
});

This made sense in a world where there was only a db, acting as a dumb data store, and all formatting decisions were left up to the user.

Then, we added the ORM and Serializer. Originally, the idea was, if you defined your models - that is, if you opted in to Mirage’s ORM - we’d inject a schema object instead of the comparatively dumb db:

this.get('/users', (schema, request) => {
  return schema.user.all();
});

This returned a User Collection, which the Serializer layer knew how to serialize.

This has been working well - but obviously, this is a breaking change for old route handlers. I also wanted people to be able to opt-in to the ORM layer, but still be able to dive into the raw db if they ever wanted to. db is an object that hangs directly off of schema, so you can always access it, even if you’ve opted in to the ORM:

this.get('/users', (schema, request) => {
  return schema.db.users;
});

This would bypass the model-specific serializers.

ES6 destructring makes this even better:

this.get('/users', ({db}) => {
  return db.users;
});

Given this, I feel it’s worth making the breaking change, and only injecting schema to route handlers. The upgrade path for existing route handlers should be a simple change:

- this.get('/users', (db) => {
+ this.get('/users', ({db}) => {
    return db.users;
  });

along with possibly specifying a default Application serializer.

The main reason I want to make this change, is to simplify the story around how Mirage data gets set up. In 0.1, Mirage looked for defined fixtures and/or factories to set up its database. In traditional server frameworks (e.g. Rails), you have a schema file that specifies the schema of your db. Using a mixture of fixtures and factories is confusing and unnecessary. Further, factories should be seen as an extension of the models (db collections) they’re creating, rather than their definitions.

This is why going forward, the story for configuring Mirage will be a unified one: Models define your schema/database. So, new users will define models for each table/collection they want in their Mirage mock server. That sets up the database tables, and also gives the user a very easy starting point when they do want to opt into the relationship/serializer support. server.create will still use a factory if it exists, but if no factory exists, it will simply create an empty model.

I’m confident this change will make Mirage simpler and more approachable. The downside is, existing users will need to define blank models for each collection they have. We’ll have a generator, which will help some, but this could prove to be annoying. My hope is that having a single /models directory, while being able to delete empty factory and fixture files, will simplify things. Also, this paves the way for a planned future addon, mirage-ember-data. The purpose of this addon is to ascertain, at run-time, the server models and their relationships, based on a user’s Ember Data models. This would eliminate the need to define models again, in Mirage-land.

This addon is still a ways off, but this change - enforcing users to simply define their models as the single source of truth for their backend schema - paves the way.


You can see all the open items left before we release 0.2.0 here. Bugs, Help Wanted and Good for New Contributors are great tags to look out for if you’d like to help push us towards release!

If you have any thoughts or comments, tweet @samselikoff or open an issue.

On October 10 I spoke at the Global Ember Meetup about why I built Mirage, and what’s planned for the next version. Here’s the video:

Yesterday I merged in the JSON:API Serializer, which is the last piece of planned work I have for the serializer layer. That means it’s ready to test! I’m sure there’s plenty I haven’t thought of yet, but I think it’s time to get some people kicking the tires.

For the brave, I’ll be writing documentation this week about how to take advantage of the ORM and Serializer layer in your route handlers. Migration will be at your own pace: you should be able to switch over, keep old custom route handlers that access the db directly, and switch them over one at a time to use the new schema object - the ORM. Using a schema in your route handler lets you respond with a model or collection, which is the basis for your serializers knowing how to transform your response into an appropriately-formatted JSON payload.

The docs should be landing soon. But, the code is already in master (since the entire ORM is opt-in) - so, for the truly brave, you can try this out right now, by doing something like the following:

  1. Upgrade to master (“samselikoff/ember-cli-mirage” in your package.json)
  2. Define your models. For each model create a file under mirage/models that looks like the following:

     // mirage/models/post.js
     import { Model } from 'ember-cli-mirage';
    
     export default Model;
    

    Use the singular version of your model for the filename.

  3. Define your serializer. There are two named serializers, JSON:API and ActiveModelSerializer. You can customize these as well the basic Serializer that’s also included.

     // mirage/serializers/application.js
     import Serializer from 'ember-cli-mirage/serializers/json-api-serializer';
    
     export default Serializer;
    
  4. Once you do the above, Mirage will now be using an ORM. This means your custom route handlers will no longer have the signature

     function(db, request)
    

    but rather

     function(schema, request)
    

    where schema is the ORM object. Fortunately, the db is available at schema.db. This means you can give your old route handlers access to the db by doing the following refactoring:

     - this.get('/some/path', function(db, request), {
     + this.get('/some/path', function({db}, request), {
        // your custom route handler
     });
    

    Not bad, thanks to the magic of ES6 object destructuring!

    Additionally, the ORM standardizes the formatting of database attributes and collections. Previously, for example, the name of the database collection was based on the filename of your fixture or factory - so, you could have a collection called db.blog_posts. With the ORM, everything is camel-cased (we are writing JS, after all). So, this may necessitate some refactoring of your custom route handler code.

Adding relationship support looks like this:

// mirage/models/author.js
import { Model, hasMany } from 'ember-cli-mirage';

export default Model.extend({
  posts: hasMany()
});

// mirage/models/post.js
import { Model, belongsTo } from 'ember-cli-mirage';

export default Model.extend({
  author: belongsTo()
});

// mirage/serializers/author.js
import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
  relationships: ['posts']
});

and now, the GET shorthands to /authors should return included posts!


That’s just a taste of what’s to come! I want to reiterate this is very new and I basically don’t suggest using it. But, if you’re feeling adventurous, dive in! I would love to hear any feedback. If you do try it out, hit me up on Slack if you have questions.

Updating the shorthands to work with the serializer layer proved harder than I thought. Serializers made it clear that the shorthands were making assumptions about the shape of the JSON payload. Now that users will be able to use serializers to transform how their data looks going out, I’ll also need a way for them to specify how the data looks coming in. This is similar to Ember Data’s normalize function.

If a user is using a PUT or POST shorthand, I’ll need to first deserialize the payload into a standard format, so the shorthands know what to do with it. I’ll use the JSON:API format for the standard; that way, if you’re using JSON:API, normalize will be a no-op, and AMS-style responses will simply convert to JSON:API.

This sounds a lot like Ember Data, and I’ve even considered using Ember Data for the data store/identity map portion of Mirage; but at this point, there are still too many unknowns. I’d rather get the rest of the main features incorporated + wait for the API to stabilize, before making such a big decision.

Mirage’s ORM has very different needs than Ember Data’s: it’s a synchronous in-memory store, and while ED also has a clientside store, it was designed around an async layer, incorporates Ember.Object for KVO, requires attr declarations, and much more. Mirage’s orm uses object.defineProperty to keep things as lightweight as possible, so you’ll be able to user.createPost, user.posts = [1], post.user = user etc. in your routes. Adding the ceremony of .get(), .set(), and createRecord` everywhere would make Mirage feel like more of a burden, and I think it’s important to try to keep things as slim and easy-to-use as possible, given that Mirage is designed for mocking.

In any case, the shorthands were originally simple functions that were unit tested. Now that there’s a bit more going on, I felt the need to refactor the server/controller code a bit. I also got around to slimming down the initializer, moving that code to the Server, and moving a lot of route-handling-related code from Server to a new RouteHandler class. My next step will be to turn the shorthand functions into RouteHandlers (probably subclasses), which will hopefully provide some direction on how the data will flow from request, through normalize, to the shorthands and out to a response.

I wanted to quickly note that, although the models PR has been merged into master, it is not quite ready for use. To be really effective, Mirage also needs a serializer layer (in progress), and an update to the factory layer (to support associations and traits).

My plan is to document all three of these features (models, serializers and updated factories) at once, since they all rely on the orm, and will all require you to write simple model definitions to take advantage of.

Once you add a model definition, say by defining an author model

// mirage/models/author.js
export default Mirage.Model.extend({
  posts: Mirage.hasMany()
});

then you opt into the orm. Now, routes will get a schema object injected instead of a db (the db will be accessible via schema.db), and shorthands and factories will leverage the schema, and you’ll be able to use serializers as well.

I’ve hit some snags writing serializers, and there’s a lot of hidden complexity in this effort, but I’m hoping I can wrap this all up in the next few weeks.

I ran into an interesting problem while working on the serializer layer. I was just wrapping up AMS-style responses and was going to start working on the JSON:API version, when a wrinkle came up: the formatting of attribute names, both on Mirage’s model layer instances, and on the field names of Mirage’s database.

Currently, Mirage’s database simply takes whatever POJO of attrs you give it, and sticks that in its db. So if you write

db.users.create({ first_name: 'Link' })

then you’ll end up with that POJO in the db, but if you use first-name, you’ll get that instead. This was originally done to make things as simple as possible - your db fields matched your API responses, so fixtures would “just work”, and accessing the data is as you’d expect based on your API.

When I introduced the model layer, I added attr accessors that simply matched the keys in the db. But right now, it’s a naive implementation that just wraps the db attrs. So, if you’re working with a user model (e.g. in your route handler), you would either access user.first_name or user['first-name'], depending on how your database looked.

It seems like attrs on models should be consistently camelCase. One would expect to write user.firstName on a JavaScript model. That’s the convention. I could keep the model’s attrs in the format of your API (i.e. whatever’s in your db), so we’d have something like user.first_name. But, what happens when you switch your app over to JSON:API? Now, you have to rewrite all the custom parts of your Mirage server, since it’s now user['first-name'] in JSON:API. That’s pretty crappy. Not to mention, the dynamic methods added by the model layer, like user.createPost, should probably be consistent across API formats.

So, I think models should have camelCase attributes. That way you’re always using camelCase, regardless of the format of your API - which makes sense, since you’re writing a (mock) JavaScript server.

This presents an interesting challenge. How should the db fields be formatted? There’s three ways to create db data. Fixture files, factories, and using the ORM in a route handler (e.g. schema.user.create(...)). The latter two seem like they should be camelCase (again, you don’t want to have to update all your factories if you change from AMS to JSON:API…you may have to update some parts of your routes). But fixtures should always “just work”.

This leads me to think there should be a part of the “serializer layer” that can deserialize an API payload and get the attrs for the model(s), or at least in some way standardize it. This would mean if you change your API, you’d be able to use new fixture files just by specifying your new serializer. Also, it’d make the shorthands more versatile - they could basically use your serializers to deserialize the payload, and then they’d be able to create/update/delete the appropriate models regardless of your API format. Right now, they are coupled to AMS-style responses.

I’ll have to think more about this, but right now this feels like the right move.