Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Feed Normalization with Ember Data 1.0

9 163 vues

Publié le

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.

Publié dans : Internet
  • Identifiez-vous pour voir les commentaires

Feed Normalization with Ember Data 1.0

  1. 1. Normalizing with Ember Data 1.0b Jeremy Gillick
  2. 2. or
  3. 3. True Facts of Using Data in Ember
  4. 4. I’m Jeremy http://mozmonkey.com https://github.com/jgillick/ https://linkedin.com/in/jgillick
  5. 5. I work at Nest
  6. 6. We love Ember depending on the day
  7. 7. Ember Data is Great Except when data feeds don’t conform
  8. 8. Serializers connect Raw Data to Ember Data { … } JSON Serializer Ember Data
  9. 9. Let’s talk about data
  10. 10. Ember prefers side loading to nested JSON But why?
  11. 11. 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"! }! }! ]! }
  12. 12. {! "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?
  13. 13. 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"! }! ]! }
  14. 14. Ember Data Expects {! "modelOneRecord": {! ...! }! "modelTwoRecords": [! { ... },! { ... }! ],! "modelThreeRecords": [! { ... },! { ... }! ]! } No further nesting is allowed
  15. 15. Ember Data Expects {! "posts": [! ...! ],! ! "users": [! …! ]! } App.Post records App.User records
  16. 16. Not all JSON APIs will be flat
  17. 17. 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"]! }! ]! }! ]! }
  18. 18. Ember Data can’t process that
  19. 19. {! "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
  20. 20. How do we do this? With a custom Ember Data Serializer!
  21. 21. Two common ways • Create ProductSerializer that manually converts the JSON • A lot of very specific code that you’ll have to repeat for all nested JSON payloads. • Build a generic serializer that automatically flattens nested JSON objects • Good, generic, DRY
  22. 22. Defining 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()! });
  23. 23. 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
  24. 24. {! "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 $$$ Profit $$$
  25. 25. JS Methods extract: function(store, type, payload, id, requestType) { ... } processRelationships: function(store, type, payload, hash) { ... } sideloadRecord: function(store, type, payload, hash) { ... }
  26. 26. 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);! }! ! });
  27. 27. {! "products": [! {! ...! }! ]! } extract: function(store, type, payload, id, requestType) {! return this._super(store, type, payload, id, requestType);! }
  28. 28. {! "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);! }
  29. 29. 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": [! {! ...! }! ]! }
  30. 30. {! "products": [! {! ...! }! ]! } product Singularize container.lookup(‘model:product’) App.Product "products"
  31. 31. 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": [! {! ...! }! ]! }
  32. 32. {! "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);! }
  33. 33. 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": ! [! {! ...! }! ]! }
  34. 34. /**! 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) {! ! },
  35. 35. {! "products": [! {! ...! }! ]! } processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! return hash;! },
  36. 36. {! "products": [! {! ...! }! ]! } processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! else {! ! }! ! return hash;! },
  37. 37. {! "products": [! {! ...! }! ]! } processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! else {! ! // Find all relationships in this model! type.eachRelationship(function(key, relationship) {! ! }, this);! }! ! return hash;! },
  38. 38. ! App.Product.eachRelationship(function(key, relationship) {! ! ! }, this);! 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')! }); key = 'price'! relationship = {! "type": App.Price,! "kind": "belongsTo",! ...! } key = 'size'! relationship = {! "type": App.Dimension,! "kind": "belongsTo",! ...! } key = 'options'! relationship = {! "type": App.Option,! "kind": "hasMany",! ...! }
  39. 39. {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! } processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! else {! ! // Find all relationships in this model! type.eachRelationship(function(key, relationship) {! var related = hash[key]; // The hash for this relationship! ! }, this);! }! ! return hash;! },
  40. 40. processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! else {! ! // Find all relationships in this model! type.eachRelationship(function(key, relationship) {! var related = hash[key], // The hash for this relationship! relType = relationship.type; // The model for this relationship ! }, this);! }! ! return hash;! }, {! "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.Price
  41. 41. processRelationships: function(store, type, payload, hash) {! ! // If hash is an array, process each item in the array! if (hash instanceof Array) {! hash.forEach(function(item, i){! hash[i] = this.processRelationships(store, type, payload, item);! }, this);! }! ! else {! ! // Find all relationships in this model! type.eachRelationship(function(key, relationship) {! var related = hash[key], ! relType = relationship.type;! ! hash[key] = this.sideloadRecord(store, relType, payload, related);! ! }, this);! }! ! return hash;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! }
  42. 42. /**! Sideload a record hash to the payload! ! @method sideloadRecord! @param {DS.Store} store! @param {DS.Model} type! @param {Object} payload The entire payload! @param {Object} hash The record hash object! @return {Object} The ID of the record(s) sideloaded! */! sideloadRecord: function(store, type, payload, hash) {! ! },
  43. 43. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! ! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! }
  44. 44. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! // Sideload record! else if (typeof hash === 'object') {! ! }! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ]! }
  45. 45. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! // Sideload record! else if (typeof hash === 'object') {! sideLoadkey = type.typeKey.pluralize(); ! sideloadArr = payload[sideLoadkey] || [];! }! ! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ],! "prices": [! ]! }
  46. 46. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! // Sideload record! else if (typeof hash === 'object') {! sideLoadkey = type.typeKey.pluralize(); ! sideloadArr = payload[sideLoadkey] || [];! id = this.generateID(store, type, hash);! }! ! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "id": “generated-1",! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ],! "prices": [! ]! } Every record needs an ID
  47. 47. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! // Sideload record! else if (typeof hash === 'object') {! sideLoadkey = type.typeKey.pluralize(); ! sideloadArr = payload[sideLoadkey] || [];! ! // Sideload, if it's not already sideloaded! if (sideloadArr.findBy('id', id) === undefined){! sideloadArr.push(hash);! payload[sideLoadkey] = sideloadArr;! }! }! ! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": ! {! "id": “generated-1",! "value": 59.99,! "currency": "USD"! },! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ],! "prices": [! {! "id": “generated-1",! "value": 59.99,! "currency": "USD"! }! ]! }
  48. 48. sideloadRecord: function(store, type, payload, hash) {! var id, sideLoadkey, sideloadArr, serializer;! ! // If hash is an array, sideload each item in the array! if (hash instanceof Array) {! id = [];! hash.forEach(function(item, i){! id[i] = this.sideloadRecord(store, type, payload, item);! }, this);! }! // Sideload record! else if (typeof hash === 'object') {! sideLoadkey = type.typeKey.pluralize(); ! sideloadArr = payload[sideLoadkey] || [];! ! // Sideload, if it's not already sideloaded! if (sideloadArr.findBy('id', id) === undefined){! sideloadArr.push(hash);! payload[sideLoadkey] = sideloadArr;! }! }! ! return id;! }, {! "products": [! {! "name": "Robot",! "description": "...",! "price": "generated-1",! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ],! "prices": [! {! "id": "generated-1",! "value": 59.99,! "currency": "USD"! }! ]! } processRelationships: function(store, type, payload, hash) {! ...! hash[key] = this.sideloadRecord(store, relType, payload, related);! ...! },
  49. 49. {! "products": [! {! "name": "Robot",! "description": "...",! "price": "generated-1",! "size": "generated-2",! "options": [! “generated-3”! ]! }! ],! "prices": [! {! "id": "generated-1",! "value": 59.99,! "currency": "USD"! }! ],! "dimensions": [{! "id": "generated-2",! "height": 24,! "width": 12,! "depth": 14! }],! "options": [ ! {! "id": "generated-3",! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! } {! "products": [! {! "name": "Robot",! "description": "...",! "price": "generated-1",! "size": {! "height": 24,! "width": 12,! "depth": 14! },! "options": [! {! "name": "Color",! "values": ["silver", ! "black", ! "#E1563F"]! }! ]! }! ],! "prices": [! {! "id": "generated-1",! "value": 59.99,! "currency": "USD"! }! ]! }
  50. 50. Apply the Serializer App.ApplicationSerializer = App.NestedSerializer; App.ProductSerializer = App.NestedSerializer.extend({}); - OR -
  51. 51. Now for a demo
  52. 52. http://emberjs.jsbin.com/neriyi/edit
  53. 53. http://emberjs.jsbin.com/neriyi/edit Questions? http://www.slideshare.net/JeremyGillick/normalizing-data

×