2

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.

btrude
  • 21
  • 3
  • If I recall correctly relationships on mirage models are camelCased always. You might alter the naming on the response by using a serializer. – jelhan Dec 13 '19 at 15:44
  • @jelhan I tried hooking into the various ember-cli-mirage serializers *at every possible spot* but could not figure out specifically when this transformation occurs and that is the clarification that I was hoping to get by making this post. – btrude Dec 13 '19 at 17:34

1 Answers1

4

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!

Sam Selikoff
  • 12,366
  • 13
  • 58
  • 104
  • Thanks for the response. This didn't quite answer my question but I went back this morning with fresh eyes and found the exact function in the json api serializer in which this check is being done (specifically, "_addPrimaryModelToRequestedIncludesGraph") and re-wrote it to check for both underscore and camelized relationship keys (we have both on our models) and that has been working for me. – btrude Dec 16 '19 at 20:22
  • That looks like private API and is subject to change! I'd suggest not overwriting it – your code is bound to break in a future update if you do. Feel free to open an issue if you'd like to keep discussing. – Sam Selikoff Dec 17 '19 at 18:18
  • Issue opened here: https://github.com/miragejs/miragejs/issues/247 – btrude Dec 18 '19 at 20:04