SlideShare uma empresa Scribd logo
1 de 53
Baixar para ler offline
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

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Destaque

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
 

Destaque (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
 

Semelhante a 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
 

Semelhante a 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
 

Último

Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...
Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...
Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...gajnagarg
 
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
 
20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdfMatthew Sinclair
 
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrStory Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrHenryBriggs2
 
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
 
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
 
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
 
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
 
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac RoomVip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Roommeghakumariji156
 
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查ydyuyu
 
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
 
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
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查ydyuyu
 
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
 
Best SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasBest SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasDigicorns Technologies
 
一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理F
 
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsRussian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsMonica Sydney
 
Real Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirtReal Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirtrahman018755
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样ayvbos
 
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girls
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girlsRussian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girls
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girlsMonica Sydney
 

Último (20)

Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...
Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...
Top profile Call Girls In Dindigul [ 7014168258 ] Call Me For Genuine Models ...
 
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 ...
 
20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf20240508 QFM014 Elixir Reading List April 2024.pdf
20240508 QFM014 Elixir Reading List April 2024.pdf
 
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrStory Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
 
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
 
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
 
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.
 
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
 
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac RoomVip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
Vip Firozabad Phone 8250092165 Escorts Service At 6k To 30k Along With Ac Room
 
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查在线制作约克大学毕业证(yu毕业证)在读证明认证可查
在线制作约克大学毕业证(yu毕业证)在读证明认证可查
 
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
 
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
 
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
原版制作美国爱荷华大学毕业证(iowa毕业证书)学位证网上存档可查
 
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
 
Best SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency DallasBest SEO Services Company in Dallas | Best SEO Agency Dallas
Best SEO Services Company in Dallas | Best SEO Agency Dallas
 
一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理一比一原版奥兹学院毕业证如何办理
一比一原版奥兹学院毕业证如何办理
 
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi EscortsRussian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
Russian Escort Abu Dhabi 0503464457 Abu DHabi Escorts
 
Real Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirtReal Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirt
 
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
一比一原版(Curtin毕业证书)科廷大学毕业证原件一模一样
 
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girls
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girlsRussian Call girls in Abu Dhabi 0508644382 Abu Dhabi Call girls
Russian Call girls in Abu Dhabi 0508644382 Abu Dhabi 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