SlideShare ist ein Scribd-Unternehmen logo
1 von 53
Downloaden Sie, um offline zu lesen
Normalizing with Ember
Data 1.0b
Jeremy Gillick
or
True Facts
of Using Data in Ember
I’m 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 don’t conform
Serializers connect Raw
Data to Ember Data
{ … }
JSON
Serializer
Ember Data
Let’s 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 flat
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 can’t 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 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
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()!
});
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
$$$ Profit $$$
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) {!
!
},
{!
"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;!
},
{!
"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;!
},
{!
"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;!
},
!
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",!
...!
}
{!
"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;!
},
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
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"]!
}!
]!
}!
]!
}
/**!
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) {!
!
},
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"]!
}!
]!
}!
]!
}
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"]!
}!
]!
}!
]!
}
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": [!
]!
}
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
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"!
}!
]!
}
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);!
...!
},
{!
"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"!
}!
]!
}
Apply the Serializer
App.ApplicationSerializer = App.NestedSerializer;
App.ProductSerializer = App.NestedSerializer.extend({});
- OR -
Now for a demo
http://emberjs.jsbin.com/neriyi/edit
http://emberjs.jsbin.com/neriyi/edit
Questions?
http://www.slideshare.net/JeremyGillick/normalizing-data

Weitere ähnliche Inhalte

Was ist angesagt?

Why everyone like ruby
Why everyone like rubyWhy everyone like ruby
Why everyone like rubyIvan Grishaev
 
20th.陈晓鸣 百度海量日志分析架构及处理经验分享
20th.陈晓鸣 百度海量日志分析架构及处理经验分享20th.陈晓鸣 百度海量日志分析架构及处理经验分享
20th.陈晓鸣 百度海量日志分析架构及处理经验分享elevenma
 
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...Amazon Web Services
 
CSS: A Slippery Slope to the Backend
CSS: A Slippery Slope to the BackendCSS: A Slippery Slope to the Backend
CSS: A Slippery Slope to the BackendFITC
 
Learn You a Functional JavaScript for Great Good
Learn You a Functional JavaScript for Great GoodLearn You a Functional JavaScript for Great Good
Learn You a Functional JavaScript for Great GoodMike Harris
 
NoSQL & MongoDB
NoSQL & MongoDBNoSQL & MongoDB
NoSQL & MongoDBShuai Liu
 
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's Perspective
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's PerspectiveTearing the Sofa Apart: CouchDB and CouchApps from a Beginner's Perspective
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's PerspectiveSeh Hui Leong
 
Arel - Ruby Relational Algebra
Arel - Ruby Relational AlgebraArel - Ruby Relational Algebra
Arel - Ruby Relational Algebrabrynary
 

Was ist angesagt? (13)

Assetic (Zendcon)
Assetic (Zendcon)Assetic (Zendcon)
Assetic (Zendcon)
 
Why everyone like ruby
Why everyone like rubyWhy everyone like ruby
Why everyone like ruby
 
20th.陈晓鸣 百度海量日志分析架构及处理经验分享
20th.陈晓鸣 百度海量日志分析架构及处理经验分享20th.陈晓鸣 百度海量日志分析架构及处理经验分享
20th.陈晓鸣 百度海量日志分析架构及处理经验分享
 
GraphQL
GraphQLGraphQL
GraphQL
 
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...
(BDT402) Performance Profiling in Production: Analyzing Web Requests at Scale...
 
CSS: A Slippery Slope to the Backend
CSS: A Slippery Slope to the BackendCSS: A Slippery Slope to the Backend
CSS: A Slippery Slope to the Backend
 
Learning How To Use Jquery #3
Learning How To Use Jquery #3Learning How To Use Jquery #3
Learning How To Use Jquery #3
 
Elixir + Neo4j
Elixir + Neo4jElixir + Neo4j
Elixir + Neo4j
 
Learn You a Functional JavaScript for Great Good
Learn You a Functional JavaScript for Great GoodLearn You a Functional JavaScript for Great Good
Learn You a Functional JavaScript for Great Good
 
NoSQL & MongoDB
NoSQL & MongoDBNoSQL & MongoDB
NoSQL & MongoDB
 
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's Perspective
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's PerspectiveTearing the Sofa Apart: CouchDB and CouchApps from a Beginner's Perspective
Tearing the Sofa Apart: CouchDB and CouchApps from a Beginner's Perspective
 
Djangocon
DjangoconDjangocon
Djangocon
 
Arel - Ruby Relational Algebra
Arel - Ruby Relational AlgebraArel - Ruby Relational Algebra
Arel - Ruby Relational Algebra
 

Andere mochten auch

Quadrant holdings issa asad
Quadrant holdings issa asadQuadrant holdings issa asad
Quadrant holdings issa asadissa asad
 
Detecting Reconnaissance Through Packet Forensics by Shashank Nigam
Detecting Reconnaissance Through Packet Forensics by Shashank NigamDetecting Reconnaissance Through Packet Forensics by Shashank Nigam
Detecting Reconnaissance Through Packet Forensics by Shashank NigamOWASP Delhi
 
Change Management 13 things to consider
Change Management 13 things to considerChange Management 13 things to consider
Change Management 13 things to considerpck100
 
Public business law
Public business lawPublic business law
Public business lawJack740
 
νεο λυκειο
νεο λυκειονεο λυκειο
νεο λυκειοelpitheo
 
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation Club
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation ClubTop 5 reasons to explore Saona Island with Lifestyle Holidays Vacation Club
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation ClubLifestyle Holidays Vacation Club
 
More about health
More about healthMore about health
More about healthJack740
 
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...Lifestyle Holidays Vacation Club
 
Understanding patient privacy 1
Understanding patient privacy 1Understanding patient privacy 1
Understanding patient privacy 1Jonsie12
 
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...Lifestyle Holidays Vacation Club
 
Eyeonhome
EyeonhomeEyeonhome
EyeonhomeJack740
 
Home smart home
Home smart homeHome smart home
Home smart homeJack740
 

Andere mochten auch (17)

Quadrant holdings issa asad
Quadrant holdings issa asadQuadrant holdings issa asad
Quadrant holdings issa asad
 
Detecting Reconnaissance Through Packet Forensics by Shashank Nigam
Detecting Reconnaissance Through Packet Forensics by Shashank NigamDetecting Reconnaissance Through Packet Forensics by Shashank Nigam
Detecting Reconnaissance Through Packet Forensics by Shashank Nigam
 
Gajendra_Resume1
Gajendra_Resume1Gajendra_Resume1
Gajendra_Resume1
 
Change Management 13 things to consider
Change Management 13 things to considerChange Management 13 things to consider
Change Management 13 things to consider
 
Public business law
Public business lawPublic business law
Public business law
 
νεο λυκειο
νεο λυκειονεο λυκειο
νεο λυκειο
 
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation Club
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation ClubTop 5 reasons to explore Saona Island with Lifestyle Holidays Vacation Club
Top 5 reasons to explore Saona Island with Lifestyle Holidays Vacation Club
 
More about health
More about healthMore about health
More about health
 
Matki
MatkiMatki
Matki
 
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...
Lifestyle Holidays Vacation Club Announces Whale Season In The Dominican Repu...
 
Software Testing
Software TestingSoftware Testing
Software Testing
 
Understanding patient privacy 1
Understanding patient privacy 1Understanding patient privacy 1
Understanding patient privacy 1
 
Brigade panorama
Brigade panoramaBrigade panorama
Brigade panorama
 
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...
The Best of Puerto Plata to See This Fall Revealed by Lifestyle Holidays Vac...
 
iPad Communication Apps
iPad Communication AppsiPad Communication Apps
iPad Communication Apps
 
Eyeonhome
EyeonhomeEyeonhome
Eyeonhome
 
Home smart home
Home smart homeHome smart home
Home smart home
 

Ähnlich wie Feed Normalization with Ember Data 1.0

"Writing Maintainable JavaScript". Jon Bretman, Badoo
"Writing Maintainable JavaScript". Jon Bretman, Badoo"Writing Maintainable JavaScript". Jon Bretman, Badoo
"Writing Maintainable JavaScript". Jon Bretman, BadooYandex
 
Plugin jQuery, Design Patterns
Plugin jQuery, Design PatternsPlugin jQuery, Design Patterns
Plugin jQuery, Design PatternsRobert Casanova
 
Replacing Oracle with MongoDB for a templating application at the Bavarian go...
Replacing Oracle with MongoDB for a templating application at the Bavarian go...Replacing Oracle with MongoDB for a templating application at the Bavarian go...
Replacing Oracle with MongoDB for a templating application at the Bavarian go...Comsysto Reply GmbH
 
MongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in BavariaMongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in BavariaMongoDB
 
NUS iOS Swift Talk
NUS iOS Swift TalkNUS iOS Swift Talk
NUS iOS Swift TalkGabriel Lim
 
Javascript patterns
Javascript patternsJavascript patterns
Javascript patternsChandan Jog
 
HadoopとMongoDBを活用したソーシャルアプリのログ解析
HadoopとMongoDBを活用したソーシャルアプリのログ解析HadoopとMongoDBを活用したソーシャルアプリのログ解析
HadoopとMongoDBを活用したソーシャルアプリのログ解析Takahiro Inoue
 
RubyConf Portugal 2014 - Why ruby must go!
RubyConf Portugal 2014 - Why ruby must go!RubyConf Portugal 2014 - Why ruby must go!
RubyConf Portugal 2014 - Why ruby must go!Gautam Rege
 
Finding Restfulness - Madrid.rb April 2014
Finding Restfulness - Madrid.rb April 2014Finding Restfulness - Madrid.rb April 2014
Finding Restfulness - Madrid.rb April 2014samlown
 
RESTful APIs: Promises & lies
RESTful APIs: Promises & liesRESTful APIs: Promises & lies
RESTful APIs: Promises & liesTareque Hossain
 
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasFrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasLoiane Groner
 
Beautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonBeautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonStormpath
 
ScotRuby - Dark side of ruby
ScotRuby - Dark side of rubyScotRuby - Dark side of ruby
ScotRuby - Dark side of rubyGautam Rege
 
RedDot Ruby Conf 2014 - Dark side of ruby
RedDot Ruby Conf 2014 - Dark side of ruby RedDot Ruby Conf 2014 - Dark side of ruby
RedDot Ruby Conf 2014 - Dark side of ruby Gautam Rege
 
The go-start webframework (GTUG Vienna 27.03.2012)
The go-start webframework (GTUG Vienna 27.03.2012)The go-start webframework (GTUG Vienna 27.03.2012)
The go-start webframework (GTUG Vienna 27.03.2012)ungerik
 
AMD - Why, What and How
AMD - Why, What and HowAMD - Why, What and How
AMD - Why, What and HowMike Wilcox
 
Ingo Muschenetz: Titanium Studio Deep Dive
Ingo Muschenetz: Titanium Studio Deep DiveIngo Muschenetz: Titanium Studio Deep Dive
Ingo Muschenetz: Titanium Studio Deep DiveAxway Appcelerator
 

Ähnlich wie Feed Normalization with Ember Data 1.0 (20)

"Writing Maintainable JavaScript". Jon Bretman, Badoo
"Writing Maintainable JavaScript". Jon Bretman, Badoo"Writing Maintainable JavaScript". Jon Bretman, Badoo
"Writing Maintainable JavaScript". Jon Bretman, Badoo
 
Plugin jQuery, Design Patterns
Plugin jQuery, Design PatternsPlugin jQuery, Design Patterns
Plugin jQuery, Design Patterns
 
Replacing Oracle with MongoDB for a templating application at the Bavarian go...
Replacing Oracle with MongoDB for a templating application at the Bavarian go...Replacing Oracle with MongoDB for a templating application at the Bavarian go...
Replacing Oracle with MongoDB for a templating application at the Bavarian go...
 
MongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in BavariaMongoDB Munich 2012: MongoDB for official documents in Bavaria
MongoDB Munich 2012: MongoDB for official documents in Bavaria
 
NUS iOS Swift Talk
NUS iOS Swift TalkNUS iOS Swift Talk
NUS iOS Swift Talk
 
Javascript patterns
Javascript patternsJavascript patterns
Javascript patterns
 
HadoopとMongoDBを活用したソーシャルアプリのログ解析
HadoopとMongoDBを活用したソーシャルアプリのログ解析HadoopとMongoDBを活用したソーシャルアプリのログ解析
HadoopとMongoDBを活用したソーシャルアプリのログ解析
 
RubyConf Portugal 2014 - Why ruby must go!
RubyConf Portugal 2014 - Why ruby must go!RubyConf Portugal 2014 - Why ruby must go!
RubyConf Portugal 2014 - Why ruby must go!
 
Finding Restfulness - Madrid.rb April 2014
Finding Restfulness - Madrid.rb April 2014Finding Restfulness - Madrid.rb April 2014
Finding Restfulness - Madrid.rb April 2014
 
RESTful APIs: Promises & lies
RESTful APIs: Promises & liesRESTful APIs: Promises & lies
RESTful APIs: Promises & lies
 
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasFrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
 
Ushahidi
UshahidiUshahidi
Ushahidi
 
Beautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with IonBeautiful REST+JSON APIs with Ion
Beautiful REST+JSON APIs with Ion
 
ScotRuby - Dark side of ruby
ScotRuby - Dark side of rubyScotRuby - Dark side of ruby
ScotRuby - Dark side of ruby
 
RedDot Ruby Conf 2014 - Dark side of ruby
RedDot Ruby Conf 2014 - Dark side of ruby RedDot Ruby Conf 2014 - Dark side of ruby
RedDot Ruby Conf 2014 - Dark side of ruby
 
Swift Basics
Swift BasicsSwift Basics
Swift Basics
 
"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues
 
The go-start webframework (GTUG Vienna 27.03.2012)
The go-start webframework (GTUG Vienna 27.03.2012)The go-start webframework (GTUG Vienna 27.03.2012)
The go-start webframework (GTUG Vienna 27.03.2012)
 
AMD - Why, What and How
AMD - Why, What and HowAMD - Why, What and How
AMD - Why, What and How
 
Ingo Muschenetz: Titanium Studio Deep Dive
Ingo Muschenetz: Titanium Studio Deep DiveIngo Muschenetz: Titanium Studio Deep Dive
Ingo Muschenetz: Titanium Studio Deep Dive
 

Kürzlich hochgeladen

20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdfMatthew Sinclair
 
Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.krishnachandrapal52
 
APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC
 
一比一原版田纳西大学毕业证如何办理
一比一原版田纳西大学毕业证如何办理一比一原版田纳西大学毕业证如何办理
一比一原版田纳西大学毕业证如何办理F
 
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...kajalverma014
 
Call girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsCall girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsMonica Sydney
 
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdfMatthew Sinclair
 
Trump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts SweatshirtTrump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts Sweatshirtrahman018755
 
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样ayvbos
 
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdf
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdfpdfcoffee.com_business-ethics-q3m7-pdf-free.pdf
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdfJOHNBEBONYAP1
 
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency""Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency"growthgrids
 
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查ydyuyu
 
20240507 QFM013 Machine Intelligence Reading List April 2024.pdf
20240507 QFM013 Machine Intelligence Reading List April 2024.pdf20240507 QFM013 Machine Intelligence Reading List April 2024.pdf
20240507 QFM013 Machine Intelligence Reading List April 2024.pdfMatthew Sinclair
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样ayvbos
 
一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理F
 
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime NagercoilNagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoilmeghakumariji156
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge GraphsEleniIlkou
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查ydyuyu
 
Mira Road Housewife Call Girls 07506202331, Nalasopara Call Girls
Mira Road Housewife Call Girls 07506202331, Nalasopara Call GirlsMira Road Housewife Call Girls 07506202331, Nalasopara Call Girls
Mira Road Housewife Call Girls 07506202331, Nalasopara Call GirlsPriya Reddy
 

Kürzlich hochgeladen (20)

20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
 
Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.Meaning of On page SEO & its process in detail.
Meaning of On page SEO & its process in detail.
 
APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53
 
一比一原版田纳西大学毕业证如何办理
一比一原版田纳西大学毕业证如何办理一比一原版田纳西大学毕业证如何办理
一比一原版田纳西大学毕业证如何办理
 
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
 
Call girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girlsCall girls Service in Ajman 0505086370 Ajman call girls
Call girls Service in Ajman 0505086370 Ajman call girls
 
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
 
Trump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts SweatshirtTrump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts Sweatshirt
 
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样
一比一原版(Flinders毕业证书)弗林德斯大学毕业证原件一模一样
 
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
call girls in Anand Vihar (delhi) call me [🔝9953056974🔝] escort service 24X7
 
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdf
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdfpdfcoffee.com_business-ethics-q3m7-pdf-free.pdf
pdfcoffee.com_business-ethics-q3m7-pdf-free.pdf
 
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency""Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
 
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
 
20240507 QFM013 Machine Intelligence Reading List April 2024.pdf
20240507 QFM013 Machine Intelligence Reading List April 2024.pdf20240507 QFM013 Machine Intelligence Reading List April 2024.pdf
20240507 QFM013 Machine Intelligence Reading List April 2024.pdf
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
 
一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理
 
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime NagercoilNagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
Nagercoil Escorts Service Girl ^ 9332606886, WhatsApp Anytime Nagercoil
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
 
Mira Road Housewife Call Girls 07506202331, Nalasopara Call Girls
Mira Road Housewife Call Girls 07506202331, Nalasopara Call GirlsMira Road Housewife Call Girls 07506202331, Nalasopara Call Girls
Mira Road Housewife Call Girls 07506202331, Nalasopara Call Girls
 

Feed Normalization with Ember Data 1.0

  • 1. Normalizing with Ember Data 1.0b Jeremy Gillick
  • 2. or
  • 3. True Facts of Using Data in Ember
  • 5. I work at Nest
  • 7. Ember Data is Great Except when data feeds don’t conform
  • 8. Serializers connect Raw Data to Ember Data { … } JSON Serializer Ember Data
  • 10. Ember prefers side loading to nested JSON But why?
  • 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. {! "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. 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. Ember Data Expects {! "modelOneRecord": {! ...! }! "modelTwoRecords": [! { ... },! { ... }! ],! "modelThreeRecords": [! { ... },! { ... }! ]! } No further nesting is allowed
  • 15. Ember Data Expects {! "posts": [! ...! ],! ! "users": [! …! ]! } App.Post records App.User records
  • 16. Not all JSON APIs will be flat
  • 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. Ember Data can’t process that
  • 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. How do we do this? With a custom Ember Data Serializer!
  • 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. 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. 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. {! "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. JS Methods extract: function(store, type, payload, id, requestType) { ... } processRelationships: function(store, type, payload, hash) { ... } sideloadRecord: function(store, type, payload, hash) { ... }
  • 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. {! "products": [! {! ...! }! ]! } extract: function(store, type, payload, id, requestType) {! return this._super(store, type, payload, id, requestType);! }
  • 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. 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": [! {! ...! }! ]! }
  • 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. {! "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. 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. /**! 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. {! "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. {! "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. {! "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. ! 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. {! "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. 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. 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. /**! 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. 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. 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. 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. 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. 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. 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. {! "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. Apply the Serializer App.ApplicationSerializer = App.NestedSerializer; App.ProductSerializer = App.NestedSerializer.extend({}); - OR -
  • 51. Now for a demo