How do I create included records in Ember Data 2.3?


#1

Hi everyone,

I’m working with a kind of funky api so I need to normalize the data from it into the JSON API that Ember Data wants. I have 2 models in Ember Data:

Task = DS.Model.extend(
  {
    constraints: DS.hasMany('constraint')
    ...

Constraint = DS.Model.extend(
  {
    task: DS.belongsTo('task')

    constraintType: DS.attr('string')
    field: DS.attr('string')
    operator: DS.attr('string')
    value: DS.attr('number')
    ....

My API inlines constraints inside the task response as an array. My goal is to normalize constraints out of tasks on the front end to make application development easier for us.

I tried to follow the instructions spelled out in the Ember Data 1.13 release and use the normalizeResponse hook on the TaskSerializer. However, all of the embedded records are stripped out of the arguments passed into normalizeResponse.

My next thought was to use the normalize hook. This is better because the hash passed into normalize has the raw data from the api in it:

It seems like I should be able to mutate the task data and put all of the constraints into the included array, return that from normalize and let Ember Data handle the rest. Here’s an example of what I’m returning from normalize:

The problem is, this won’t create the constraints in Ember Data. The task is created OK but there are no associated constraints.

My current work-around is to push the constraints onto the store myself in the normalize hook:

normalize: (typeClass, hash) ->
  normalizedTask = @_super(arguments...) 
  included = hash.attributes.constraints.map((constraint, index) =>
    normalizedConstraint = @store.normalize('constraint', constraint)

    @store.push(normalizedConstraint)   <--- why do I need to do this ?

    return normalizedConstraint.data
  )
  constraintRelationships = included.map((constraint) =>
    return {type: constraint.type, id: constraint.id}
  )
  normalizedTask.data.relationships['constraints'] = {data: constraintRelationships}
  normalizedTask['included'] = included
  return normalizedTask

This seems OK to me but I thought it was no longer necessary to push embedded models into the store manually in the normalize hook. Any feedback on this approach or tips on how to do it better would be greatly appreciated.

Thanks!


#2

I found the easiest way to get Ember Data to work with a custom API is to see which serializer the API matches as close as possible, extend it, and then normalize the response to fit that serializer and call this._super() to handle transforming it into the JSON-API document instead of you trying to convert it into a JSON-API document directly.

Can you show a snippet of your raw response data? Also, what store method are you calling?

Thought you might find my most recently blog post helpful since it is about working with custom APIs and serializers: http://thejsguy.com/2016/01/23/ember-data-and-custom-apis-5-common-serializer-customizations.html


#3

I’ve posted the raw response data above (it’s the hash in the first screenshot) and the output from TaskSerializer#normalize is the second screenshot.

I’m calling @store.push() to create the included constraint models (called out by the arrow in the code snippet).

So, everything is working now. But, I don’t understand why I have to explicitly call @store.push() for the constraints when they are all listed in the included array on task.