Friday, March 1, 2013

EmberJS, Notes and Gotchas

JSON Interface
Description of the expected REST interface, adapted from http://stackoverflow.com/questions/14922623/what-is-the-complete-list-of-expected-json-responses-for-ds-restadapter :

ContextServerURLMethodReq. DataResp. Data
Get list of all/usersGET{"users":[{...},{...}]}
Get one/users/123GET{"user":{...}}
Create/usersPOST{"user":{...}}{"user":{...}}
Update/users/123PUT{"user":{...}}{"user":{...}}
Delete/users/123DELETEN/Anull
Create in bulk/usersPOST{"users":[{...},{...}]}{"users":[{...},{...}]}
Update in bulk/users/bulkPUT{"users":[{...},{...}]}{"users":[{...},{...}]}
Delete in bulk/users/bulkDELETE{"users":[1,2]}{"users":[1,2]}

This is EmberJS's documentation on this:
http://emberjs.com/guides/models/the-rest-adapter/#toc_the-rest-adapter

Ember Gotchas!
  • Ember tolerates no unmapped fields in your JSON results! Presence of these will result in an Ember.assert() error, which in Firefox produced no logging whatsoever, just a silent failure!
    • Example: if your data looks like this {"users":[{"name":"bob","id":3}], "count":1}
      and Ember does not know about the 'count' field, JSON response processing will abort
  • Ember transforms camel-cased model field names into underscored names when emitting JSON, and expects underscored names in retreivals:
    • e.g. model field 'isCompleted' will be sent in JSON updates as 'is_completed' - and must be sent as 'is_completed' as well
    • WORKAROUND: you must explicitly map the attribute in the RESTAdapter - see the EmberJS documentation here
    • or see the related Question below for a workaround
Questions
window.Todos.TodoRESTAdapter = DS.RESTSerializer.extend({
   keyForAttributeName: function(type, name) {
     return name;
   },

   keyForBelongsTo: function(type, name) {
     var key = this.keyForAttributeName(type, name);

     if (this.embeddedType(type, name)) {
       return key;
     }

     return key + "Id";
   },

   keyForHasMany: function(type, name) {
     var key = this.keyForAttributeName(type, name);

     if (this.embeddedType(type, name)) {
       return key;
     }

     return this.singularize(key) + "Ids";
   }
});
and then use this in your Stores:
Todos.Store = DS.Store.extend({
 revision: 11,
    adapter: DS.RESTAdapter.create({ 
     bulkCommit: false, 
     namespace: "todo/api"
     serializer: Todos.TodoRESTAdapter
    })
});
  • Q: How to do the analagous replacement of underscore() when serializer is serializing back to REST?
    • A: Here is how serializing to REST gets done:
      • base class DS.Serializer.serialze() is invoked
      • calls DS.Serializer().addAttributes() which loops through all attributes
      • calls DS.Serializer().addAttribute() which can be overridden
        • calls _keyForAttributeName()
          • eventually calls keyForAttributeName() in the Serializer
          • this is where we override
  • Q: how should I represent nested data elements?

Execution of find()
  • findAll: function(store, type, since)
    • contains a 'success': callback - set breakpoint here
    • calls didFindAll()
      • uses a Loader, acquired as DS.loaderFor(store), and a Serializer
      • invokes serializer.extractMany(loader, payload, type)
        • where payload is your data! and type = model name
        • stepping into this puts you into a wrapper - step again into the 'apply()'
        • first parses sideloaded data using sideload()
          • see this for description of sideloading:
        • extractRecordRepresentation()
          • getMappingForType()
          • if [ should sideload ]
            • loader.sideload()
          • else
            • loader.load()
              • store.load()
                • this adds a prematerialization entry into a list for later parsing
                • somewhere much deeper in the stack, we get to....
                • DS.RESTSerializer.keyForAttributeName()
                  • which calls Ember.String.decamelize()
                  • this is where underscores are introduced
Pluralizing Strategy
  • executed in method: pluralize()
  • option 1: provide a mapping in definition of the RESTAdapter, as ....

    adapter: DS.RESTAdapter.create({ bulkCommit: false,
plurals: {"security": "securities"} })
  • option 2: defaults to name + "s"
Data Representation Internals, Notes on
  • maintains a 'stringCache' which JSON string field names are looked up against
    • these are given codes like 'st295' where the # is the field uuid

3 comments:

  1. You may want to check out my ember-data-grails project. It eases the process of turning a Grails server into a RESTful backend that Ember.js and Ember-data will interact with into a cinch.

    https://github.com/zachriggle/ember-data-grails

    ReplyDelete
  2. THIS should be on the ember documentation. Very useful, many thanks for posting this.

    ReplyDelete