Should there be an easy way to normalize incoming JSON payload?


#1

In the application I’m working there’s a requirement to execute a specific action on server. This action, as a side-effect updates a model. We’re using Ember 1.0/data beta2.

Let’s say we’re talking about publishing a post. Clicking “publish” button in the Ember app creates a POST request to “/posts/123/publish”.

The server-side app updates “published_at” field in the database and renders back the updated post JSON:

{
  "post": {
    "id": 123,
    "published_at": "2013-09-24T11:37:53.405Z"
  }
}

Now we want to push the updated record back to the store so that a user can see updates in the UI. But before I can use the @store.push I need to normalize the record through my serializer.

Application.PostController = Em.ObjectController.extend
  actions:
    publish: ->
      $.post("/posts/#{@get('id')}/publish")
        .then (data) =>
          normalizedObject = @store.serializerFor('post').normalize(@store.modelFor('post'), data.post)
          @store.push 'post', normalizedObject
        .then null, =>
          alert("Error occured")

The line where I call normalize is pretty wild and very non-DRY. Should there be an easier way to do this?

Maybe store itself should have a normalize method the same way it has a serialize method? Or maybe there should just be an option on push that would ask store to normalize the data before pushing?

Something like this would probably make developers’ lives easier:

$.post("/posts/#{@get('id')}/publish")
  .then (data) =>
    @store.push 'post', data.post, normalize: true

Adding a function to ED store to normalize+push a single type
#2

It you want to be more DRY, I suppose you could create an ApplicationSerializer that applies to all models.

App.ApplicationSerializer = DS.RESTSerializer.extend({
    // Override normalize method in all models

    normalize: function(type, hash, property) {
        var json = {};

        // normalize the underscored properties
        for (var prop in hash) {
            json[prop.camelize()] = hash[prop];
        }

        // delegate to any type-specific normalizations
        return this._super(type, json, property);
    }
});

Depending on the complexity of your needs you can even have specific models inherit from this serializer and then override functionality of the normalize method/hook.

App.PostSerializer = App.ApplicationSerializer.extend({
  // ...
});

If you need to tweak the normalize method, you might even be able to hack a parameter type you pass.

OOP is your friend in this case.

Some good information here

https://github.com/emberjs/data/blob/master/TRANSITION.md


#3

Thanks, but I already have an ApplicationSerializer (and the only way I found to access it from controller is to use tihs.store.serializerFor('model')). The problem is that store.push method requires that the passed object is already normalized — the store will completely ignore any adapters (application or model specific) that may have been defined.

So this is more a question of whether anyone here feels a small patch to Store may be warranted to make this use case happier :smile:


#4

Ahh I see. Misunderstood the question.

I suppose the other approach to take here is to start hacking the internals.

Perhaps reopen the class and modify the behavior of the pushPayload method? Just a guess.

Probably want to preserve the default behavior, but maybe add another parameter to the method and do something custom.

Not sure all the implications of this approach though, so probably want to thoroughly test the side effects and of course use at your own risk.

But the push method looks relatively straightforward.

    @method push
    @param {String} type
    @param {Object} payload
  */

  pushPayload: function (type, payload) {
    var serializer = this.serializerFor(type);
    serializer.pushPayload(this, payload);
  },

Even safer is probably just create another method that does its work and then passes on to the original method

    @method custompush
    @param {String} type
    @param {Object} payload
  */

 customPushPayload: function (type, payload) {
   // implement the specific behavior
  },

There might be a better way. I would go read all the code in store.js first.

https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/store.js


#5

I’m very much in favor of normalize: true. Seems to me as if this is a pretty common use case.


#6

+1, there should be a store.push that goes through deserialization.