[SOLVED] Ember Data JSON API included relationships


#1

We have built our API using katharsis.io and a relatively complicated domain model. I’m trying to reduce the number of round trips to the server to fill out the information on the client and have enabled the ability for Katharsis to send some of the relationships directly in the “included” block. I can see that the data is being sent, but Ember Data appears to ignore that information and make a new request for the included data.

Is Ember Data capable of handling included relationships? If so, is there anything I need to do to make that work correctly?

Thanks, Craig


#2

May be you need to setup your relationship to be synchronous instead of async


#3

Ahh… that may be it. I will give it a try. Thanks!


#4

That really sounded like it might be the key. Unfortunately, when I switch those relationships to synchronous, it doesn’t see the included data and because it is synchronous, it also doesn’t go get it at all. So, it is worse because the result is actually broken versus just making too many calls.

Any other ideas?


#5

Am new to emberjs but did you follow the guide on defining include? Am not on PC so cannot help check it


#6

Any chance you can paste an example of the data that is coming back from the server?

It sounds like what you want to use is “embedded records”, to do that you need to define a serializer for the parent model and use the embedded records mixin. I’m using this myself and it’s working like a charm :slight_smile:

http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html


#7

Thanks for the idea on this. On first attempt it didn’t seem to be working. However, I may have done something wrong. If I’m using the mixin does the model need to be set to synchronous? I had it set that way and was seeing warnings in the console.

Unfortunately, I’ve coded a good bit of application logic with the assumption of async loads and the need to explicitly unwrap promises, so I very well may be hitting that rather than embedded records behavior.


#8

@Craig_Setera Do you have the “relationships” property and is it set up correctly? I wrote about this scenario in a blog post not too long ago. http://thejsguy.com/2015/12/05/which-ember-data-serializer-should-i-use.html. Look at the section JSONAPISerializer.


#9

In my scenario I fetch a single project that has child tasks, in the project model I have:

tasks: DS.hasMany('task', {inverse: null})

(The inverse:null isn’t needed as far as I know, I use it because we have some odd rules that I need to apply when viewing a single project).

In the serializer I have:

import DS from 'ember-data'; export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { 'tasks': { embedded: 'always' } }, });

Excuse the weird formatting, for some reason when I put in the line breaks I lose the code tags, sorry about that.


#10

Shouldn’t you be extending from JSONAPISerializer instead of RESTSerializer?


#11

That is what I tried initially. However, I’ve had to focus over the weekend on an upcoming deadline and have not had time to look at this any more for the time being. I’m hopeful that when I have more time that some combination of these ideas will help me get this going. If I manage to get things going, I will report back here on how I accomplished it.


[SOLVED] EmbeddedRecordsMixin for JSON:API data structures?
#12

I use the RESTSerializer but that’s because it’s default behaviour is a better fit for the main API we use :slight_smile:


[SOLVED] EmbeddedRecordsMixin for JSON:API data structures?
#13

I’m finally getting a chance to revisit this issue and provide more information in hopes that someone can help me figure out why this still is not working. What I see essentially is that the embedded records are in the Ember data store, but that they are not be rendered in my HBS template.

The data model is defined as:

import DS from 'ember-data';
import BaseObject from './base-object';

export default BaseObject.extend({
    'phones': DS.hasMany('phone', { async: false }),
    'addresses': DS.hasMany('address', { async: false }),
    'emails': DS.hasMany('email', { async: false }),
    'websites': DS.hasMany('website', { async: false }),
});

I have an associated serializer definition using the EmbeddedRecordsMixin for this particular model, where the application serializer is a subclass of JSONAPISerializer:

import Ember from 'ember';
import DS from 'ember-data';
import ApplicationSerializer from './application';

export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
      addresses: { embedded: 'always' },
      emails: { embedded: 'always' },
    phones: { embedded: 'always' },
    websites: { embedded: 'always' }
  }
});

The server is responding to the request with this data structure:

