ember.jsactive-model-serializersjson-apiember-cli-miragefastjsonapi

Ember Cli Mirage: Active Model Adapter with JSONAPISerializer


I am on halfway of implementing JSON API structure (with underscore attributes).


Actual state for development environment is:

I use the Active Model Adapter structure for requesting to the backend for resources and backend response me with JSON API structure.

In Application Serializer I am using JSONAPISerializer. I override methods:

serializeBelongsTo
keyForRelationship
keyForAttribute
serialize
serializeAttribute
serializeHasMany

and for development, everything works for me (backend in Rails communicate with Ember very good).


The problem is with Ember CLI Mirage and conventions (not sure if there are simple solutions or I need to override again methods in this addon).

Actual state with Ember Cli Mirage and test environment:

I am using import { JSONAPISerializer } from 'ember-cli-mirage'; and then trying to manipulate proper request and then transform it for JSON API format.

It could work like this:

Ember Adapter (Active Model Adapter format - with underscore attributes) ---> Mirage Serializer should get request (find resources created before in tests with associations) and then response it with JSON API format ---> JSON API Serializer could catch it and fill Ember DS.

For now, I have a missing part to serialize it for all cases to JSON API standard (with underscored attributes)

Where should I do this transformation to minimize overriding JSONAPISerializer Mirage Serializer.

I noticed that there are some helpers, but I have a problem to wrap this knowledge together (http://www.ember-cli-mirage.com/docs/advanced/route-handlers#helpers)

UPDATE:

Example of structure from Backend:

{
  "data": {
    "id": "6",
    "type": "first_resource",
    "attributes": {
      "id": 6,
      "my_attribute": "my_attribute"
    },
    "relationships": {
      "second_resources": {
        "data": [
          {
            "id": "16",
            "type": "second_resource"
          }
        ]
      },
      "third_resource_other_type": {
        "data": {
          "id": "1",
          "type": "third_resource"
        }
      },
      "fourth_resource": {
        "data": {
          "id": "1",
          "type": "fourth_resource"
        }
      }
    },
    "links": {
      "fifth_resources": "/api/v1/first_resources/6/fifth_resources"
    }
  },
  "included": [
    {
      "id": "1",
      "type": "fourth_resource",
      "attributes": {
        "id": 1,
        "my_attribute": "my_attribute"
      },
      "links": {
        "sixth_resource": "/api/v1/fourth_resources/1/sixth_resource"
      }
    },
    {
      "id": "16",
      "type": "second_resource",
      "attributes": {
        "id": 16,
        "my_attribute": "my_attribute"
      },
      "relationships": {
        "eighth_resources": {
          "data": []
        }
      },
      "links": {
        "seventh_resources": "/api/v1/second_resources/16/seventh_resources"
      }
    },
    {
      "id": "17",
      "type": "second_resource",
      "attributes": {
        "id": 17,
        "my_attribute": "my_attribute"
      },
      "relationships": {
        "eighth_resources": {
          "data": []
        }
      },
      "links": {
        "seventh_resources": "/api/v1/second_resources/17/seventh_resources"
      }
    },
    {
      "id": "15",
      "type": "second_resource",
      "attributes": {
        "id": 15,
        "my_attribute": "my_attribute"
      },
      "relationships": {
        "eighth_resources": {
          "data": [
            {
              "id": "26",
              "type": "eighth_resource"
            },
            {
              "id": "24",
              "type": "eighth_resource"
            }
          ]
        }
      },
      "links": {
        "seventh_resources": "/api/v1/second_resources/15/seventh_resources"
      }
    },
    {
      "id": "26",
      "type": "eighth_resource",
      "attributes": {
        "id": 26,
        "my_attribute": "my_attribute"
      }
    }
  ]
}

UPDATE2

structure from mirage response:

data: {
  attributes: {
    my_attribute: 'my_attribute',
    second_resource_ids: [36, 37],
    fifth_resource_ids: []
  },
  id: 11,
  relationships: {
    third_resource_other_type: {data: null}
    fourth_resource: {data: null}
    second_resources: {data: []}
  },
  type: "first_resources"
}

resources in tests:

server.create('second-resource', {
  id: 36,
  first_resource_id: '11',
  my_attribute: "my_attribute"
});

server.create('eighth-resource', { 
  id: 140,
  second_resource_id: 37
});

server.create('eighth-resource', {
  id: 141,
  second_resource_id: 37
});

server.create('second-resource', {
  id: 37,
  first_resource_id: '11',
  eighth_resource_ids: [140, 141]
});

server.create('first-resource', {
  id: 11,
  second_resource_ids: [36, 37]
});

first_resource model in mirage:

export default Model.extend({
  third_resource_other_type: belongsTo(),
  fourth_resource: belongsTo(),
  fifth_resources: hasMany(),
  second_resources: hasMany()
});

Solution

  • Let's try to focus a single relationship, since there's a lot going on in the question you've posted. We'll look at second-resource.

    It looks like Mirage is sending back second_resource_ids under the attributes key of the first_resource primary data in the JSON:API payload. That tells me Mirage thinks second_resource_ids is an attribute of first_resource, when in fact it's a relationship.

    Assuming your Models & Relationships are setup correctly, you need to tweak the way you're creating data in Mirage.

    If you take a look at the Associations section of the Defining Routes guide, you'll see this message:

    Mirage's database uses camelCase for all model attributes, including foreign keys (e.g. authorId in the example above)

    Right now, you're doing this:

    server.create('second-resource', {
      id: 36,
      first_resource_id: '11',
      my_attribute: "my_attribute"
    });
    
    server.create('first-resource', {
      id: 11,
      second_resource_ids: [36, 37]
    });
    

    But from Mirage's perspective, you need to use camelCase IDs, or just pass in the relationships, to set these up correctly. Something like this:

    let firstResource = server.create('first-resource', {
      id: 11
    });
    
    server.create('second-resource', {
      id: 36,
      firstResource,
      myAttribute: "my_attribute"
    });
    

    You could also pass in the foreign key on creation if you wanted - just be sure to use camelCase:

    server.create('second-resource', {
      id: 36,
      firstResourceId: '11',
      myAttribute: "my_attribute"
    });
    

    Just remember that the formatting decisions for things like attributes and foreign keys (things like some-attribute vs. some_attribute or relationship-id vs. relationship_id) are made at the serializer layer. When dealing with Mirage's ORM and database, you want to stick to camelCase, regardless of the format your Serializer emits.


    For more information, have a look at these sections from the docs: