our app was created using mostly underscores for model attributes and we are having a very difficult time fully implementing mirage as a result. When we try to include a related model that is referenced as an underscore attribute on the model we receive the following error:

Mirage: You tried to include 'dasherized-attribute-name' with model:example(1) but no association named 'dasherizedAttributeName' is defined on the model

With models/factories setup as such:

app/models/example.js:

import { belongsTo } from 'ember-data/relationships';
import Model from 'ember-data/model';

export default Model.extend({
    dasherized_attribute_name: belongsTo('attribute', { inverse: 'examples' }),
});

app/models/attribute.js:

import { hasMany } from 'ember-data/relationships';
import Model from 'ember-data/model';

export default Model.extend({
    examples: hasMany('example', {
        inverse: 'dasherized_attribute_name',
    }),
});

app/mirage/factories/example.js:

import { Factory, association } from 'ember-cli-mirage';

export default Factory.extend({
    dasherized_attribute_name: association(),
});

app/mirage/serializers/application.js:

import { JSONAPISerializer } from 'ember-cli-mirage';
import { pluralize } from 'ember-inflector';
import { underscore } from '@ember/string';

export default JSONAPISerializer.extend({
    alwaysIncludeLinkageData: true,

    keyForAttribute(key) {
        return underscore(key);
    },

    keyForRelationship(key) {
        // underscoring this value does nothing
        return key;
    },

    payloadKeyFromModelName(modelName) {
        return underscore(pluralize(modelName));
    },
});

example test:

import { module, test } from 'qunit';
import hbs from 'htmlbars-inline-precompile';
import { render } from '@ember/test-helpers';
import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';

module('Integration | Component | example', (hooks) => {
    setupRenderingTest(hooks);
    setupMirage(hooks);

    hooks.beforeEach(async function () {
        this.store = this.owner.lookup('service:store');
        const serverExample = this.server.create('example');
        const example = await this.store.findRecord(
            'example',
            serverExample.id,
            { include: 'dasherized_attribute_name' }
        );

        this.set('example', example);
    });

    test('it renders', async (assert) => {
        await render(hbs`
            {{component
                example=example
            }}
        `);

        assert.dom('[data-test-example]').exists();
    });
});

I find this to be extremely confusing and unintuitive. Our attribute is snake case, the error says we are trying to include the attribute dasherized but it is finally looking for a camel cased attribute on the model???. Of course changing the model/factory attributes to be camel cased resolves this issue but that isn't necessarily viable for our app right now given the sheer number of attributes. I have tried every single hook referenced in the serializers docs to try to handle this case but simply cannot find where these transformations are actually taking place. Any help would be greatly appreciated.

1

There are 1 best solutions below

3
Sam Selikoff On

Sorry you're struggling, we should really add a guide somewhere to the site to explain this!

I can help clarify. When I first wrote Mirage, we did in fact do what your intuition is suggesting: match Mirage's models & relationships to your API conventions.

This turned out to be limiting and confusing. What Mirage really needed was a Serializer layer, that would be responsible for the shape of the data independent of the data layer. This way the data layer could make many more assumptions about the host app's data + relationships, for example where the foreign keys are stored.

This is why we introduced Serializers to Mirage. Serializers convert incoming payloads to a "standard" format that Mirage understands, and converts outgoing payloads to the format your API sends (which is also the format your Ember app expects). So to answer your question, Serializers is the layer you want to look at to address this issue.

There is an ActiveModelSerializer which might be a good starting point for you, as it already underscores attributes. Depending on your backend, that could work, or you could implement a serializer yourself. Configuring a serializer should be a one-time, app-wide thing, presuming your production API is somewhat consistent. The various keyFor* methods are what you use to turn Mirage models + relationships camelCase convention into underscore, for example. You can take a look at the source for ActiveModelSerializer if you'd like to see an example.

The last part is the normalize() method, which takes incoming requests and converts them to the JSON:API format, so Mirage knows where to find attributes and relationships. Again, most apps should be able to extend one of the existing serializers so you don't have to write this yourself. But that depends on your backend API format. The normalize() method is what lets Mirage's shorthands and helpers like normalizedRequestAttrs work with your Ember app's request data.

Hopefully that sheds some light on the subject, you can actually see an older blog post here back from 2015 where I was first thinking about this problem. If you're still having an issue configuring a Serializer open a new issue on Mirage repo or a new Stack Overflow question and I'll do my best to answer it!