{
  "data": {
    "type": "contact-datum",
    "id": "1",
    "attributes": {
      "updated-on": null,
      "object-state": "SAVED",
      "created-on": "2016-01-29",
      "updated-by": null,
      "uuid": "35ee70fd-51e1-4fb2-97e7-521532bb8638",
      "created-by": "SYSTEM"
    },
    "relationships": {
      "websites": {
        "links": {
          "self": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/relationships/websites",
          "related": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/websites"
        }
      },
      "emails": {
        "links": {
          "self": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/relationships/emails",
          "related": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/emails"
        }
      },
      "addresses": {
        "links": {
          "self": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/relationships/addresses",
          "related": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/addresses"
        }
      },
      "phones": {
        "links": {
          "self": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/relationships/phones",
          "related": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1/phones"
        }
      }
    },
    "links": {
      "self": "http://docker:28080/bv-core/domain/jsonapi/v1/contact-datum/1"
    }
  },
  "included": [
    {
      "type": "websites",
      "id": "1",
      "attributes": {
        "updated-on": null,
        "object-state": "SAVED",
        "created-on": "2016-01-29",
        "updated-by": null,
        "type": "BUSINESS",
        "uuid": "6c2535eb-14b1-4cc6-90a7-be9807559341",
        "created-by": "SYSTEM",
        "url": "www.xxxx.com"
      },
      "relationships": {},
      "links": {
        "self": "http://docker:28080/bv-core/domain/jsonapi/v1/websites/1"
      }
    },
    {
      "type": "emails",
      "id": "1",
      "attributes": {
        "address": "info@xxxx.com",
        "updated-on": null,
        "object-state": "SAVED",
        "created-on": "2016-01-29",
        "updated-by": null,
        "type": "BUSINESS",
        "uuid": "ad9876a5-03dd-4a74-96f8-fb0796e479a4",
        "created-by": "SYSTEM"
      },
      "relationships": {},
      "links": {
        "self": "http://docker:28080/bv-core/domain/jsonapi/v1/emails/1"
      }
    },
    {
      "type": "addresses",
      "id": "1",
      "attributes": {
        "country": null,
        "city": "Cleveland",
        "created-on": "2016-01-29",
        "updated-by": null,
        "type": "BUSINESS",
        "uuid": "dd6b7661-7be1-4f9b-b0df-90e466e4f6ca",
        "zipcode": "44101",
        "updated-on": null,
        "object-state": "SAVED",
        "street1": "100 Pine Road",
        "state": "OH",
        "street2": "Suite 125",
        "created-by": "SYSTEM"
      },
      "relationships": {},
      "links": {
        "self": "http://docker:28080/bv-core/domain/jsonapi/v1/addresses/1"
      }
    },
    {
      "type": "phones",
      "id": "1",
      "attributes": {
        "number": "216-555-6446",
        "updated-on": null,
        "object-state": "SAVED",
        "created-on": "2016-01-29",
        "updated-by": null,
        "type": "BUSINESS",
        "uuid": "95a6c3a1-25e3-4153-b25e-4cf6d54bfd61",
        "created-by": "SYSTEM"
      },
      "relationships": {},
      "links": {
        "self": "http://docker:28080/bv-core/domain/jsonapi/v1/phones/1"
      }
    }
  ]
}

As I said, according to the Ember browser plugin, the records appear to be there and be properly linked together. However, the values do not show up when the template is rendered. I added some logging to the template and I’m seeing the following information relative to the “addresses” when I attempt to render them:

