1
votes

I have an Ember app with a Product model. My server response is json like this (for one entry)

{
   "id": "a01dfaa0",
   "type": "computer",
   "cost": "0.50"
}

or an array of such objects

   [{
       "id": "6b2360d0",
       "type": "fridge",
       "cost": "2.50"
   },{
       "id": "a01dfaa1",
       "type": "car",
       "cost": "5.95"
   }]

In other words, there is no top level namespace (based on the model name), which Ember expects. When I return the data to the Ember app, I know I can run it through the extractArray function to add that namespace (either "product" or "products" I'm assuming) but I can't figure out how to do it. Question: how would I do that

extractArray: function(store, type, payload) {

}

Update, if I use the method proposed by the first answer, namely put this in my DS.RESTSerializer

// Override
extractArray(store, type, payload) {
  payload = this._extractPayload(Ember.String.pluralize(type.typeKey), payload);
  return this._super(store, type, payload);
},

// Override    
extractSingle(store, type, payload, id) {
  payload = this._extractPayload(type.typeKey, payload);
  return this._super(store, type, payload, id);
},

_extractPayload(typeKey, payload) {
  var conformPayload = {};
  if (payload) {
    conformPayload[typeKey] = payload;
  }
  return conformPayload;
}

I get an error when my server responds with an empty array. Ember normally expects this {products : [] } when there are no records, so I'm returning [] when there are no records, but with the above extractArray method, it throws an error, saying the response from findAll must be an array, not undefined Error. When I do console.log(payload) in the extractArray function it shows an empty array []. Question, how do I modify Ember in the RESTSerializer so that it can work with json without the toplevel namespace, and, if it's not supposed to receive an empty array [] when there are no records in the db, then what is it supposed to receive?

Note, since the server expects the json without the toplevel namespace, I I send it to the server through the serializeIntoHash function of the RestAdapter to remove the namespace (the typekey) from the json (but I can't figure out how to add it back in the extractArray function)

  serializeIntoHash: function( hash, type, record, options ) {
    console.log(hash, type, record, options, "h,t,r,o");
    // Usergrid does not expect a type-key
    record.eachAttribute(function( name, meta ) {
      hash[name] = record.get(name);
    });

    return hash;
  }

Update this is the stacktrace of the error I'm getting when I run any of the suggested answers

Error while processing route: gopher Assertion Failed: The response from a findAll must be an Array, not undefined Error: Assertion Failed: The response from a findAll must be an Array, not undefined
    at new Error (native)
    at Error.EmberError (http://localhost:8080/js/vendor.js:22707:21)
    at Object.Ember.default.assert (http://localhost:8080/js/vendor.js:15408:13)
    at http://localhost:8080/js/vendor.js:64040:17
    at Object.Backburner.run (http://localhost:8080/js/vendor.js:10778:27)
    at ember$data$lib$system$store$$Service.extend._adapterRun (http://localhost:8080/js/vendor.js:70295:33)
    at http://localhost:8080/js/vendor.js:64037:15
    at tryCatch (http://localhost:8080/js/vendor.js:55993:16)
    at invokeCallback (http://localhost:8080/js/vendor.js:56005:17)
    at publish (http://localhost:8080/js/vendor.js:55976:11)

Update. This is the full adapter and serializer code (I'm not using Ember-Cli)

App.ApplicationAdapter = DS.RESTAdapter.extend({


});
// Must extend REST serializer to handle Usergrid JSON format, which is 
// different from what Ember-Data expects.
App.ApplicationSerializer = DS.RESTSerializer.extend({

  extractSingle: function(store, type, payload, id) {
  var convertedPayload = {};
  convertedPayload[type.modelName] = payload;
  return this._super(store, type, convertedPayload, id);
},

extractArray: function(store, type, payload) {
  console.log(payload, "payload");
  var convertedPayload = {};
  convertedPayload[type.modelName] = payload;
  return this._super(store, type, convertedPayload);
},


});
3
The title of your question presupposes a particular solution. It would be more general if stated as something like "Ember Data: how to deal with payloads from server missing root key". - user663031
Just tried your code and it works for me... Are you overriding the normalizePayload function? And which version of Ember are you using exactly? Furthermore what is in convertedPayload before you pass it to this._super in extractArray? You can also try to debug into that function to see when the payload goes "undefined"... github.com/emberjs/data/blob/… this is the function where the primaryArray goes undefined... - enspandi

3 Answers

2
votes

You can use the typeKey to create an ember data conform payload like this:

Use type.modelName instead of type.typeKey in ember data beta 18 (http://emberjs.com/blog/2015/05/21/ember-data-1-0-beta-18-released.html)

// Override
extractArray(store, type, payload) {
  payload = this._extractPayload(Ember.String.pluralize(type.typeKey), payload);
  return this._super(store, type, payload);
},

// Override    
extractSingle(store, type, payload, id) {
  payload = this._extractPayload(type.typeKey, payload);
  return this._super(store, type, payload, id);
},

_extractPayload(typeKey, payload) {
  var conformPayload = {};
  if (payload) {
    conformPayload[typeKey] = payload;
  }
  return conformPayload;
}
2
votes

I would suggest defining your own find on the adapter. It would do an ajax call and push the payload into the store. See http://emberjs.com/api/data/classes/DS.Adapter.html#method_find.

The below uses fetch (replace with $.ajax if you prefer).

// adapters/product.js
...
find(store, type, id, snapshot) {
  function get()          { return fetch(url); }
  function json(response) { return response.json(); }
  function push(json)     { return store.pushPayload({ products: json }); }

  var url = this.buildURL ('product', id);
  return get() . then(json) . then(push);
}

Using this.buildURL ensures that the URL is build correctly, including the namespace, pluralization, etc.

Overriding extractArray and its friends is not too much fun and is bug-prone. I'd reserve that approach for trickier cases.

For going back up to the server, using serializeIntoHash is fine, but the following would be bit more compact and robust:

// app/serializers/product.js
serializeIntoHash: function(data, type, record, options) {
  Ember.merge(data, this.serialize(record, options));
}
1
votes

Ember Data 1.0.0-beta.18 and up (using modelName)

serializeIntoHash: function(hash, type, snapshot, options) {
  Ember.merge(hash, this.serialize(snapshot, options));
},

extractSingle: function(store, type, payload, id) {
  var convertedPayload = {};
  convertedPayload[type.modelName] = payload;
  return this._super(store, type, convertedPayload, id);
},

extractArray: function(store, type, payload) {
  var convertedPayload = {};
  convertedPayload[type.modelName] = payload;
  return this._super(store, type, convertedPayload);
}

Here is a working JSBin that demonstrates this: http://jsbin.com/bebori/1/edit?js,output

Ember Data 1.0.0-beta.17 and lower (using typeKey)

serializeIntoHash: function(hash, type, snapshot, options) {
  Ember.merge(hash, this.serialize(snapshot, options));
},

extractSingle: function(store, type, payload, id) {
  var convertedPayload = {};
  convertedPayload[type.typeKey] = payload;
  return this._super(store, type, convertedPayload, id);
},

extractArray: function(store, type, payload) {
  var convertedPayload = {};
  convertedPayload[type.typeKe] = payload;
  return this._super(store, type, convertedPayload);
}

Here is a working JSBin that demonstrates this: http://jsbin.com/hanuwa/1/edit?js,output

I believe the problem with the other answer that looks quite similar to this one is the use of Ember.String.pluralize - I don't think that should be there.