Feed Normalization with Ember Data 1.0

download Feed Normalization with Ember Data 1.0

of 53

  • date post

    23-Aug-2014
  • Category

    Internet

  • view

    5.702
  • download

    11

Embed Size (px)

description

So you're working with a web service that doesn't play nice with Ember Data, that's okay! Using Ember Data 1.0.0-beta we will normalize ugly JSON feeds into something that Ember understands and loves.

Transcript of Feed Normalization with Ember Data 1.0

  • Normalizing with Ember Data 1.0b Jeremy Gillick
  • or
  • True Facts of Using Data in Ember
  • Im Jeremy http://mozmonkey.com https://github.com/jgillick/ https://linkedin.com/in/jgillick
  • I work at Nest
  • We love Ember depending on the day
  • Ember Data is Great Except when data feeds dont conform
  • Serializers connect Raw Data to Ember Data { } JSON Serializer Ember Data
  • Lets talk about data
  • Ember prefers side loading to nested JSON But why?
  • For example {! "posts": [! {! "id": 5,! "title":You won't believe what was hiding in this kid's locker",! "body": "...",! "author": {! "name": "Jeremy Gillick",! "role": "Author",! "email": "spam-me@please.com"! }! }! ]! }
  • {! "posts": [! {! "id": 6,! "title": "New Study: Apricots May Help Cure Glaucoma",! "body": "...",! "author": {! "name": "Jeremy Gillick",! "role": "Author",! "email": "spam-me@please.com"! }! },! {! "id": 5,! "title": "You won't believe what was hiding in this kid's locker",! "body": "...",! "author": {! "name": "Jeremy Gillick",! "role": "Author",! "email": "spam-me@please.com"! }! }! ]! } For example Redundant, adds feed bloat and which one is the source of truth?
  • This is better {! "posts": [! {! "id": 4,! "title": "New Study: Apricots May Help Cure Glaucoma",! "body": "...",! "author": 42! },! {! "id": 5,! "title": "You won't believe what was hiding in this kid's locker",! "body": "...",! "author": 42! }! ],! "users": [! {! "id": 42,! "name": "Jeremy Gillick",! "role": "Author",! "email": "spam-me@please.com"! }! ]! }
  • Ember Data Expects {! "modelOneRecord": {! ...! }! "modelTwoRecords": [! { ... },! { ... }! ],! "modelThreeRecords": [! { ... },! { ... }! ]! } No further nesting is allowed
  • Ember Data Expects {! "posts": [! ...! ],! ! "users": [! ! ]! } App.Post records App.User records
  • Not all JSON APIs will be at
  • A nested world {! "products": [! {! "name": "Robot",! "description": "A robot may not injure a human being or...",! "price": {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", "black", "#E1563F"]! }! ]! }! ]! }
  • Ember Data cant process that
  • {! "products": [! {! "name": "Robot",! "description": "...",! "price": {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! } {! "products": [! {! "id": "product-1",! "name": "Robot",! "description": ...,! "price": "price-1",! "size": "dimension-1",! "options": [! options-1! ]! }! ],! "prices": [! {! "id": "price-1",! "value": 59.99,! "currency": "USD"! } ! ]! "dimensions": [ ],! "options": [ ]! }! ! Flatten that feed
  • How do we do this? With a custom Ember Data Serializer!
  • Two common ways Create ProductSerializer that manually converts the JSON A lot of very specic code that youll have to repeat for all nested JSON payloads. Build a generic serializer that automatically attens nested JSON objects Good, generic, DRY
  • Dening the model {! "products": [! {! "name": "Robot",! "description": "...",! "price": {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! } App.Product = DS.Model.extend({! name: DS.attr('string'),! description: DS.attr('string'),! price: DS.belongsTo('Price'),! size: DS.belongsTo('Dimension'),! options: DS.hasMany('Option')! });! ! App.Price = DS.Model.extend({! value: DS.attr('number'),! currency: DS.attr('string')! });! ! App.Dimension = DS.Model.extend({! height: DS.attr('number'),! width: DS.attr('number'),! depth: DS.attr('number')! });! ! App.Option = DS.Model.extend({! name: DS.attr('string'),! values: DS.attr()! });
  • Steps Loop through all root JSON properties Determine which model they represent Get all the relationships for that model Side load any of those relationships
  • {! "products": [! {! "name": "Robot",! "description": "...",! "price": {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! } App.Product Relationships price size option Side load $$$ Prot $$$
  • JS Methods extract: function(store, type, payload, id, requestType) { ... } processRelationships: function(store, type, payload, hash) { ... } sideloadRecord: function(store, type, payload, hash) { ... }
  • Create a Serializer /**! Deserialize a nested JSON payload into a flat object! with sideloaded relationships that Ember Data can import.! */! App.NestedSerializer = DS.RESTSerializer.extend({! ! /**! (overloaded method)! Deserialize a JSON payload from the server.! ! @method normalizePayload! @param {Object} payload! @return {Object} the normalized payload! */! extract: function(store, type, payload, id, requestType) {! return this._super(store, type, payload, id, requestType);! }! ! });
  • {! "products": [! {! ...! }! ]! } extract: function(store, type, payload, id, requestType) {! return this._super(store, type, payload, id, requestType);! }
  • {! "products": [! {! ...! }! ]! } extract: function(store, type, payload, id, requestType) {! var rootKeys = Ember.keys(payload);! ! // Loop through root properties and process their relationships! rootKeys.forEach(function(key){! ! }, this);! ! return this._super(store, type, payload, id, requestType);! }
  • extract: function(store, type, payload, id, requestType) {! var rootKeys = Ember.keys(payload);! ! // Loop through root properties and process their relationships! rootKeys.forEach(function(key){! var type = store.container! ! ! ! ! .lookupFactory('model:' + key.singularize());! ! }, this);! ! return this._super(store, type, payload, id, requestType);! } {! "products": [! {! ...! }! ]! }
  • {! "products": [! {! ...! }! ]! } product Singularize container.lookup(model:product) App.Product "products"
  • extract: function(store, type, payload, id, requestType) {! var rootKeys = Ember.keys(payload);! ! // Loop through root properties and process their relationships! rootKeys.forEach(function(key){! var type = store.container! ! ! ! ! .lookupFactory('model:' + key.singularize());! ! }, this);! ! return this._super(store, type, payload, id, requestType);! } {! "products": [! {! ...! }! ]! }
  • {! "products": ! [! {! ...! }! ]! } extract: function(store, type, payload, id, requestType) {! var rootKeys = Ember.keys(payload);! ! // Loop through root properties and process their relationships! rootKeys.forEach(function(key){! var type = store.container! .lookupFactory('model:' + key.singularize()),! hash = payload[key];! ! }, this);! ! return this._super(store, type, payload, id, requestType);! }
  • extract: function(store, type, payload, id, requestType) {! var rootKeys = Ember.keys(payload);! ! // Loop through root properties and process their relationships! rootKeys.forEach(function(key){! var type = store.container! .lookupFactory('model:' + key.singularize()),! hash = payload[key];! ! // Sideload embedded relationships of this model hash! if (type) {! this.processRelationships(store, type, payload, hash);! }! }, this);! ! return this._super(store, type, payload, id, requestType);! } {! "products": ! [! {! ...! }! ]! }
  • /**! Process nested relationships on a single hash record! ! @method extractRelationships! @param {DS.Store} store! @param {DS.Model} type! @param {Object} payload The entire payload! @param {Object} hash The hash for the record being processed! @return {Object} The updated hash object! */! processRelationships: function(store, type, payload, hash) {! ! },