WARNING: You have pushed a record of type 'contact-data' with 'phones' as a link, but the association is not an async relationship.
ember.debug.js:5533 WARNING: You have pushed a record of type 'contact-data' with 'addresses' as a link, but the association is not an async relationship.
ember.debug.js:5533 WARNING: You have pushed a record of type 'contact-data' with 'emails' as a link, but the association is not an async relationship.
ember.debug.js:5533 WARNING: You have pushed a record of type 'contact-data' with 'websites' as a link, but the association is not an async relationship.
ember.debug.js:7866 Addresses Class {canonicalState: Array[0], store: Class, relationship: ember$data$lib$system$relationships$state$has$many$$ManyRelationship, record: InternalModel, currentState: Array[0]…}[]: (...)get []: GETTER_FUNCTION()set []: SETTER_FUNCTION(value)__ember1454094611514: null__ember_meta__: Object__nextSuper: undefinedcanonicalState: Array[0]currentState: Array[0]isLoaded: trueisPolymorphic: undefinedrecord: InternalModelrelationship: ember$data$lib$system$relationships$state$has$many$$ManyRelationshipstore: Classtype: bv-ember-application@model:address:__proto__: Class@each: ComputedProperty[]: Object__ember1454094611514: null__ember_meta__: Object_super: superFunction()addArrayObserver: (target, opts)addEnumerableObserver: (target, opts)addObject: (obj)addObjects: (objects)addRecord: (record)any: (callback, target)anyBy: ()arrayContentDidChange: (startIdx, removeAmt, addAmt)arrayContentWillChange: (startIdx, removeAmt, addAmt)canonicalState: nullclear: ()compact: ()constructor: DS.ManyArraycontains: (obj)createRecord: (hash)currentState: nullenumerableContentDidChange: (removing, adding)enumerableContentWillChange: (removing, adding)every: (callback, target)everyBy: ()everyProperty: ()filter: (callback, target)filterBy: (key, value)filterProperty: ()find: (callback, target)findBy: (key, value)findProperty: ()firstObject: ObjectflushCanonical: ()forEach: (callback, target)getEach: (key)has: (name)hasArrayObservers: ComputedPropertyhasEnumerableObservers: ComputedPropertyindexOf: (object, startAt)init: ()insertAt: (idx, object)internalAddRecords: (records, idx)internalRemoveRecords: (records)internalReplace: (idx, amt, objects)invoke: (methodName)isAny: (key, value)isEvery: (key, value)isLoaded: falseisPolymorphic: falselastIndexOf: (object, startAt)lastObject: Objectlength: 0loadedRecord: ()loadingRecordsCount: (count)map: (callback, target)mapBy: (key)mapProperty: ()meta: nullnextObject: (idx)objectAt: (index)objectsAt: (indexes)off: (name, target, method)on: (name, target, method)one: (name, target, method)popObject: ()promise: nullpushObject: (obj)pushObjects: (objects)record: nullreduce: (callback, initialValue, reducerProperty)reject: (callback, target)rejectBy: (key, value)rejectProperty: ()relationship: nullreload: ()removeArrayObserver: (target, opts)removeAt: (start, len)removeEnumerableObserver: (target, opts)removeObject: (obj)removeObjects: (objects)removeRecord: (record)replace: (idx, amt, objects)reverseObjects: ()save: ()setEach: (key, value)setObjects: (objects)shiftObject: ()slice: (beginIndex, endIndex)some: ()someProperty: ()sortBy: ()toArray: ()trigger: (name)uniq: ()unshiftObject: (obj)unshiftObjects: (objects)without: (value)__proto__: Class

The logging that is inside the #each for the addresses array never logs anything and appears to never be entered.

Does anyone have any further thoughts based on these details?

Thanks again, Craig


#14

As a follow up to this. It turns out that the server library we are using (Katharsis) was including the data in the included block of the response, but was not including the “data” block in the relationships that would reference the included data. Once I fixed the server-side configuration such that Katharsis sent both the resource definitions in the included block as well as the data blocks in the relationships, Ember Data “just works” as I would expect it to work without the need for the EmbeddedRecordsMixin.

I apologize for the confusion on this and want to thank everyone for all of their thoughts.

Thanks, Craig


#15

Hi @Craig_Setera,

could you tell me how have you configured katharsis? I have the same issue but I’m not able to get included data in the “data” block.

Thanks in advance.

Gianluca


#16

Gianluca,

I can’t honestly remember what we had to do to get things working at the time. Unfortunately, we’ve since switched to the Crnk version of things and all of their annotations have changed.

Craig