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

Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhimiss dipika
 
Magic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMagic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMartaLoveguard
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书rnrncn29
 
Blepharitis inflammation of eyelid symptoms cause everything included along w...
Blepharitis inflammation of eyelid symptoms cause everything included along w...Blepharitis inflammation of eyelid symptoms cause everything included along w...
Blepharitis inflammation of eyelid symptoms cause everything included along w...Excelmac1
 
PHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationPHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationLinaWolf1
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书rnrncn29
 
Intellectual property rightsand its types.pptx
Intellectual property rightsand its types.pptxIntellectual property rightsand its types.pptx
Intellectual property rightsand its types.pptxBipin Adhikari
 
Q4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxQ4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxeditsforyah
 
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一z xss
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxDyna Gilbert
 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)Christopher H Felton
 
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书zdzoqco
 
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作ys8omjxb
 
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一Fs
 
Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Sonam Pathan
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predieusebiomeyer
 
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一Fs
 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITMgdsc13
 

Kürzlich hochgeladen (20)

Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhi
 
Magic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMagic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptx
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
 
Blepharitis inflammation of eyelid symptoms cause everything included along w...
Blepharitis inflammation of eyelid symptoms cause everything included along w...Blepharitis inflammation of eyelid symptoms cause everything included along w...
Blepharitis inflammation of eyelid symptoms cause everything included along w...
 
PHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationPHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 Documentation
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
 
Intellectual property rightsand its types.pptx
Intellectual property rightsand its types.pptxIntellectual property rightsand its types.pptx
Intellectual property rightsand its types.pptx
 
Q4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxQ4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptx
 
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptx
 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
 
Hot Sexy call girls in Rk Puram 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in  Rk Puram 🔝 9953056974 🔝 Delhi escort ServiceHot Sexy call girls in  Rk Puram 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Rk Puram 🔝 9953056974 🔝 Delhi escort Service
 
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
 
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
 
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
 
Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170
 
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Serviceyoung call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predi
 
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一
定制(Management毕业证书)新加坡管理大学毕业证成绩单原版一比一
 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITM
 

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