Ember Data & Relationships. Related data not loading into data store

Hi all,

I’m hoping someone can help me with understanding how to load related data into the data store. I think I’m following the right method of using the include directive on a request and my API seems to be returning the correct information, but the included data in the JSON response isn’t loaded into the data store.

  • I’m using DS.JSONAPIAdapter and I also have a JSON API Spec compliant backend (using fastjson_api gem for RoR).
  • I’m requesting from the API a single user and I’m using the include option to also return the organization that user is associated with
  • The request is formatted correctly (e.g., /users/1?include=organization)
  • The response returns the following:
{
  "data":{
    "id":"1",
    "type":"user",
    "attributes":{
      "id":1,
      "email":"john@test.com",
      "first_name":"John"
    },
    "relationships":{
      "organization":{
        "data":{
          "id":"1",
          "type":"organization"
        }
      }
    }
  },
  "included":[
    {
      "id":"1",
      "type":"organization",
      "attributes":{
        "id":1,
        "name":"Awesome Co."
      }
    }
  ]
}

I should probably note that the relationship is defined in theember/models/user.js as:

organization: belongsTo('agency')

In the ember app, the data is represented as an agency. In the API/backend, it’s represented as an organization.

Using Ember Inspector to look at the ember data store, I can only see the user loaded. I do not see the organization.

Is there something else I should be doing?

In the ember app, the data is represented as an agency . In the API/backend, it’s represented as an organization .

That’s probably the source of the issue. If the model is coming from the API as “organization” and it’s called “agency” in Ember you’ll need to have a custom adapter and serializer for “agency” that tells Ember Data to remap everything related to “agency” to “organization” instead.

I’d look specifically at a few methods:

Serializer

  • modelNameFromPayloadKey - tell ED when it sees the “organization” payload key you want it to map to the “agency” model
  • payloadKeyFromModelName - the inverse of modelNameFromPayloadKey, when creating a payload to send to the backend you want it to map “agency” model name to the “organization” payload key

Adapter

  • pathForType - customize the URL path that ED makes requests to on the backend (by default it will use “agencies”, you want to change it to return “organizations”)

There are most likely some other things you’ll have to customize but those are hopefully a good start.

1 Like

Thanks @dknutsen! I’ll give the Serializer approach a shot.

Just to be sure, I would override these methods on the ApplicationSerializer (e.g., app/serializers/application.js), correct?

Or would I override it specifically on the UserSerializer (e.g., app/serializers/user.js)?

Also, someone on the Discord chat pointed me to using the attrs object within DS.JSONAPISerializer (docs).

Curious if you had any experience with this and/or if you think it might work?

For easy reference, here’s the documentation:

attrs

MODULE: ember-data

Inherited from DS.JSONSerializer addon/serializers/json.js:107

The attrs object can be used to declare a simple mapping between property names on DS.Model records and payload keys in the serialized JSON object representing the record. An object with the property key can also be used to designate the attribute’s key on the response payload.

Example

app/models/person.js import DS from ‘ember-data’;

export default DS.Model.extend({
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  occupation: DS.attr('string'),
  admin: DS.attr('boolean')
});

app/serializers/person.js

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
  attrs: {
    admin: 'is_admin',
    occupation: { key: 'career' }
  }
});

You can also remove attributes and relationships by setting the serialize key to false in your mapping object.

Example

app/serializers/person.js

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
  attrs: {
    admin: { serialize: false },
    occupation: { key: 'career' }
  }
});

When serialized:

{
  "firstName": "Harry",
  "lastName": "Houdini",
  "career": "magician"
}

Note that the admin is now not included in the payload.

Setting serialize to true enforces serialization for hasMany relationships even if it’s neither a many-to-many nor many-to-none relationship.

Thanks again, @dknutsen!

Oh yeah that’s a really good place to look as well. Where you put these things kinda depends on the context. The methods I recommend looking at should probably go on the “agency” serializer and adapter. That tells Ember Data how to manage records of type “agency”, both payloads (serializer) and requests (adapter). But you might have to make changes on the application serializer as well to tell it that any references to “organization” should be mapped to “agency”. Ideally you wouldn’t have to make changes to the user serializer because then you’d have to make the same modifications to anything else that had a relationship with organization.

I’ve never attempted to use a completely different name for a model type before, just change the pluralization or general form of the name (camel_case → camelCase, etc) so right offhand I’m not totally sure what all you’ll have to do, but definitely try and start with the most general purpose things possible. Ember Data lets you customize really fine grained things so the “further up” the serialization chain you start the easier it will be.

Thanks again, @dknutsen.

At first approach, I thought it made sense to override the method on the UserSerializer, since that’s the model making the request. Also, since the mapping is in the User model. My thinking was that how would Ember know to even look at the AgencySerializer if the JSON key is organization. In any case, I could be wrong! :slight_smile:

Here’s what it looks like:

import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
  modelNameFromPayloadKey(key) {
    if (key === 'organization') {
      key = 'agency'
    }

    return key;
  }
});

When it tries to serialize the data, I get this error:

"Assertion Failed: You tried to push data with a type 'organization' but no model could be found with that name."

You might need to define keyForRelationship on your application serializer too… and use a similar if/return.

That method definitely needed to be overridden as I could see that the organization key was being used when stepping through the debugger.

Going back to you previous post, maybe I should try following your advice by going “further up” the serialization chain by starting with ApplicationSerializer?

I’ll give that a shot now.

That did the trick — moving it up to ApplicationSerializer! Thanks, @dknutsen!

1 Like

Hmm. While the organization is loaded into the data store, it doesn’t seem to be associated with the user model.

In Ember Inspector > Data, and then inspecting the user that was loaded, the organization field shows up as: <(unknown):ember493>.

When I try to access the organization via the user in the console, it returns a proxy object.

I thought I would have access to the agency that is loaded in the data store.

@dknutsen I found this tutorial about mapping model types/names between the Ember App and the API Backend – it was written last year. It seems to be exactly what I want to do.

It instructs overriding the same methods that you pointed out, so I didn’t have to do much work.

I did, however, write the unit test it suggests. I thought the normalizedResponse results looked a bit random/arbitrary. Some elements were repositioned in the hash while others completely removed with no rhyme or reason that I could guess. Maybe you could shed some light on this?

In any case, I followed along with my own set of models. The tests pass. However, the relationships still don’t seem to be set up properly between the records in the database. It is the same as a described in the post previous to this—unknown and proxy objects.

Got any ideas?

Thanks in advance!

Hmmmm… that’s annoying. My guess is that it’s still not correctly serializing this portion of the response:

    "relationships":{
      "organization":{
        "data":{
          "id":"1",
          "type":"organization"
        }
      }
    }
  },

Specifically line 5 (“type”: “organization”). Like it seems to know that it has a relationship, and the right id for that relationship, and the right name for the relationship, but it doesn’t seem to know what type of record that related object is supposed to be. Maybe play around with extractRelationship?