Force Load Ember Data Relationship

I have an ember data model that looks like this (note that I am using typed-ember/ember-cli-typescript)

import DS from "ember-data";

export default class AccountIdentity extends DS.Model.extend({
  organizations: DS.hasMany("org/organization", { async: true })
}) {
  // normal class body definition here
}

// DO NOT DELETE: this is how TypeScript knows how to look up your models.
declare module "ember-data/types/registries/model" {
  export default interface ModelRegistry {
    "account/identity": AccountIdentity;
  }
}

Then I load my account/identity like so:

const identity = this.store.findRecord("account/identity", "abc")

Which calls the api, and the api returns:

{
    "data": {
        "type": "account/identities",
        "id": "abc"
    }
}

Note how the response doesn’t contain any organization relationships, and this is because they are on a different service and the account service doesn’t know anything about the org service. But I was hoping i could override the urlForFindHasMany and tell ember data where to load the organizations from. However when I do:

const organizations = await identity.get("organizations");

My urlForFindHasMany method isn’t called, nor is any other API call of any kind. How can I tell/force ember data to load the relationship data?

I would advocate restructuring the API response to follow the specification by sending back a “compound document” from your server. ember-data would then load auxiliary records based on the relationships specified in the payload. The “Retrieving Related Model Records” section here gives a usage example.

In your case do you have control over what the payload returned by the account/identities endpoint?

Also, are any organizations loaded already into the store?

@efx I don’t have control over the account service. Also, it is meant to be a generic authorization/authentication service used for many applications, and it isn’t supposed to know which applications.

The org service is one such application, and it knows how the identities and organizations are related within itself.

Now I am writing an ember app for the org service which authenticates against the account service. I was hoping that I could somehow tell ember data to load the organizations from the org service instead of expecting them to be specified on the account/identity resource from the account service.

I suppose I could write a custom serializer for the account/identity model that just sticks a related/self link into the response…but I thought there would be a better way to go about this.

Thanks this clarifies a few things for me.

Is org represented as a model in your app? If so I think a findAll using the model name used by the org service could load them as you expect. It seems you would need to load the relationships from whichever service knows about them. Offhandedly I’m not sure how the order of loading models and auxiliary records works so would suggest testing to see.

As an aside you could explore using store.push if you need to load arbitrary JSON:API payloads. But that seems like the same if not more manual work of using a custom serializer.

But I was hoping i could override the urlForFindHasMany and tell ember data where to load the organizations from

If that’s what you’re going for I think all you’d need to do is add relationship “links” in your serializer. The problem is that you are overriding urlForFindHasMany but as far as ED is concerned it doesn’t know that your account is linked to anything, so it doesn’t know it should fetch.

You can link relationship data in multiple ways, like by id (probably the most common) or by links. But ED needs either links or ids to know how/where to look up the relationships and link the records. Typically this information is provided by your API but if it is links and they are able to be generated by data that you already have on the front-end, you can add them in the serializer.

This is what I’ve done in the past. You may be able to import the adapter from the serializer and use that to format the URLs if that is desirable.

// accounts serializer
  normalize: function(typeClass, hash, prop){
    // add a 'users' link to support our 'hasMany'
    hash.links = {
      users: `/accounts/${hash.id}/users`
    };
    return this._super(typeClass, hash);
  },

Other resources:

1 Like

@dknutsen thanks for confirming that my custom serializer idea isn’t crazy.

Not crazy at all. I don’t think there’s a “better way to go about this” because I would argue that this way isn’t bad or the least bit unusual. I mean technically an API would/should typically be the single source of truth about data and how it is related, but I think this is a fairly common problem, and this is a very natural way of supplementing information from the API. So I don’t think it should feel weird.

I think the only reason it seems that way is that ED makes everything so easy that a custom serializer seems like “overkill” in a lot of scenarios even if it shouldn’t. ED is an incredibly powerful data layer with a very broad API surface that is intended to be extensible and configurable and this use case fits pretty nicely within those standard APIs. Ember Data itself only covers the most common data use cases (it’s definitely not good for some things), and you can only get so far with “config free” Ember Data. So… all that to say… far from crazy, I think this is actually pretty standard.

1 Like

Not only is this way not bad, this is the way this should be done.