pyro 1.0.0.rc2 → 1.0.0.rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,14 @@
1
- // Version: v1.0.0-beta.1
2
- // Last commit: cafab9d (2013-09-01 00:42:39 -0700)
1
+ // ==========================================================================
2
+ // Project: Ember Data
3
+ // Copyright: ©2011-2012 Tilde Inc. and contributors.
4
+ // Portions ©2011 Living Social Inc. and contributors.
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+
8
+
9
+
10
+ // Version: v1.0.0-beta.3
11
+ // Last commit: 2259c27 (2013-09-28 19:24:07 -0700)
3
12
 
4
13
 
5
14
  (function() {
@@ -42,547 +51,185 @@ var define, requireModule;
42
51
  };
43
52
  })();
44
53
  (function() {
45
- Ember.String.pluralize = function(word) {
46
- return Ember.Inflector.inflector.pluralize(word);
47
- };
48
-
49
- Ember.String.singularize = function(word) {
50
- return Ember.Inflector.inflector.singularize(word);
51
- };
54
+ /**
55
+ @module ember-data
56
+ */
52
57
 
53
- })();
58
+ /**
59
+ All Ember Data methods and functions are defined inside of this namespace.
54
60
 
61
+ @class DS
62
+ @static
63
+ */
55
64
 
65
+ if ('undefined' === typeof DS) {
66
+ DS = Ember.Namespace.create({
67
+ VERSION: '1.0.0-beta.2'
68
+ });
56
69
 
57
- (function() {
58
- var BLANK_REGEX = /^\s*$/;
70
+ if ('undefined' !== typeof window) {
71
+ window.DS = DS;
72
+ }
59
73
 
60
- function loadUncountable(rules, uncountable) {
61
- for (var i = 0, length = uncountable.length; i < length; i++) {
62
- rules.uncountable[uncountable[i]] = true;
74
+ if (Ember.libraries) {
75
+ Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION);
63
76
  }
64
77
  }
78
+ })();
65
79
 
66
- function loadIrregular(rules, irregularPairs) {
67
- var pair;
68
-
69
- for (var i = 0, length = irregularPairs.length; i < length; i++) {
70
- pair = irregularPairs[i];
71
80
 
72
- rules.irregular[pair[0]] = pair[1];
73
- rules.irregularInverse[pair[1]] = pair[0];
74
- }
75
- }
76
81
 
77
- function Inflector(ruleSet) {
78
- ruleSet = ruleSet || {};
79
- ruleSet.uncountable = ruleSet.uncountable || {};
80
- ruleSet.irregularPairs= ruleSet.irregularPairs|| {};
82
+ (function() {
83
+ var get = Ember.get, set = Ember.set, isNone = Ember.isNone;
81
84
 
82
- var rules = this.rules = {
83
- plurals: ruleSet.plurals || [],
84
- singular: ruleSet.singular || [],
85
- irregular: {},
86
- irregularInverse: {},
87
- uncountable: {}
85
+ // Simple dispatcher to support overriding the aliased
86
+ // method in subclasses.
87
+ function aliasMethod(methodName) {
88
+ return function() {
89
+ return this[methodName].apply(this, arguments);
88
90
  };
89
-
90
- loadUncountable(rules, ruleSet.uncountable);
91
- loadIrregular(rules, ruleSet.irregularPairs);
92
91
  }
93
92
 
94
- Inflector.prototype = {
95
- pluralize: function(word) {
96
- return this.inflect(word, this.rules.plurals);
93
+ DS.JSONSerializer = Ember.Object.extend({
94
+ primaryKey: 'id',
95
+
96
+ applyTransforms: function(type, data) {
97
+ type.eachTransformedAttribute(function(key, type) {
98
+ var transform = this.transformFor(type);
99
+ data[key] = transform.deserialize(data[key]);
100
+ }, this);
101
+
102
+ return data;
97
103
  },
98
104
 
99
- singularize: function(word) {
100
- return this.inflect(word, this.rules.singular);
105
+ normalize: function(type, hash) {
106
+ if (!hash) { return hash; }
107
+
108
+ this.applyTransforms(type, hash);
109
+ return hash;
101
110
  },
102
111
 
103
- inflect: function(word, typeRules) {
104
- var inflection, substitution, result, lowercase, isBlank,
105
- isUncountable, isIrregular, isIrregularInverse, rule;
112
+ // SERIALIZE
106
113
 
107
- isBlank = BLANK_REGEX.test(word);
114
+ serialize: function(record, options) {
115
+ var json = {};
108
116
 
109
- if (isBlank) {
110
- return word;
117
+ if (options && options.includeId) {
118
+ var id = get(record, 'id');
119
+
120
+ if (id) {
121
+ json[get(this, 'primaryKey')] = get(record, 'id');
122
+ }
111
123
  }
112
124
 
113
- lowercase = word.toLowerCase();
125
+ record.eachAttribute(function(key, attribute) {
126
+ this.serializeAttribute(record, json, key, attribute);
127
+ }, this);
114
128
 
115
- isUncountable = this.rules.uncountable[lowercase];
129
+ record.eachRelationship(function(key, relationship) {
130
+ if (relationship.kind === 'belongsTo') {
131
+ this.serializeBelongsTo(record, json, relationship);
132
+ } else if (relationship.kind === 'hasMany') {
133
+ this.serializeHasMany(record, json, relationship);
134
+ }
135
+ }, this);
116
136
 
117
- if (isUncountable) {
118
- return word;
119
- }
137
+ return json;
138
+ },
120
139
 
121
- isIrregular = this.rules.irregular[lowercase];
140
+ serializeAttribute: function(record, json, key, attribute) {
141
+ var attrs = get(this, 'attrs');
142
+ var value = get(record, key), type = attribute.type;
122
143
 
123
- if (isIrregular) {
124
- return isIrregular;
144
+ if (type) {
145
+ var transform = this.transformFor(type);
146
+ value = transform.serialize(value);
125
147
  }
126
148
 
127
- isIrregularInverse = this.rules.irregularInverse[lowercase];
149
+ // if provided, use the mapping provided by `attrs` in
150
+ // the serializer
151
+ key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key);
152
+
153
+ json[key] = value;
154
+ },
155
+
156
+ serializeBelongsTo: function(record, json, relationship) {
157
+ var key = relationship.key;
158
+
159
+ var belongsTo = get(record, key);
160
+
161
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
128
162
 
129
- if (isIrregularInverse) {
130
- return isIrregularInverse;
163
+ if (isNone(belongsTo)) {
164
+ json[key] = belongsTo;
165
+ } else {
166
+ json[key] = get(belongsTo, 'id');
131
167
  }
132
168
 
133
- for (var i = typeRules.length, min = 0; i > min; i--) {
134
- inflection = typeRules[i-1];
135
- rule = inflection[0];
169
+ if (relationship.options.polymorphic) {
170
+ this.serializePolymorphicType(record, json, relationship);
171
+ }
172
+ },
136
173
 
137
- if (rule.test(word)) {
138
- break;
139
- }
174
+ serializeHasMany: function(record, json, relationship) {
175
+ var key = relationship.key;
176
+
177
+ var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
178
+
179
+ if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') {
180
+ json[key] = get(record, key).mapBy('id');
181
+ // TODO support for polymorphic manyToNone and manyToMany relationships
140
182
  }
183
+ },
141
184
 
142
- inflection = inflection || [];
185
+ /**
186
+ You can use this method to customize how polymorphic objects are serialized.
187
+ */
188
+ serializePolymorphicType: Ember.K,
143
189
 
144
- rule = inflection[0];
145
- substitution = inflection[1];
190
+ // EXTRACT
146
191
 
147
- result = word.replace(rule, substitution);
192
+ extract: function(store, type, payload, id, requestType) {
193
+ this.extractMeta(store, type, payload);
148
194
 
149
- return result;
150
- }
151
- };
195
+ var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
196
+ return this[specificExtract](store, type, payload, id, requestType);
197
+ },
152
198
 
153
- Ember.Inflector = Inflector;
199
+ extractFindAll: aliasMethod('extractArray'),
200
+ extractFindQuery: aliasMethod('extractArray'),
201
+ extractFindMany: aliasMethod('extractArray'),
202
+ extractFindHasMany: aliasMethod('extractArray'),
154
203
 
155
- })();
204
+ extractCreateRecord: aliasMethod('extractSave'),
205
+ extractUpdateRecord: aliasMethod('extractSave'),
206
+ extractDeleteRecord: aliasMethod('extractSave'),
156
207
 
208
+ extractFind: aliasMethod('extractSingle'),
209
+ extractFindBelongsTo: aliasMethod('extractSingle'),
210
+ extractSave: aliasMethod('extractSingle'),
157
211
 
212
+ extractSingle: function(store, type, payload) {
213
+ return this.normalize(type, payload);
214
+ },
158
215
 
159
- (function() {
160
- Ember.Inflector.defaultRules = {
161
- plurals: [
162
- [/$/, 's'],
163
- [/s$/i, 's'],
164
- [/^(ax|test)is$/i, '$1es'],
165
- [/(octop|vir)us$/i, '$1i'],
166
- [/(octop|vir)i$/i, '$1i'],
167
- [/(alias|status)$/i, '$1es'],
168
- [/(bu)s$/i, '$1ses'],
169
- [/(buffal|tomat)o$/i, '$1oes'],
170
- [/([ti])um$/i, '$1a'],
171
- [/([ti])a$/i, '$1a'],
172
- [/sis$/i, 'ses'],
173
- [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'],
174
- [/(hive)$/i, '$1s'],
175
- [/([^aeiouy]|qu)y$/i, '$1ies'],
176
- [/(x|ch|ss|sh)$/i, '$1es'],
177
- [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'],
178
- [/^(m|l)ouse$/i, '$1ice'],
179
- [/^(m|l)ice$/i, '$1ice'],
180
- [/^(ox)$/i, '$1en'],
181
- [/^(oxen)$/i, '$1'],
182
- [/(quiz)$/i, '$1zes']
183
- ],
216
+ extractArray: function(store, type, payload) {
217
+ return this.normalize(type, payload);
218
+ },
184
219
 
185
- singular: [
186
- [/s$/i, ''],
187
- [/(ss)$/i, '$1'],
188
- [/(n)ews$/i, '$1ews'],
189
- [/([ti])a$/i, '$1um'],
190
- [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'],
191
- [/(^analy)(sis|ses)$/i, '$1sis'],
192
- [/([^f])ves$/i, '$1fe'],
193
- [/(hive)s$/i, '$1'],
194
- [/(tive)s$/i, '$1'],
195
- [/([lr])ves$/i, '$1f'],
196
- [/([^aeiouy]|qu)ies$/i, '$1y'],
197
- [/(s)eries$/i, '$1eries'],
198
- [/(m)ovies$/i, '$1ovie'],
199
- [/(x|ch|ss|sh)es$/i, '$1'],
200
- [/^(m|l)ice$/i, '$1ouse'],
201
- [/(bus)(es)?$/i, '$1'],
202
- [/(o)es$/i, '$1'],
203
- [/(shoe)s$/i, '$1'],
204
- [/(cris|test)(is|es)$/i, '$1is'],
205
- [/^(a)x[ie]s$/i, '$1xis'],
206
- [/(octop|vir)(us|i)$/i, '$1us'],
207
- [/(alias|status)(es)?$/i, '$1'],
208
- [/^(ox)en/i, '$1'],
209
- [/(vert|ind)ices$/i, '$1ex'],
210
- [/(matr)ices$/i, '$1ix'],
211
- [/(quiz)zes$/i, '$1'],
212
- [/(database)s$/i, '$1']
213
- ],
220
+ extractMeta: function(store, type, payload) {
221
+ if (payload && payload.meta) {
222
+ store.metaForType(type, payload.meta);
223
+ delete payload.meta;
224
+ }
225
+ },
214
226
 
215
- irregularPairs: [
216
- ['person', 'people'],
217
- ['man', 'men'],
218
- ['child', 'children'],
219
- ['sex', 'sexes'],
220
- ['move', 'moves'],
221
- ['cow', 'kine'],
222
- ['zombie', 'zombies']
223
- ],
227
+ // HELPERS
224
228
 
225
- uncountable: [
226
- 'equipment',
227
- 'information',
228
- 'rice',
229
- 'money',
230
- 'species',
231
- 'series',
232
- 'fish',
233
- 'sheep',
234
- 'jeans',
235
- 'police'
236
- ]
237
- };
238
-
239
- })();
240
-
241
-
242
-
243
- (function() {
244
- if (Ember.EXTEND_PROTOTYPES) {
245
- /**
246
- See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}}
247
-
248
- @method pluralize
249
- @for String
250
- */
251
- String.prototype.pluralize = function() {
252
- return Ember.String.pluralize(this);
253
- };
254
-
255
- /**
256
- See {{#crossLink "Ember.String/singularize"}}{{/crossLink}}
257
-
258
- @method singularize
259
- @for String
260
- */
261
- String.prototype.singularize = function() {
262
- return Ember.String.singularize(this);
263
- };
264
- }
265
-
266
- })();
267
-
268
-
269
-
270
- (function() {
271
- Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
272
-
273
- })();
274
-
275
-
276
-
277
- (function() {
278
-
279
- })();
280
-
281
-
282
- })();
283
- // Version: v1.0.0-beta.1
284
- // Last commit: cafab9d (2013-09-01 00:42:39 -0700)
285
-
286
-
287
- (function() {
288
- /**
289
- @module ember-data
290
- */
291
-
292
- /**
293
- All Ember Data methods and functions are defined inside of this namespace.
294
-
295
- @class DS
296
- @static
297
- */
298
-
299
- if ('undefined' === typeof DS) {
300
- DS = Ember.Namespace.create({
301
- VERSION: '1.0.0-beta.1'
302
- });
303
-
304
- if ('undefined' !== typeof window) {
305
- window.DS = DS;
306
- }
307
- }
308
- })();
309
-
310
-
311
-
312
- (function() {
313
- /**
314
- @module ember-data
315
- */
316
-
317
- var isNone = Ember.isNone, isEmpty = Ember.isEmpty;
318
-
319
- /**
320
- DS.JSONTransforms is a hash of transforms used by DS.Serializer.
321
-
322
- @class JSONTransforms
323
- @static
324
- @namespace DS
325
- */
326
- DS.JSONTransforms = {
327
- string: {
328
- deserialize: function(serialized) {
329
- return isNone(serialized) ? null : String(serialized);
330
- },
331
-
332
- serialize: function(deserialized) {
333
- return isNone(deserialized) ? null : String(deserialized);
334
- }
335
- },
336
-
337
- number: {
338
- deserialize: function(serialized) {
339
- return isEmpty(serialized) ? null : Number(serialized);
340
- },
341
-
342
- serialize: function(deserialized) {
343
- return isEmpty(deserialized) ? null : Number(deserialized);
344
- }
345
- },
346
-
347
- // Handles the following boolean inputs:
348
- // "TrUe", "t", "f", "FALSE", 0, (non-zero), or boolean true/false
349
- 'boolean': {
350
- deserialize: function(serialized) {
351
- var type = typeof serialized;
352
-
353
- if (type === "boolean") {
354
- return serialized;
355
- } else if (type === "string") {
356
- return serialized.match(/^true$|^t$|^1$/i) !== null;
357
- } else if (type === "number") {
358
- return serialized === 1;
359
- } else {
360
- return false;
361
- }
362
- },
363
-
364
- serialize: function(deserialized) {
365
- return Boolean(deserialized);
366
- }
367
- },
368
-
369
- date: {
370
- deserialize: function(serialized) {
371
- var type = typeof serialized;
372
-
373
- if (type === "string") {
374
- return new Date(Ember.Date.parse(serialized));
375
- } else if (type === "number") {
376
- return new Date(serialized);
377
- } else if (serialized === null || serialized === undefined) {
378
- // if the value is not present in the data,
379
- // return undefined, not null.
380
- return serialized;
381
- } else {
382
- return null;
383
- }
384
- },
385
-
386
- serialize: function(date) {
387
- if (date instanceof Date) {
388
- var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
389
- var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
390
-
391
- var pad = function(num) {
392
- return num < 10 ? "0"+num : ""+num;
393
- };
394
-
395
- var utcYear = date.getUTCFullYear(),
396
- utcMonth = date.getUTCMonth(),
397
- utcDayOfMonth = date.getUTCDate(),
398
- utcDay = date.getUTCDay(),
399
- utcHours = date.getUTCHours(),
400
- utcMinutes = date.getUTCMinutes(),
401
- utcSeconds = date.getUTCSeconds();
402
-
403
-
404
- var dayOfWeek = days[utcDay];
405
- var dayOfMonth = pad(utcDayOfMonth);
406
- var month = months[utcMonth];
407
-
408
- return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
409
- pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
410
- } else {
411
- return null;
412
- }
413
- }
414
- }
415
- };
416
-
417
- })();
418
-
419
-
420
-
421
- (function() {
422
- var get = Ember.get, set = Ember.set, isNone = Ember.isNone;
423
-
424
- var transforms = DS.JSONTransforms;
425
-
426
- // Simple dispatcher to support overriding the aliased
427
- // method in subclasses.
428
- function aliasMethod(methodName) {
429
- return function() {
430
- return this[methodName].apply(this, arguments);
431
- };
432
- }
433
-
434
- DS.JSONSerializer = Ember.Object.extend({
435
- primaryKey: 'id',
436
-
437
- deserialize: function(type, data) {
438
- var store = get(this, 'store');
439
-
440
- type.eachTransformedAttribute(function(key, type) {
441
- data[key] = transforms[type].deserialize(data[key]);
442
- });
443
-
444
- type.eachRelationship(function(key, relationship) {
445
- // A link (usually a URL) was already provided in
446
- // normalized form
447
- if (data.links && data.links[key]) {
448
- return;
449
- }
450
-
451
- var type = relationship.type,
452
- value = data[key];
453
-
454
- if (value == null) { return; }
455
-
456
- if (relationship.kind === 'belongsTo') {
457
- this.deserializeRecordId(data, key, relationship, value);
458
- } else if (relationship.kind === 'hasMany') {
459
- this.deserializeRecordIds(data, key, relationship, value);
460
- }
461
- }, this);
462
-
463
- return data;
464
- },
465
-
466
- deserializeRecordId: function(data, key, relationship, id) {
467
- if (isNone(id) || id instanceof DS.Model) {
468
- return;
469
- }
470
-
471
- var type;
472
-
473
- if (typeof id === 'number' || typeof id === 'string') {
474
- type = this.typeFor(relationship, key, data);
475
- data[key] = get(this, 'store').recordForId(type, id);
476
- } else if (typeof id === 'object') {
477
- // polymorphic
478
- data[key] = get(this, 'store').recordForId(id.type, id.id);
479
- }
480
- },
481
-
482
- deserializeRecordIds: function(data, key, relationship, ids) {
483
- for (var i=0, l=ids.length; i<l; i++) {
484
- this.deserializeRecordId(ids, i, relationship, ids[i]);
485
- }
486
- },
487
-
488
- // SERIALIZE
489
-
490
- serialize: function(record, options) {
491
- var store = get(this, 'store');
492
-
493
- var json = {};
494
-
495
- if (options && options.includeId) {
496
- var id = get(record, 'id');
497
-
498
- if (id) {
499
- json[get(this, 'primaryKey')] = get(record, 'id');
500
- }
501
- }
502
-
503
- var attrs = get(this, 'attrs');
504
-
505
- record.eachAttribute(function(key, attribute) {
506
- var value = get(record, key), type = attribute.type;
507
-
508
- if (type) {
509
- value = transforms[type].serialize(value);
510
- }
511
-
512
- // if provided, use the mapping provided by `attrs` in
513
- // the serializer
514
- key = attrs && attrs[key] || key;
515
-
516
- json[key] = value;
517
- }, this);
518
-
519
- record.eachRelationship(function(key, relationship) {
520
- if (relationship.kind === 'belongsTo') {
521
- this.serializeBelongsTo(record, json, relationship);
522
- } else if (relationship.kind === 'hasMany') {
523
- this.serializeHasMany(record, json, relationship);
524
- }
525
- }, this);
526
-
527
- return json;
528
- },
529
-
530
- serializeBelongsTo: function(record, json, relationship) {
531
- var key = relationship.key;
532
-
533
- var belongsTo = get(record, key);
534
-
535
- if (isNone(belongsTo)) { return; }
536
-
537
- json[key] = get(belongsTo, 'id');
538
-
539
- if (relationship.options.polymorphic) {
540
- json[key + "_type"] = belongsTo.constructor.typeKey;
541
- }
542
- },
543
-
544
- serializeHasMany: Ember.K,
545
-
546
- // EXTRACT
547
-
548
- extract: function(store, type, payload, id, requestType) {
549
- var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
550
- return this[specificExtract](store, type, payload, id, requestType);
551
- },
552
-
553
- extractFindAll: aliasMethod('extractArray'),
554
- extractFindQuery: aliasMethod('extractArray'),
555
- extractFindMany: aliasMethod('extractArray'),
556
- extractFindHasMany: aliasMethod('extractArray'),
557
-
558
- extractCreateRecord: aliasMethod('extractSave'),
559
- extractUpdateRecord: aliasMethod('extractSave'),
560
- extractDeleteRecord: aliasMethod('extractSave'),
561
-
562
- extractFind: aliasMethod('extractSingle'),
563
- extractSave: aliasMethod('extractSingle'),
564
-
565
- extractSingle: function(store, type, payload) {
566
- return payload;
567
- },
568
-
569
- extractArray: function(store, type, payload) {
570
- return payload;
571
- },
572
- // HELPERS
573
-
574
- typeFor: function(relationship, key, data) {
575
- if (relationship.options.polymorphic) {
576
- return data[key + "_type"];
577
- } else {
578
- return relationship.type;
579
- }
580
- },
581
-
582
- eachEmbeddedRecord: function() {
583
- // this is used by transaction.add
584
- }
585
- });
229
+ transformFor: function(attributeType) {
230
+ return this.container.lookup('transform:' + attributeType);
231
+ }
232
+ });
586
233
 
587
234
  })();
588
235
 
@@ -596,6 +243,11 @@ var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.St
596
243
 
597
244
  /**
598
245
  Extend `Ember.DataAdapter` with ED specific code.
246
+
247
+ @class DebugAdapter
248
+ @namespace DS
249
+ @extends Ember.DataAdapter
250
+ @private
599
251
  */
600
252
  DS.DebugAdapter = Ember.DataAdapter.extend({
601
253
  getFilters: function() {
@@ -612,7 +264,7 @@ DS.DebugAdapter = Ember.DataAdapter.extend({
612
264
 
613
265
  columnsForType: function(type) {
614
266
  var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this;
615
- Ember.A(get(type, 'attributes')).forEach(function(name, meta) {
267
+ get(type, 'attributes').forEach(function(name, meta) {
616
268
  if (count++ > self.attributeLimit) { return false; }
617
269
  var desc = capitalize(underscore(name).replace('_', ' '));
618
270
  columns.push({ name: name, desc: desc });
@@ -699,35 +351,169 @@ DS.DebugAdapter = Ember.DataAdapter.extend({
699
351
 
700
352
 
701
353
  (function() {
702
- /**
703
- @module ember-data
704
- */
354
+ DS.Transform = Ember.Object.extend({
705
355
 
706
- var set = Ember.set;
356
+ serialize: Ember.required(),
357
+
358
+ deserialize: Ember.required()
707
359
 
708
- /*
709
- This code registers an injection for Ember.Application.
360
+ });
361
+ })();
710
362
 
711
- If an Ember.js developer defines a subclass of DS.Store on their application,
712
- this code will automatically instantiate it and make it available on the
713
- router.
714
363
 
715
- Additionally, after an application's controllers have been injected, they will
716
- each have the store made available to them.
717
364
 
718
- For example, imagine an Ember.js application with the following classes:
365
+ (function() {
719
366
 
720
- App.Store = DS.Store.extend({
721
- adapter: 'App.MyCustomAdapter'
722
- });
367
+ DS.BooleanTransform = DS.Transform.extend({
368
+ deserialize: function(serialized) {
369
+ var type = typeof serialized;
723
370
 
724
- App.PostsController = Ember.ArrayController.extend({
725
- // ...
726
- });
371
+ if (type === "boolean") {
372
+ return serialized;
373
+ } else if (type === "string") {
374
+ return serialized.match(/^true$|^t$|^1$/i) !== null;
375
+ } else if (type === "number") {
376
+ return serialized === 1;
377
+ } else {
378
+ return false;
379
+ }
380
+ },
727
381
 
728
- When the application is initialized, `App.Store` will automatically be
729
- instantiated, and the instance of `App.PostsController` will have its `store`
730
- property set to that instance.
382
+ serialize: function(deserialized) {
383
+ return Boolean(deserialized);
384
+ }
385
+ });
386
+
387
+ })();
388
+
389
+
390
+
391
+ (function() {
392
+ DS.DateTransform = DS.Transform.extend({
393
+
394
+ deserialize: function(serialized) {
395
+ var type = typeof serialized;
396
+
397
+ if (type === "string") {
398
+ return new Date(Ember.Date.parse(serialized));
399
+ } else if (type === "number") {
400
+ return new Date(serialized);
401
+ } else if (serialized === null || serialized === undefined) {
402
+ // if the value is not present in the data,
403
+ // return undefined, not null.
404
+ return serialized;
405
+ } else {
406
+ return null;
407
+ }
408
+ },
409
+
410
+ serialize: function(date) {
411
+ if (date instanceof Date) {
412
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
413
+ var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
414
+
415
+ var pad = function(num) {
416
+ return num < 10 ? "0"+num : ""+num;
417
+ };
418
+
419
+ var utcYear = date.getUTCFullYear(),
420
+ utcMonth = date.getUTCMonth(),
421
+ utcDayOfMonth = date.getUTCDate(),
422
+ utcDay = date.getUTCDay(),
423
+ utcHours = date.getUTCHours(),
424
+ utcMinutes = date.getUTCMinutes(),
425
+ utcSeconds = date.getUTCSeconds();
426
+
427
+
428
+ var dayOfWeek = days[utcDay];
429
+ var dayOfMonth = pad(utcDayOfMonth);
430
+ var month = months[utcMonth];
431
+
432
+ return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
433
+ pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
434
+ } else {
435
+ return null;
436
+ }
437
+ }
438
+
439
+ });
440
+
441
+ })();
442
+
443
+
444
+
445
+ (function() {
446
+ var empty = Ember.isEmpty;
447
+
448
+ DS.NumberTransform = DS.Transform.extend({
449
+
450
+ deserialize: function(serialized) {
451
+ return empty(serialized) ? null : Number(serialized);
452
+ },
453
+
454
+ serialize: function(deserialized) {
455
+ return empty(deserialized) ? null : Number(deserialized);
456
+ }
457
+ });
458
+ })();
459
+
460
+
461
+
462
+ (function() {
463
+ var none = Ember.isNone;
464
+
465
+ DS.StringTransform = DS.Transform.extend({
466
+
467
+ deserialize: function(serialized) {
468
+ return none(serialized) ? null : String(serialized);
469
+ },
470
+
471
+ serialize: function(deserialized) {
472
+ return none(deserialized) ? null : String(deserialized);
473
+ }
474
+
475
+ });
476
+
477
+ })();
478
+
479
+
480
+
481
+ (function() {
482
+
483
+ })();
484
+
485
+
486
+
487
+ (function() {
488
+ /**
489
+ @module ember-data
490
+ */
491
+
492
+ var set = Ember.set;
493
+
494
+ /*
495
+ This code registers an injection for Ember.Application.
496
+
497
+ If an Ember.js developer defines a subclass of DS.Store on their application,
498
+ this code will automatically instantiate it and make it available on the
499
+ router.
500
+
501
+ Additionally, after an application's controllers have been injected, they will
502
+ each have the store made available to them.
503
+
504
+ For example, imagine an Ember.js application with the following classes:
505
+
506
+ App.Store = DS.Store.extend({
507
+ adapter: 'custom'
508
+ });
509
+
510
+ App.PostsController = Ember.ArrayController.extend({
511
+ // ...
512
+ });
513
+
514
+ When the application is initialized, `App.Store` will automatically be
515
+ instantiated, and the instance of `App.PostsController` will have its `store`
516
+ property set to that instance.
731
517
 
732
518
  Note that this code will only be run if the `ember-application` package is
733
519
  loaded. If Ember Data is being used in an environment other than a
@@ -751,17 +537,16 @@ Ember.onLoad('Ember.Application', function(Application) {
751
537
  }
752
538
  });
753
539
 
754
- // Keep ED compatible with previous versions of ember
755
- // TODO: Remove the if statement for Ember 1.0
756
- if (DS.DebugAdapter) {
757
- Application.initializer({
758
- name: "dataAdapter",
540
+ Application.initializer({
541
+ name: "transforms",
759
542
 
760
- initialize: function(container, application) {
761
- application.register('dataAdapter:main', DS.DebugAdapter);
762
- }
763
- });
764
- }
543
+ initialize: function(container, application) {
544
+ application.register('transform:boolean', DS.BooleanTransform);
545
+ application.register('transform:date', DS.DateTransform);
546
+ application.register('transform:number', DS.NumberTransform);
547
+ application.register('transform:string', DS.StringTransform);
548
+ }
549
+ });
765
550
 
766
551
  Application.initializer({
767
552
  name: "dataAdapter",
@@ -922,6 +707,14 @@ DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, {
922
707
 
923
708
  removeRecord: function(record) {
924
709
  get(this, 'content').removeObject(record);
710
+ },
711
+
712
+ save: function() {
713
+ var promise = Ember.RSVP.all(this.invoke("save")).then(function(array) {
714
+ return Ember.A(array);
715
+ });
716
+
717
+ return DS.PromiseArray.create({ promise: promise });
925
718
  }
926
719
  });
927
720
 
@@ -983,11 +776,13 @@ DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
983
776
  load: function(data) {
984
777
  var store = get(this, 'store'),
985
778
  type = get(this, 'type'),
986
- records = store.pushMany(type, data);
779
+ records = store.pushMany(type, data),
780
+ meta = store.metadataFor(type);
987
781
 
988
782
  this.setProperties({
989
783
  content: Ember.A(records),
990
- isLoaded: true
784
+ isLoaded: true,
785
+ meta: meta
991
786
  });
992
787
 
993
788
  // TODO: does triggering didLoad event should be the last action of the runLoop?
@@ -1021,11 +816,11 @@ var map = Ember.EnumerableUtils.map;
1021
816
  defined:
1022
817
 
1023
818
  App.Post = DS.Model.extend({
1024
- comments: DS.hasMany('App.Comment')
819
+ comments: DS.hasMany('comment')
1025
820
  });
1026
821
 
1027
822
  App.Comment = DS.Model.extend({
1028
- post: DS.belongsTo('App.Post')
823
+ post: DS.belongsTo('post')
1029
824
  });
1030
825
 
1031
826
  If you created a new instance of `App.Post` and added
@@ -1082,10 +877,11 @@ DS.ManyArray = DS.RecordArray.extend({
1082
877
  fetch: function() {
1083
878
  var records = get(this, 'content'),
1084
879
  store = get(this, 'store'),
1085
- owner = get(this, 'owner');
880
+ owner = get(this, 'owner'),
881
+ resolver = Ember.RSVP.defer();
1086
882
 
1087
883
  var unloadedRecords = records.filterProperty('isEmpty', true);
1088
- store.fetchMany(unloadedRecords, owner);
884
+ store.fetchMany(unloadedRecords, owner, resolver);
1089
885
  },
1090
886
 
1091
887
  // Overrides Ember.Array's replace method to implement
@@ -1100,7 +896,7 @@ DS.ManyArray = DS.RecordArray.extend({
1100
896
  },
1101
897
 
1102
898
  arrangedContentDidChange: function() {
1103
- this.fetch();
899
+ Ember.run.once(this, 'fetch');
1104
900
  },
1105
901
 
1106
902
  arrayContentWillChange: function(index, removed, added) {
@@ -1209,6 +1005,7 @@ DS.ManyArray = DS.RecordArray.extend({
1209
1005
  */
1210
1006
 
1211
1007
  var get = Ember.get;
1008
+ var forEach = Ember.ArrayPolyfills.forEach;
1212
1009
 
1213
1010
  var resolveMapConflict = function(oldValue, newValue) {
1214
1011
  return oldValue;
@@ -1304,7 +1101,7 @@ DS._Mappable = Ember.Mixin.create({
1304
1101
 
1305
1102
  var classMap = classMeta[mapName];
1306
1103
  if (classMap) {
1307
- classMap.forEach(eachMap, this);
1104
+ forEach.call(classMap, eachMap, this);
1308
1105
  }
1309
1106
 
1310
1107
  function eachMap(key, value) {
@@ -1322,7 +1119,6 @@ DS._Mappable = Ember.Mixin.create({
1322
1119
  }
1323
1120
  }
1324
1121
 
1325
-
1326
1122
  });
1327
1123
 
1328
1124
  DS._Mappable.generateMapFunctionFor = function(mapName, transform) {
@@ -1370,7 +1166,6 @@ var isNone = Ember.isNone;
1370
1166
  var forEach = Ember.EnumerableUtils.forEach;
1371
1167
  var indexOf = Ember.EnumerableUtils.indexOf;
1372
1168
  var map = Ember.EnumerableUtils.map;
1373
- var OrderedSet = Ember.OrderedSet;
1374
1169
  var resolve = Ember.RSVP.resolve;
1375
1170
 
1376
1171
  // Implementors Note:
@@ -1501,12 +1296,15 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1501
1296
  This property is cacheable, so the same instance of a specified
1502
1297
  adapter class should be used for the lifetime of the store.
1503
1298
 
1504
- @property _adapter
1299
+ @property defaultAdapter
1505
1300
  @private
1506
1301
  @returns DS.Adapter
1507
1302
  */
1508
- _adapter: Ember.computed(function() {
1303
+ defaultAdapter: Ember.computed(function() {
1509
1304
  var adapter = get(this, 'adapter');
1305
+
1306
+ Ember.assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name or a factory', !(adapter instanceof DS.Adapter));
1307
+
1510
1308
  if (typeof adapter === 'string') {
1511
1309
  adapter = this.container.lookup('adapter:' + adapter) || this.container.lookup('adapter:application') || this.container.lookup('adapter:_rest');
1512
1310
  }
@@ -1578,7 +1376,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1578
1376
  @returns String if the adapter can generate one, an ID
1579
1377
  */
1580
1378
  _generateId: function(type) {
1581
- var adapter = this.adapterForType(type);
1379
+ var adapter = this.adapterFor(type);
1582
1380
 
1583
1381
  if (adapter && adapter.generateIdForRecord) {
1584
1382
  return adapter.generateIdForRecord(this);
@@ -1682,12 +1480,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1682
1480
  findById: function(type, id) {
1683
1481
  type = this.modelFor(type);
1684
1482
 
1685
- var record = this.getById(type, id);
1686
- if (get(record, 'isEmpty')) {
1687
- return promiseObject(this.fetchRecord(record));
1688
- } else {
1689
- return promiseObject(resolve(record));
1690
- }
1483
+ var record = this.recordForId(type, id);
1484
+
1485
+ var promise = this.fetchRecord(record) || resolve(record);
1486
+ return promiseObject(promise);
1691
1487
  },
1692
1488
 
1693
1489
  /**
@@ -1720,13 +1516,17 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1720
1516
  @returns Promise
1721
1517
  */
1722
1518
  fetchRecord: function(record) {
1519
+ if (isNone(record)) { return null; }
1520
+ if (record._loadingPromise) { return record._loadingPromise; }
1521
+ if (!get(record, 'isEmpty')) { return null; }
1522
+
1723
1523
  var type = record.constructor,
1724
1524
  id = get(record, 'id'),
1725
1525
  resolver = Ember.RSVP.defer();
1726
1526
 
1727
- record.loadingData();
1527
+ record.loadingData(resolver.promise);
1728
1528
 
1729
- var adapter = this.adapterForType(type);
1529
+ var adapter = this.adapterFor(type);
1730
1530
 
1731
1531
  Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter);
1732
1532
  Ember.assert("You tried to find a record but your adapter (for " + type + ") does not implement 'find'", adapter.find);
@@ -1740,7 +1540,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1740
1540
  Get a record by a given type and ID without triggering a fetch.
1741
1541
 
1742
1542
  This method will synchronously return the record if it's available.
1743
- Otherwise, it will return undefined.
1543
+ Otherwise, it will return null.
1744
1544
 
1745
1545
  ```js
1746
1546
  var post = store.getById('post', 1);
@@ -1756,7 +1556,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1756
1556
  if (this.hasRecordForId(type, id)) {
1757
1557
  return this.recordForId(type, id);
1758
1558
  } else {
1759
- return this.buildRecord(type, id);
1559
+ return null;
1760
1560
  }
1761
1561
  },
1762
1562
 
@@ -1775,8 +1575,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1775
1575
  */
1776
1576
  reloadRecord: function(record, resolver) {
1777
1577
  var type = record.constructor,
1778
- adapter = this.adapterForType(type),
1779
- store = this,
1578
+ adapter = this.adapterFor(type),
1780
1579
  id = get(record, 'id');
1781
1580
 
1782
1581
  Ember.assert("You cannot reload a record without an ID", id);
@@ -1817,7 +1616,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1817
1616
 
1818
1617
  forEach(recordsByTypeMap, function(type, records) {
1819
1618
  var ids = records.mapProperty('id'),
1820
- adapter = this.adapterForType(type);
1619
+ adapter = this.adapterFor(type);
1821
1620
 
1822
1621
  Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
1823
1622
  Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany);
@@ -1829,6 +1628,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1829
1628
  /**
1830
1629
  Returns true if a record for a given type and ID is already loaded.
1831
1630
 
1631
+ @method hasRecordForId
1832
1632
  @param {String} type
1833
1633
  @param {String|Integer} id
1834
1634
  @returns Boolean
@@ -1843,6 +1643,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1843
1643
  Returns id record for a given type and ID. If one isn't already loaded,
1844
1644
  it builds a new record and leaves it in the `empty` state.
1845
1645
 
1646
+ @method recordForId
1846
1647
  @param {String} type
1847
1648
  @param {String|Integer} id
1848
1649
  @returns DS.Model
@@ -1878,19 +1679,20 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1878
1679
  var unloadedRecords = records.filterProperty('isEmpty', true),
1879
1680
  manyArray = this.recordArrayManager.createManyArray(type, records);
1880
1681
 
1881
- unloadedRecords.forEach(function(record) {
1682
+ forEach(unloadedRecords, function(record) {
1882
1683
  record.loadingData();
1883
1684
  });
1884
1685
 
1885
1686
  manyArray.loadingRecordsCount = unloadedRecords.length;
1886
1687
 
1887
1688
  if (unloadedRecords.length) {
1888
- unloadedRecords.forEach(function(record) {
1689
+ forEach(unloadedRecords, function(record) {
1889
1690
  this.recordArrayManager.registerWaitingRecordArray(record, manyArray);
1890
1691
  }, this);
1891
1692
 
1892
1693
  this.fetchMany(unloadedRecords, owner, resolver);
1893
1694
  } else {
1695
+ if (resolver) { resolver.resolve(); }
1894
1696
  manyArray.set('isLoaded', true);
1895
1697
  Ember.run.once(manyArray, 'trigger', 'didLoad');
1896
1698
  }
@@ -1909,6 +1711,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1909
1711
  The usual use-case is for the server to register a URL as a link, and
1910
1712
  then use that URL in the future to make a request for the relationship.
1911
1713
 
1714
+ @method findHasMany
1912
1715
  @private
1913
1716
  @param {DS.Model} owner
1914
1717
  @param {any} link
@@ -1917,7 +1720,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1917
1720
  @return DS.ManyArray
1918
1721
  */
1919
1722
  findHasMany: function(owner, link, relationship, resolver) {
1920
- var adapter = this.adapterForType(owner.constructor);
1723
+ var adapter = this.adapterFor(owner.constructor);
1921
1724
 
1922
1725
  Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter);
1923
1726
  Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`", adapter.findHasMany);
@@ -1927,6 +1730,15 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1927
1730
  return records;
1928
1731
  },
1929
1732
 
1733
+ findBelongsTo: function(owner, link, relationship, resolver) {
1734
+ var adapter = this.adapterFor(owner.constructor);
1735
+
1736
+ Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter);
1737
+ Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`", adapter.findBelongsTo);
1738
+
1739
+ _findBelongsTo(adapter, this, owner, link, relationship, resolver);
1740
+ },
1741
+
1930
1742
  /**
1931
1743
  This method delegates a query to the adapter. This is the one place where
1932
1744
  adapter-level semantics are exposed to the application.
@@ -1954,7 +1766,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1954
1766
  store: this
1955
1767
  });
1956
1768
 
1957
- var adapter = this.adapterForType(type),
1769
+ var adapter = this.adapterFor(type),
1958
1770
  resolver = Ember.RSVP.defer();
1959
1771
 
1960
1772
  Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter);
@@ -1989,7 +1801,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1989
1801
  @returns Promise
1990
1802
  */
1991
1803
  fetchAll: function(type, array) {
1992
- var adapter = this.adapterForType(type),
1804
+ var adapter = this.adapterFor(type),
1993
1805
  sinceToken = this.typeMapFor(type).metadata.since,
1994
1806
  resolver = Ember.RSVP.defer();
1995
1807
 
@@ -2027,6 +1839,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2027
1839
  @return {DS.RecordArray}
2028
1840
  */
2029
1841
  all: function(type) {
1842
+ type = this.modelFor(type);
1843
+
2030
1844
  var typeMap = this.typeMapFor(type),
2031
1845
  findAllCache = typeMap.findAllCache;
2032
1846
 
@@ -2045,6 +1859,24 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2045
1859
  return array;
2046
1860
  },
2047
1861
 
1862
+
1863
+ /**
1864
+ This method unloads all of the known records for a given type.
1865
+
1866
+ @method unloadAll
1867
+ @param {Class} type
1868
+ */
1869
+ unloadAll: function(type) {
1870
+ type = this.modelFor(type);
1871
+
1872
+ var typeMap = this.typeMapFor(type),
1873
+ records = typeMap.records, record;
1874
+
1875
+ while(record = records.pop()) {
1876
+ record.unloadRecord();
1877
+ }
1878
+ },
1879
+
2048
1880
  /**
2049
1881
  Takes a type and filter function, and returns a live RecordArray that
2050
1882
  remains up to date as new records are loaded into the store or created
@@ -2061,14 +1893,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2061
1893
  filter function will be invoked again to determine whether it should
2062
1894
  still be in the array.
2063
1895
 
2064
- Note that the existence of a filter on a type will trigger immediate
2065
- materialization of all loaded data for a given type, so you might
2066
- not want to use filters for a type if you are loading many records
2067
- into the store, many of which are not active at any given time.
2068
-
2069
- In this scenario, you might want to consider filtering the raw
2070
- data before loading it into the store.
2071
-
2072
1896
  @method filter
2073
1897
  @param {Class} type
2074
1898
  @param {Function} filter
@@ -2118,6 +1942,18 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2118
1942
  return !get(this.recordForId(type, id), 'isEmpty');
2119
1943
  },
2120
1944
 
1945
+ /**
1946
+ This method returns the metadata for a specific type.
1947
+
1948
+ @method metadataFor
1949
+ @param {string} type
1950
+ @return {object}
1951
+ */
1952
+ metadataFor: function(type) {
1953
+ type = this.modelFor(type);
1954
+ return this.typeMapFor(type).metadata;
1955
+ },
1956
+
2121
1957
  // ............
2122
1958
  // . UPDATING .
2123
1959
  // ............
@@ -2187,7 +2023,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2187
2023
 
2188
2024
  forEach(pending, function(tuple) {
2189
2025
  var record = tuple[0], resolver = tuple[1],
2190
- adapter = this.adapterForType(record.constructor),
2026
+ adapter = this.adapterFor(record.constructor),
2191
2027
  operation;
2192
2028
 
2193
2029
  if (get(record, 'isNew')) {
@@ -2217,6 +2053,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2217
2053
  */
2218
2054
  didSaveRecord: function(record, data) {
2219
2055
  if (data) {
2056
+ // normalize relationship IDs into records
2057
+ data = normalizeRelationships(this, record.constructor, data, record);
2058
+
2220
2059
  this.updateId(record, data);
2221
2060
  }
2222
2061
 
@@ -2308,14 +2147,15 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2308
2147
  @method _load
2309
2148
  @private
2310
2149
  @param {DS.Model} type
2311
- @param data
2312
- @param prematerialized
2150
+ @param {Object} data
2151
+ @param {Boolean} partial the data should be merged into
2152
+ the existing data, not replace it.
2313
2153
  */
2314
- _load: function(type, data) {
2154
+ _load: function(type, data, partial) {
2315
2155
  var id = coerceId(data.id),
2316
2156
  record = this.recordForId(type, id);
2317
2157
 
2318
- record.setupData(data);
2158
+ record.setupData(data, partial);
2319
2159
  this.recordArrayManager.recordDidChange(record);
2320
2160
 
2321
2161
  return record;
@@ -2326,6 +2166,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2326
2166
  methods that take a type key (like `find`, `createRecord`,
2327
2167
  etc.)
2328
2168
 
2169
+ @method modelFor
2329
2170
  @param {String} key
2330
2171
  @returns {subclass of DS.Model}
2331
2172
  */
@@ -2405,17 +2246,67 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2405
2246
  @returns DS.Model the record that was created or
2406
2247
  updated.
2407
2248
  */
2408
- push: function(type, data) {
2409
- var serializer = this.serializerFor(type);
2249
+ push: function(type, data, _partial) {
2250
+ // _partial is an internal param used by `update`.
2251
+ // If passed, it means that the data should be
2252
+ // merged into the existing data, not replace it.
2253
+
2254
+ Ember.assert("You must include an `id` in a hash passed to `push`", data.id != null);
2255
+
2410
2256
  type = this.modelFor(type);
2411
2257
 
2412
- data = serializer.deserialize(type, data);
2258
+ // normalize relationship IDs into records
2259
+ data = normalizeRelationships(this, type, data);
2413
2260
 
2414
- this._load(type, data);
2261
+ this._load(type, data, _partial);
2415
2262
 
2416
2263
  return this.recordForId(type, data.id);
2417
2264
  },
2418
2265
 
2266
+ /**
2267
+ Push some raw data into the store.
2268
+
2269
+ The data will be automatically deserialized using the
2270
+ serializer for the `type` param.
2271
+
2272
+ This method can be used both to push in brand new
2273
+ records, as well as to update existing records.
2274
+
2275
+ You can push in more than one type of object at once.
2276
+ All objects should be in the format expected by the
2277
+ serializer.
2278
+
2279
+ ```js
2280
+ App.ApplicationSerializer = DS.ActiveModelSerializer;
2281
+
2282
+ var pushData = {
2283
+ posts: [
2284
+ {id: 1, post_title: "Great post", comment_ids: [2]}
2285
+ ],
2286
+ comments: [
2287
+ {id: 2, comment_body: "Insightful comment"}
2288
+ ]
2289
+ }
2290
+
2291
+ store.pushPayload('post', pushData);
2292
+ ```
2293
+
2294
+ @method push
2295
+ @param {String} type
2296
+ @param {Object} payload
2297
+ */
2298
+
2299
+ pushPayload: function (type, payload) {
2300
+ var serializer = this.serializerFor(type);
2301
+ serializer.pushPayload(this, payload);
2302
+ },
2303
+
2304
+ update: function(type, data) {
2305
+ Ember.assert("You must include an `id` in a hash passed to `update`", data.id != null);
2306
+
2307
+ return this.push(type, data, true);
2308
+ },
2309
+
2419
2310
  /**
2420
2311
  If you have an Array of normalized data to push,
2421
2312
  you can call `pushMany` with the Array, and it will
@@ -2432,6 +2323,20 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2432
2323
  }, this);
2433
2324
  },
2434
2325
 
2326
+ /**
2327
+ If you have some metadata to set for a type
2328
+ you can call `metaForType`.
2329
+
2330
+ @method metaForType
2331
+ @param {String} type
2332
+ @param {Object} metadata
2333
+ */
2334
+ metaForType: function(type, metadata) {
2335
+ type = this.modelFor(type);
2336
+
2337
+ Ember.merge(this.typeMapFor(type).metadata, metadata);
2338
+ },
2339
+
2435
2340
  /**
2436
2341
  Build a brand new record for a given type, ID, and
2437
2342
  initial data.
@@ -2449,9 +2354,12 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2449
2354
 
2450
2355
  Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]);
2451
2356
 
2357
+ // lookupFactory should really return an object that creates
2358
+ // instances with the injections applied
2452
2359
  var record = type._create({
2453
2360
  id: id,
2454
2361
  store: this,
2362
+ container: this.container
2455
2363
  });
2456
2364
 
2457
2365
  if (data) {
@@ -2554,19 +2462,19 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2554
2462
  /**
2555
2463
  Returns the adapter for a given type.
2556
2464
 
2557
- @method adapterForType
2465
+ @method adapterFor
2558
2466
  @private
2559
2467
  @param {subclass of DS.Model} type
2560
2468
  @returns DS.Adapter
2561
2469
  */
2562
- adapterForType: function(type) {
2470
+ adapterFor: function(type) {
2563
2471
  var container = this.container, adapter;
2564
2472
 
2565
2473
  if (container) {
2566
2474
  adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application');
2567
2475
  }
2568
2476
 
2569
- return adapter || get(this, '_adapter');
2477
+ return adapter || get(this, 'defaultAdapter');
2570
2478
  },
2571
2479
 
2572
2480
  // ..............................
@@ -2590,19 +2498,66 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2590
2498
  @param {String} type the record to serialize
2591
2499
  */
2592
2500
  serializerFor: function(type) {
2593
- var container = this.container;
2501
+ type = this.modelFor(type);
2502
+ var adapter = this.adapterFor(type);
2503
+
2504
+ return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer);
2505
+ }
2506
+ });
2507
+
2508
+ function normalizeRelationships(store, type, data, record) {
2509
+ type.eachRelationship(function(key, relationship) {
2510
+ // A link (usually a URL) was already provided in
2511
+ // normalized form
2512
+ if (data.links && data.links[key]) {
2513
+ if (record && relationship.options.async) { record._relationships[key] = null; }
2514
+ return;
2515
+ }
2594
2516
 
2595
- // TODO: Make tests pass without this
2517
+ var kind = relationship.kind,
2518
+ value = data[key];
2596
2519
 
2597
- if (!container) {
2598
- return DS.JSONSerializer.create({ store: this });
2520
+ if (value == null) { return; }
2521
+
2522
+ if (kind === 'belongsTo') {
2523
+ deserializeRecordId(store, data, key, relationship, value);
2524
+ } else if (kind === 'hasMany') {
2525
+ deserializeRecordIds(store, data, key, relationship, value);
2599
2526
  }
2527
+ });
2528
+
2529
+ return data;
2530
+ }
2600
2531
 
2601
- return container.lookup('serializer:'+type) ||
2602
- container.lookup('serializer:application') ||
2603
- container.lookup('serializer:_default');
2532
+ function deserializeRecordId(store, data, key, relationship, id) {
2533
+ if (isNone(id) || id instanceof DS.Model) {
2534
+ return;
2604
2535
  }
2605
- });
2536
+
2537
+ var type;
2538
+
2539
+ if (typeof id === 'number' || typeof id === 'string') {
2540
+ type = typeFor(relationship, key, data);
2541
+ data[key] = store.recordForId(type, id);
2542
+ } else if (typeof id === 'object') {
2543
+ // polymorphic
2544
+ data[key] = store.recordForId(id.type, id.id);
2545
+ }
2546
+ }
2547
+
2548
+ function typeFor(relationship, key, data) {
2549
+ if (relationship.options.polymorphic) {
2550
+ return data[key + "Type"];
2551
+ } else {
2552
+ return relationship.type;
2553
+ }
2554
+ }
2555
+
2556
+ function deserializeRecordIds(store, data, key, relationship, ids) {
2557
+ for (var i=0, l=ids.length; i<l; i++) {
2558
+ deserializeRecordId(store, ids, i, relationship, ids[i]);
2559
+ }
2560
+ }
2606
2561
 
2607
2562
  // Delegation to the adapter and promise management
2608
2563
 
@@ -2621,15 +2576,20 @@ function isThenable(object) {
2621
2576
  return object && typeof object.then === 'function';
2622
2577
  }
2623
2578
 
2624
- function serializerFor(adapter, type) {
2579
+ function serializerFor(container, type, defaultSerializer) {
2580
+ return container.lookup('serializer:'+type) ||
2581
+ container.lookup('serializer:application') ||
2582
+ container.lookup('serializer:' + defaultSerializer) ||
2583
+ container.lookup('serializer:_default');
2584
+ }
2585
+
2586
+ function serializerForAdapter(adapter, type) {
2625
2587
  var serializer = adapter.serializer,
2626
2588
  defaultSerializer = adapter.defaultSerializer,
2627
2589
  container = adapter.container;
2628
2590
 
2629
2591
  if (container && serializer === undefined) {
2630
- serializer = container.lookup('serializer:'+type.typeKey) ||
2631
- container.lookup('serializer:application') ||
2632
- container.lookup('serializer:' + defaultSerializer || 'serializer:_default');
2592
+ serializer = serializerFor(container, type.typeKey, defaultSerializer);
2633
2593
  }
2634
2594
 
2635
2595
  if (serializer === null || serializer === undefined) {
@@ -2643,46 +2603,68 @@ function serializerFor(adapter, type) {
2643
2603
 
2644
2604
  function _find(adapter, store, type, id, resolver) {
2645
2605
  var promise = adapter.find(store, type, id),
2646
- serializer = serializerFor(adapter, type);
2606
+ serializer = serializerForAdapter(adapter, type);
2647
2607
 
2648
2608
  return resolve(promise).then(function(payload) {
2649
2609
  Ember.assert("You made a request for a " + type.typeKey + " with id " + id + ", but the adapter's response did not have any data", payload);
2650
2610
  payload = serializer.extract(store, type, payload, id, 'find');
2651
2611
 
2652
2612
  return store.push(type, payload);
2613
+ }, function(error) {
2614
+ var record = store.getById(type, id);
2615
+ record.notFound();
2616
+ throw error;
2653
2617
  }).then(resolver.resolve, resolver.reject);
2654
2618
  }
2655
2619
 
2656
2620
  function _findMany(adapter, store, type, ids, owner, resolver) {
2657
2621
  var promise = adapter.findMany(store, type, ids, owner),
2658
- serializer = serializerFor(adapter, type);
2622
+ serializer = serializerForAdapter(adapter, type);
2659
2623
 
2660
2624
  return resolve(promise).then(function(payload) {
2661
2625
  payload = serializer.extract(store, type, payload, null, 'findMany');
2662
2626
 
2627
+ Ember.assert("The response from a findMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2628
+
2663
2629
  store.pushMany(type, payload);
2664
2630
  }).then(resolver.resolve, resolver.reject);
2665
2631
  }
2666
2632
 
2667
2633
  function _findHasMany(adapter, store, record, link, relationship, resolver) {
2668
2634
  var promise = adapter.findHasMany(store, record, link, relationship),
2669
- serializer = serializerFor(adapter, relationship.type);
2635
+ serializer = serializerForAdapter(adapter, relationship.type);
2670
2636
 
2671
2637
  return resolve(promise).then(function(payload) {
2672
2638
  payload = serializer.extract(store, relationship.type, payload, null, 'findHasMany');
2673
2639
 
2640
+ Ember.assert("The response from a findHasMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2641
+
2674
2642
  var records = store.pushMany(relationship.type, payload);
2675
2643
  record.updateHasMany(relationship.key, records);
2676
2644
  }).then(resolver.resolve, resolver.reject);
2677
2645
  }
2678
2646
 
2647
+ function _findBelongsTo(adapter, store, record, link, relationship, resolver) {
2648
+ var promise = adapter.findBelongsTo(store, record, link, relationship),
2649
+ serializer = serializerForAdapter(adapter, relationship.type);
2650
+
2651
+ return resolve(promise).then(function(payload) {
2652
+ payload = serializer.extract(store, relationship.type, payload, null, 'findBelongsTo');
2653
+
2654
+ var record = store.push(relationship.type, payload);
2655
+ record.updateBelongsTo(relationship.key, record);
2656
+ }).then(resolver.resolve, resolver.reject);
2657
+ }
2658
+
2679
2659
  function _findAll(adapter, store, type, sinceToken, resolver) {
2680
2660
  var promise = adapter.findAll(store, type, sinceToken),
2681
- serializer = serializerFor(adapter, type);
2661
+ serializer = serializerForAdapter(adapter, type);
2682
2662
 
2683
2663
  return resolve(promise).then(function(payload) {
2684
2664
  payload = serializer.extract(store, type, payload, null, 'findAll');
2685
2665
 
2666
+ Ember.assert("The response from a findAll must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2667
+
2686
2668
  store.pushMany(type, payload);
2687
2669
  store.didUpdateAll(type);
2688
2670
  return store.all(type);
@@ -2691,11 +2673,13 @@ function _findAll(adapter, store, type, sinceToken, resolver) {
2691
2673
 
2692
2674
  function _findQuery(adapter, store, type, query, recordArray, resolver) {
2693
2675
  var promise = adapter.findQuery(store, type, query, recordArray),
2694
- serializer = serializerFor(adapter, type);
2676
+ serializer = serializerForAdapter(adapter, type);
2695
2677
 
2696
2678
  return resolve(promise).then(function(payload) {
2697
2679
  payload = serializer.extract(store, type, payload, null, 'findAll');
2698
2680
 
2681
+ Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2682
+
2699
2683
  recordArray.load(payload);
2700
2684
  return recordArray;
2701
2685
  }).then(resolver.resolve, resolver.reject);
@@ -2704,12 +2688,12 @@ function _findQuery(adapter, store, type, query, recordArray, resolver) {
2704
2688
  function _commit(adapter, store, operation, record, resolver) {
2705
2689
  var type = record.constructor,
2706
2690
  promise = adapter[operation](store, type, record),
2707
- serializer = serializerFor(adapter, type);
2691
+ serializer = serializerForAdapter(adapter, type);
2708
2692
 
2709
2693
  Ember.assert("Your adapter's '" + operation + "' method must return a promise, but it returned " + promise, isThenable(promise));
2710
2694
 
2711
2695
  return promise.then(function(payload) {
2712
- payload = serializer.extract(store, type, payload, get(record, 'id'), operation);
2696
+ if (payload) { payload = serializer.extract(store, type, payload, get(record, 'id'), operation); }
2713
2697
  store.didSaveRecord(record, payload);
2714
2698
  return record;
2715
2699
  }, function(reason) {
@@ -2732,8 +2716,7 @@ function _commit(adapter, store, operation, record, resolver) {
2732
2716
  @module ember-data
2733
2717
  */
2734
2718
 
2735
- var get = Ember.get, set = Ember.set,
2736
- once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
2719
+ var get = Ember.get, set = Ember.set;
2737
2720
 
2738
2721
  /*
2739
2722
  WARNING: Much of these docs are inaccurate as of bf8497.
@@ -2901,10 +2884,14 @@ var hasDefinedProperties = function(object) {
2901
2884
  };
2902
2885
 
2903
2886
  var didSetProperty = function(record, context) {
2904
- if (context.value !== context.oldValue) {
2887
+ if (context.value === context.originalValue) {
2888
+ delete record._attributes[context.name];
2889
+ record.send('propertyWasReset', context.name);
2890
+ } else if (context.value !== context.oldValue) {
2905
2891
  record.send('becomeDirty');
2906
- record.updateRecordArraysLater();
2907
2892
  }
2893
+
2894
+ record.updateRecordArraysLater();
2908
2895
  };
2909
2896
 
2910
2897
  // Implementation notes:
@@ -2962,10 +2949,20 @@ var DirtyState = {
2962
2949
  // This means that there are local pending changes, but they
2963
2950
  // have not yet begun to be saved, and are not invalid.
2964
2951
  uncommitted: {
2965
-
2966
2952
  // EVENTS
2967
2953
  didSetProperty: didSetProperty,
2968
2954
 
2955
+ propertyWasReset: function(record, name) {
2956
+ var stillDirty = false;
2957
+
2958
+ for (var prop in record._attributes) {
2959
+ stillDirty = true;
2960
+ break;
2961
+ }
2962
+
2963
+ if (!stillDirty) { record.send('rolledBack'); }
2964
+ },
2965
+
2969
2966
  pushedData: Ember.K,
2970
2967
 
2971
2968
  becomeDirty: Ember.K,
@@ -2978,7 +2975,7 @@ var DirtyState = {
2978
2975
  get(record, 'store').reloadRecord(record, resolver);
2979
2976
  },
2980
2977
 
2981
- becameClean: function(record) {
2978
+ rolledBack: function(record) {
2982
2979
  record.transitionTo('loaded.saved');
2983
2980
  },
2984
2981
 
@@ -3108,6 +3105,10 @@ var createdState = dirtyState({
3108
3105
  isNew: true
3109
3106
  });
3110
3107
 
3108
+ createdState.uncommitted.rolledBack = function(record) {
3109
+ record.transitionTo('deleted.saved');
3110
+ };
3111
+
3111
3112
  var updatedState = dirtyState({
3112
3113
  dirtyType: 'updated'
3113
3114
  });
@@ -3138,6 +3139,16 @@ var RootState = {
3138
3139
  isNew: false,
3139
3140
  isValid: true,
3140
3141
 
3142
+ // DEFAULT EVENTS
3143
+
3144
+ // Trying to roll back if you're not in the dirty state
3145
+ // doesn't change your state. For example, if you're in the
3146
+ // in-flight state, rolling back the record doesn't move
3147
+ // you out of the in-flight state.
3148
+ rolledBack: Ember.K,
3149
+
3150
+ propertyWasReset: Ember.K,
3151
+
3141
3152
  // SUBSTATES
3142
3153
 
3143
3154
  // A record begins its lifecycle in the `empty` state.
@@ -3149,7 +3160,8 @@ var RootState = {
3149
3160
  isEmpty: true,
3150
3161
 
3151
3162
  // EVENTS
3152
- loadingData: function(record) {
3163
+ loadingData: function(record, promise) {
3164
+ record._loadingPromise = promise;
3153
3165
  record.transitionTo('loading');
3154
3166
  },
3155
3167
 
@@ -3163,6 +3175,7 @@ var RootState = {
3163
3175
 
3164
3176
  pushedData: function(record) {
3165
3177
  record.transitionTo('loaded.saved');
3178
+ record.triggerLater('didLoad');
3166
3179
  }
3167
3180
  },
3168
3181
 
@@ -3176,6 +3189,10 @@ var RootState = {
3176
3189
  // FLAGS
3177
3190
  isLoading: true,
3178
3191
 
3192
+ exit: function(record) {
3193
+ record._loadingPromise = null;
3194
+ },
3195
+
3179
3196
  // EVENTS
3180
3197
  pushedData: function(record) {
3181
3198
  record.transitionTo('loaded.saved');
@@ -3185,6 +3202,10 @@ var RootState = {
3185
3202
 
3186
3203
  becameError: function(record) {
3187
3204
  record.triggerLater('becameError', record);
3205
+ },
3206
+
3207
+ notFound: function(record) {
3208
+ record.transitionTo('empty');
3188
3209
  }
3189
3210
  },
3190
3211
 
@@ -3301,7 +3322,7 @@ var RootState = {
3301
3322
  becomeDirty: Ember.K,
3302
3323
  deleteRecord: Ember.K,
3303
3324
 
3304
- becameClean: function(record) {
3325
+ rolledBack: function(record) {
3305
3326
  record.transitionTo('loaded.saved');
3306
3327
  }
3307
3328
  },
@@ -3322,6 +3343,11 @@ var RootState = {
3322
3343
  record.transitionTo('saved');
3323
3344
 
3324
3345
  record.send('invokeLifecycleCallbacks');
3346
+ },
3347
+
3348
+ becameError: function(record) {
3349
+ record.transitionTo('uncommitted');
3350
+ record.triggerLater('becameError', record);
3325
3351
  }
3326
3352
  },
3327
3353
 
@@ -3355,8 +3381,6 @@ var RootState = {
3355
3381
  }
3356
3382
  };
3357
3383
 
3358
- var hasOwnProp = {}.hasOwnProperty;
3359
-
3360
3384
  function wireState(object, parent, name) {
3361
3385
  /*jshint proto:true*/
3362
3386
  // TODO: Use Object.create and copy instead
@@ -3387,11 +3411,9 @@ DS.RootState = RootState;
3387
3411
  @module ember-data
3388
3412
  */
3389
3413
 
3390
- var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map,
3414
+ var get = Ember.get, set = Ember.set,
3391
3415
  merge = Ember.merge, once = Ember.run.once;
3392
3416
 
3393
- var arrayMap = Ember.ArrayPolyfills.map;
3394
-
3395
3417
  var retrieveFromCurrentState = Ember.computed(function(key, value) {
3396
3418
  return get(get(this, 'currentState'), key);
3397
3419
  }).property('currentState').readOnly();
@@ -3455,7 +3477,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3455
3477
  @returns {Object} A JSON representation of the object.
3456
3478
  */
3457
3479
  toJSON: function(options) {
3458
- var serializer = DS.JSONSerializer.create();
3480
+ // container is for lazy transform lookups
3481
+ var serializer = DS.JSONSerializer.create({ container: this.container });
3459
3482
  return serializer.serialize(this, options);
3460
3483
  },
3461
3484
 
@@ -3592,14 +3615,18 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3592
3615
  if (transaction) { fn(transaction); }
3593
3616
  },
3594
3617
 
3595
- loadingData: function() {
3596
- this.send('loadingData');
3618
+ loadingData: function(promise) {
3619
+ this.send('loadingData', promise);
3597
3620
  },
3598
3621
 
3599
3622
  loadedData: function() {
3600
3623
  this.send('loadedData');
3601
3624
  },
3602
3625
 
3626
+ notFound: function() {
3627
+ this.send('notFound');
3628
+ },
3629
+
3603
3630
  pushedData: function() {
3604
3631
  this.send('pushedData');
3605
3632
  },
@@ -3632,6 +3659,27 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3632
3659
  }
3633
3660
  },
3634
3661
 
3662
+ /**
3663
+ Gets the diff for the current model.
3664
+
3665
+ @method changedAttributes
3666
+
3667
+ @returns {Object} an object, whose keys are changed properties,
3668
+ and value is an [oldProp, newProp] array.
3669
+ */
3670
+ changedAttributes: function() {
3671
+ var oldData = get(this, '_data'),
3672
+ newData = get(this, '_attributes'),
3673
+ diffData = {},
3674
+ prop;
3675
+
3676
+ for (prop in newData) {
3677
+ diffData[prop] = [oldData[prop], newData[prop]];
3678
+ }
3679
+
3680
+ return diffData;
3681
+ },
3682
+
3635
3683
  adapterWillCommit: function() {
3636
3684
  this.send('willCommit');
3637
3685
  },
@@ -3677,6 +3725,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3677
3725
  var relationships = get(this.constructor, 'relationshipsByName');
3678
3726
  this.updateRecordArraysLater();
3679
3727
  relationships.forEach(function(name, relationship) {
3728
+ if (this._data.links && this._data.links[name]) { return; }
3680
3729
  if (relationship.kind === 'hasMany') {
3681
3730
  this.hasManyDidChange(relationship.key);
3682
3731
  }
@@ -3687,8 +3736,6 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3687
3736
  var hasMany = this._relationships[key];
3688
3737
 
3689
3738
  if (hasMany) {
3690
- var type = get(this.constructor, 'relationshipsByName').get(key).type;
3691
- var store = get(this, 'store');
3692
3739
  var records = this._data[key] || [];
3693
3740
 
3694
3741
  set(hasMany, 'content', Ember.A(records));
@@ -3701,8 +3748,19 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3701
3748
  Ember.run.once(this, this.updateRecordArrays);
3702
3749
  },
3703
3750
 
3704
- setupData: function(data) {
3705
- this._data = data;
3751
+ setupData: function(data, partial) {
3752
+ if (partial) {
3753
+ Ember.merge(this._data, data);
3754
+ } else {
3755
+ this._data = data;
3756
+ }
3757
+
3758
+ var relationships = this._relationships;
3759
+
3760
+ this.eachRelationship(function(name, rel) {
3761
+ if (data.links && data.links[name]) { return; }
3762
+ if (rel.options.async) { relationships[name] = null; }
3763
+ });
3706
3764
 
3707
3765
  if (data) { this.pushedData(); }
3708
3766
 
@@ -3729,9 +3787,19 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3729
3787
  this.hasManyDidChange(name);
3730
3788
  },
3731
3789
 
3790
+ updateBelongsTo: function(name, record) {
3791
+ this._data[name] = record;
3792
+ },
3793
+
3732
3794
  rollback: function() {
3733
- this._setup();
3734
- this.send('becameClean');
3795
+ this._attributes = {};
3796
+
3797
+ if (get(this, 'isError')) {
3798
+ this._inFlightAttributes = {};
3799
+ set(this, 'isError', false);
3800
+ }
3801
+
3802
+ this.send('rolledBack');
3735
3803
 
3736
3804
  this.suspendRelationshipObservers(function() {
3737
3805
  this.notifyPropertyChange('data');
@@ -3781,7 +3849,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3781
3849
  @method save
3782
3850
  */
3783
3851
  save: function() {
3784
- var resolver = Ember.RSVP.defer(), record = this;
3852
+ var resolver = Ember.RSVP.defer();
3785
3853
 
3786
3854
  this.get('store').scheduleSave(this, resolver);
3787
3855
  this._inFlightAttributes = this._attributes;
@@ -3962,21 +4030,48 @@ DS.Model.reopen({
3962
4030
  }
3963
4031
  });
3964
4032
 
3965
- function getAttr(record, options, key) {
3966
- var attributes = get(record, 'data');
3967
- var value = attributes[key];
3968
-
3969
- if (value === undefined) {
3970
- if (typeof options.defaultValue === "function") {
3971
- value = options.defaultValue();
3972
- } else {
3973
- value = options.defaultValue;
3974
- }
4033
+ function getDefaultValue(record, options, key) {
4034
+ if (typeof options.defaultValue === "function") {
4035
+ return options.defaultValue();
4036
+ } else {
4037
+ return options.defaultValue;
3975
4038
  }
4039
+ }
3976
4040
 
3977
- return value;
4041
+ function hasValue(record, key) {
4042
+ return record._attributes.hasOwnProperty(key) ||
4043
+ record._inFlightAttributes.hasOwnProperty(key) ||
4044
+ record._data.hasOwnProperty(key);
4045
+ }
4046
+
4047
+ function getValue(record, key) {
4048
+ if (record._attributes.hasOwnProperty(key)) {
4049
+ return record._attributes[key];
4050
+ } else if (record._inFlightAttributes.hasOwnProperty(key)) {
4051
+ return record._inFlightAttributes[key];
4052
+ } else {
4053
+ return record._data[key];
4054
+ }
3978
4055
  }
3979
4056
 
4057
+ /**
4058
+ `DS.attr` defines an attribute on a DS.Model.
4059
+ By default, attributes are passed through as-is, however you can specify an
4060
+ optional type to have the value automatically transformed.
4061
+ Ember Data ships with four basic transform types:
4062
+ 'string', 'number', 'boolean' and 'date'.
4063
+ You can define your own transforms by subclassing DS.Transform.
4064
+
4065
+ DS.attr takes an optional hash as a second parameter, currently
4066
+ supported options are:
4067
+ 'defaultValue': Pass a string or a function to be called to set the attribute
4068
+ to a default value if none is supplied.
4069
+
4070
+ @method attr
4071
+ @param {String} type the attribute type
4072
+ @param {Object} options a hash of options
4073
+ */
4074
+
3980
4075
  DS.attr = function(type, options) {
3981
4076
  options = options || {};
3982
4077
 
@@ -3986,18 +4081,19 @@ DS.attr = function(type, options) {
3986
4081
  options: options
3987
4082
  };
3988
4083
 
3989
- return Ember.computed(function(key, value, oldValue) {
4084
+ return Ember.computed(function(key, value) {
3990
4085
  if (arguments.length > 1) {
3991
4086
  Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
3992
- this.send('didSetProperty', { name: key, oldValue: this._attributes[key] || this._inFlightAttributes[key] || this._data[key], value: value });
4087
+ var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key];
4088
+ this.send('didSetProperty', { name: key, oldValue: oldValue, originalValue: this._data[key], value: value });
3993
4089
  this._attributes[key] = value;
3994
- } else if (this._attributes[key]) {
3995
- return this._attributes[key];
4090
+ return value;
4091
+ } else if (hasValue(this, key)) {
4092
+ return getValue(this, key);
3996
4093
  } else {
3997
- value = getAttr(this, options, key);
4094
+ return getDefaultValue(this, options, key);
3998
4095
  }
3999
4096
 
4000
- return value;
4001
4097
  // `data` is never set directly. However, it may be
4002
4098
  // invalidated from the state manager's setData
4003
4099
  // event.
@@ -4545,16 +4641,33 @@ function asyncBelongsTo(type, options, meta) {
4545
4641
  store = get(this, 'store');
4546
4642
 
4547
4643
  if (arguments.length === 2) {
4548
- Ember.assert("You can only add a '" + type + "' record to this relationship", !value || store.modelFor(type).detectInstance(value));
4644
+ Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type));
4549
4645
  return value === undefined ? null : value;
4550
4646
  }
4551
4647
 
4552
- return store.fetchRecord(data[key]);
4648
+ var link = data.links && data.links[key],
4649
+ belongsTo = data[key];
4650
+
4651
+ if(!isNone(belongsTo)) {
4652
+ var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo);
4653
+ return DS.PromiseObject.create({promise: promise});
4654
+ } else if (link) {
4655
+ var resolver = Ember.RSVP.defer();
4656
+ store.findBelongsTo(this, link, meta, resolver);
4657
+ return DS.PromiseObject.create({ promise: resolver.promise });
4658
+ } else {
4659
+ return null;
4660
+ }
4553
4661
  }).property('data').meta(meta);
4554
4662
  }
4555
4663
 
4556
4664
  DS.belongsTo = function(type, options) {
4557
- Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
4665
+ if (typeof type === 'object') {
4666
+ options = type;
4667
+ type = undefined;
4668
+ } else {
4669
+ Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
4670
+ }
4558
4671
 
4559
4672
  options = options || {};
4560
4673
 
@@ -4569,17 +4682,13 @@ DS.belongsTo = function(type, options) {
4569
4682
  store = get(this, 'store'), belongsTo, typeClass;
4570
4683
 
4571
4684
  if (typeof type === 'string') {
4572
- if (type.indexOf(".") === -1) {
4573
- typeClass = store.modelFor(type);
4574
- } else {
4575
- typeClass = get(Ember.lookup, type);
4576
- }
4685
+ typeClass = store.modelFor(type);
4577
4686
  } else {
4578
4687
  typeClass = type;
4579
4688
  }
4580
4689
 
4581
4690
  if (arguments.length === 2) {
4582
- Ember.assert("You can only add a '" + type + "' record to this relationship", !value || typeClass.detectInstance(value));
4691
+ Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass);
4583
4692
  return value === undefined ? null : value;
4584
4693
  }
4585
4694
 
@@ -4587,9 +4696,7 @@ DS.belongsTo = function(type, options) {
4587
4696
 
4588
4697
  if (isNone(belongsTo)) { return null; }
4589
4698
 
4590
- if (get(belongsTo, 'isEmpty')) {
4591
- store.fetchRecord(belongsTo);
4592
- }
4699
+ store.fetchRecord(belongsTo);
4593
4700
 
4594
4701
  return belongsTo;
4595
4702
  }).property('data').meta(meta);
@@ -4613,11 +4720,12 @@ DS.Model.reopen({
4613
4720
  */
4614
4721
  belongsToWillChange: Ember.beforeObserver(function(record, key) {
4615
4722
  if (get(record, 'isLoaded')) {
4616
- var oldParent = get(record, key),
4617
- store = get(record, 'store');
4723
+ var oldParent = get(record, key);
4724
+
4725
+ if (oldParent) {
4726
+ var store = get(record, 'store'),
4727
+ change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" });
4618
4728
 
4619
- if (oldParent){
4620
- var change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" });
4621
4729
  change.sync();
4622
4730
  this._changesToSync[key] = change;
4623
4731
  }
@@ -4634,7 +4742,8 @@ DS.Model.reopen({
4634
4742
  belongsToDidChange: Ember.immediateObserver(function(record, key) {
4635
4743
  if (get(record, 'isLoaded')) {
4636
4744
  var newParent = get(record, key);
4637
- if(newParent){
4745
+
4746
+ if (newParent) {
4638
4747
  var store = get(record, 'store'),
4639
4748
  change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" });
4640
4749
 
@@ -4656,10 +4765,11 @@ DS.Model.reopen({
4656
4765
  */
4657
4766
 
4658
4767
  var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties;
4659
- var forEach = Ember.EnumerableUtils.forEach;
4660
4768
 
4661
4769
  function asyncHasMany(type, options, meta) {
4662
4770
  return Ember.computed(function(key, value) {
4771
+ if (this._relationships[key]) { return this._relationships[key]; }
4772
+
4663
4773
  var resolver = Ember.RSVP.defer();
4664
4774
 
4665
4775
  var relationship = buildRelationship(this, key, options, function(store, data) {
@@ -4707,14 +4817,17 @@ function hasRelationship(type, options) {
4707
4817
  return Ember.computed(function(key, value) {
4708
4818
  return buildRelationship(this, key, options, function(store, data) {
4709
4819
  var records = data[key];
4710
- Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.attr({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false));
4820
+ Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false));
4711
4821
  return store.findMany(this, data[key], meta.type);
4712
4822
  });
4713
4823
  }).property('data').meta(meta);
4714
4824
  }
4715
4825
 
4716
4826
  DS.hasMany = function(type, options) {
4717
- Ember.assert("The type passed to DS.hasMany must be defined", !!type);
4827
+ if (typeof type === 'object') {
4828
+ options = type;
4829
+ type = undefined;
4830
+ }
4718
4831
  return hasRelationship(type, options);
4719
4832
  };
4720
4833
 
@@ -4753,7 +4866,7 @@ DS.Model.reopen({
4753
4866
  being defined. So, for example, when the user does this:
4754
4867
 
4755
4868
  DS.Model.extend({
4756
- parent: DS.belongsTo(App.User)
4869
+ parent: DS.belongsTo('user')
4757
4870
  });
4758
4871
 
4759
4872
  This hook would be called with "parent" as the key and the computed
@@ -4807,7 +4920,7 @@ DS.Model.reopenClass({
4807
4920
  For example, if you define a model like this:
4808
4921
 
4809
4922
  App.Post = DS.Model.extend({
4810
- comments: DS.hasMany(App.Comment)
4923
+ comments: DS.hasMany('comment')
4811
4924
  });
4812
4925
 
4813
4926
  Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`.
@@ -4881,9 +4994,9 @@ DS.Model.reopenClass({
4881
4994
  For example, given the following model definition:
4882
4995
 
4883
4996
  App.Blog = DS.Model.extend({
4884
- users: DS.hasMany(App.User),
4885
- owner: DS.belongsTo(App.User),
4886
- posts: DS.hasMany(App.Post)
4997
+ users: DS.hasMany('user'),
4998
+ owner: DS.belongsTo('user'),
4999
+ posts: DS.hasMany('post')
4887
5000
  });
4888
5001
 
4889
5002
  This computed property would return a map describing these
@@ -4913,7 +5026,7 @@ DS.Model.reopenClass({
4913
5026
  // it to the map.
4914
5027
  if (meta.isRelationship) {
4915
5028
  if (typeof meta.type === 'string') {
4916
- meta.type = Ember.get(Ember.lookup, meta.type);
5029
+ meta.type = this.store.modelFor(meta.type);
4917
5030
  }
4918
5031
 
4919
5032
  var relationshipsForType = map.get(meta.type);
@@ -4931,10 +5044,10 @@ DS.Model.reopenClass({
4931
5044
  definition:
4932
5045
 
4933
5046
  App.Blog = DS.Model.extend({
4934
- users: DS.hasMany(App.User),
4935
- owner: DS.belongsTo(App.User),
5047
+ users: DS.hasMany('user'),
5048
+ owner: DS.belongsTo('user'),
4936
5049
 
4937
- posts: DS.hasMany(App.Post)
5050
+ posts: DS.hasMany('post')
4938
5051
  });
4939
5052
 
4940
5053
  This property would contain the following:
@@ -4970,9 +5083,10 @@ DS.Model.reopenClass({
4970
5083
  For example, given a model with this definition:
4971
5084
 
4972
5085
  App.Blog = DS.Model.extend({
4973
- users: DS.hasMany(App.User),
4974
- owner: DS.belongsTo(App.User),
4975
- posts: DS.hasMany(App.Post)
5086
+ users: DS.hasMany('user'),
5087
+ owner: DS.belongsTo('user'),
5088
+
5089
+ posts: DS.hasMany('post')
4976
5090
  });
4977
5091
 
4978
5092
  This property would contain the following:
@@ -4997,7 +5111,7 @@ DS.Model.reopenClass({
4997
5111
  type = meta.type;
4998
5112
 
4999
5113
  if (typeof type === 'string') {
5000
- type = get(this, type, false) || get(Ember.lookup, type);
5114
+ type = get(this, type, false) || this.store.modelFor(type);
5001
5115
  }
5002
5116
 
5003
5117
  Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type);
@@ -5020,10 +5134,10 @@ DS.Model.reopenClass({
5020
5134
  definition:
5021
5135
 
5022
5136
  App.Blog = DS.Model.extend({
5023
- users: DS.hasMany(App.User),
5024
- owner: DS.belongsTo(App.User),
5137
+ users: DS.hasMany('user'),
5138
+ owner: DS.belongsTo('user'),
5025
5139
 
5026
- posts: DS.hasMany(App.Post)
5140
+ posts: DS.hasMany('post')
5027
5141
  });
5028
5142
 
5029
5143
  This property would contain the following:
@@ -5047,6 +5161,12 @@ DS.Model.reopenClass({
5047
5161
  meta.key = name;
5048
5162
  type = meta.type;
5049
5163
 
5164
+ if (!type && meta.kind === 'hasMany') {
5165
+ type = Ember.String.singularize(name);
5166
+ } else if (!type) {
5167
+ type = name;
5168
+ }
5169
+
5050
5170
  if (typeof type === 'string') {
5051
5171
  meta.type = this.store.modelFor(type);
5052
5172
  }
@@ -5066,10 +5186,10 @@ DS.Model.reopenClass({
5066
5186
  For example:
5067
5187
 
5068
5188
  App.Blog = DS.Model.extend({
5069
- users: DS.hasMany(App.User),
5070
- owner: DS.belongsTo(App.User),
5189
+ users: DS.hasMany('user'),
5190
+ owner: DS.belongsTo('user'),
5071
5191
 
5072
- posts: DS.hasMany(App.Post),
5192
+ posts: DS.hasMany('post'),
5073
5193
 
5074
5194
  title: DS.attr('string')
5075
5195
  });
@@ -5375,9 +5495,8 @@ DS.RecordArrayManager = Ember.Object.extend({
5375
5495
  @module ember-data
5376
5496
  */
5377
5497
 
5378
- var get = Ember.get, set = Ember.set, merge = Ember.merge;
5379
- var forEach = Ember.EnumerableUtils.forEach;
5380
- var resolve = Ember.RSVP.resolve;
5498
+ var get = Ember.get, set = Ember.set;
5499
+ var map = Ember.ArrayPolyfills.map;
5381
5500
 
5382
5501
  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
5383
5502
 
@@ -5391,18 +5510,6 @@ DS.InvalidError = function(errors) {
5391
5510
  };
5392
5511
  DS.InvalidError.prototype = Ember.create(Error.prototype);
5393
5512
 
5394
- function isThenable(object) {
5395
- return object && typeof object.then === 'function';
5396
- }
5397
-
5398
- // Simple dispatcher to support overriding the aliased
5399
- // method in subclasses.
5400
- function aliasMethod(methodName) {
5401
- return function() {
5402
- return this[methodName].apply(this, arguments);
5403
- };
5404
- }
5405
-
5406
5513
  /**
5407
5514
  An adapter is an object that receives requests from a store and
5408
5515
  translates them into the appropriate action to take against your
@@ -5441,7 +5548,7 @@ function aliasMethod(methodName) {
5441
5548
  * `deleteRecords()`
5442
5549
  * `commit()`
5443
5550
 
5444
- For an example implementation, see `DS.RestAdapter`, the
5551
+ For an example implementation, see `DS.RESTAdapter`, the
5445
5552
  included REST adapter.
5446
5553
 
5447
5554
  @class Adapter
@@ -5456,8 +5563,8 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5456
5563
  The `find()` method is invoked when the store is asked for a record that
5457
5564
  has not previously been loaded. In response to `find()` being called, you
5458
5565
  should query your persistence layer for a record with the given ID. Once
5459
- found, you can asynchronously call the store's `load()` method to load
5460
- the record.
5566
+ found, you can asynchronously call the store's `push()` method to push
5567
+ the record into the store.
5461
5568
 
5462
5569
  Here is an example `find` implementation:
5463
5570
 
@@ -5468,8 +5575,8 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5468
5575
  jQuery.getJSON(url, function(data) {
5469
5576
  // data is a hash of key/value pairs. If your server returns a
5470
5577
  // root, simply do something like:
5471
- // store.load(type, id, data.person)
5472
- store.load(type, id, data);
5578
+ // store.push(type, id, data.person)
5579
+ store.push(type, id, data);
5473
5580
  });
5474
5581
  }
5475
5582
 
@@ -5544,9 +5651,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5544
5651
  method on success or `didError` method on failure.
5545
5652
 
5546
5653
  @method createRecord
5547
- @property {DS.Store} store
5548
- @property {subclass of DS.Model} type the DS.Model class of the record
5549
- @property {DS.Model} record
5654
+ @param {DS.Store} store
5655
+ @param {subclass of DS.Model} type the DS.Model class of the record
5656
+ @param {DS.Model} record
5550
5657
  */
5551
5658
  createRecord: Ember.required(Function),
5552
5659
 
@@ -5557,9 +5664,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5557
5664
  Serializes the record update and send it to the server.
5558
5665
 
5559
5666
  @method updateRecord
5560
- @property {DS.Store} store
5561
- @property {subclass of DS.Model} type the DS.Model class of the record
5562
- @property {DS.Model} record
5667
+ @param {DS.Store} store
5668
+ @param {subclass of DS.Model} type the DS.Model class of the record
5669
+ @param {DS.Model} record
5563
5670
  */
5564
5671
  updateRecord: Ember.required(Function),
5565
5672
 
@@ -5570,9 +5677,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5570
5677
  Sends a delete request for the record to the server.
5571
5678
 
5572
5679
  @method deleteRecord
5573
- @property {DS.Store} store
5574
- @property {subclass of DS.Model} type the DS.Model class of the record
5575
- @property {DS.Model} record
5680
+ @param {DS.Store} store
5681
+ @param {subclass of DS.Model} type the DS.Model class of the record
5682
+ @param {DS.Model} record
5576
5683
  */
5577
5684
  deleteRecord: Ember.required(Function),
5578
5685
 
@@ -5584,12 +5691,12 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5584
5691
  server requests.
5585
5692
 
5586
5693
  @method findMany
5587
- @property {DS.Store} store
5588
- @property {subclass of DS.Model} type the DS.Model class of the records
5589
- @property {Array} ids
5694
+ @param {DS.Store} store
5695
+ @param {subclass of DS.Model} type the DS.Model class of the records
5696
+ @param {Array} ids
5590
5697
  */
5591
5698
  findMany: function(store, type, ids) {
5592
- var promises = ids.map(function(id) {
5699
+ var promises = map.call(ids, function(id) {
5593
5700
  return this.find(store, type, id);
5594
5701
  }, this);
5595
5702
 
@@ -5699,7 +5806,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5699
5806
  @param record
5700
5807
  */
5701
5808
  generateIdForRecord: function(store) {
5702
- return counter++;
5809
+ return "fixture-" + counter++;
5703
5810
  },
5704
5811
 
5705
5812
  /**
@@ -5712,7 +5819,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5712
5819
  var fixtures = this.fixturesForType(type),
5713
5820
  fixture;
5714
5821
 
5715
- Ember.warn("Unable to find fixtures for model type " + type.toString(), fixtures);
5822
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
5716
5823
 
5717
5824
  if (fixtures) {
5718
5825
  fixture = Ember.A(fixtures).findProperty('id', id);
@@ -5734,7 +5841,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5734
5841
  findMany: function(store, type, ids) {
5735
5842
  var fixtures = this.fixturesForType(type);
5736
5843
 
5737
- Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
5844
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
5738
5845
 
5739
5846
  if (fixtures) {
5740
5847
  fixtures = fixtures.filter(function(item) {
@@ -5757,7 +5864,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5757
5864
  findAll: function(store, type) {
5758
5865
  var fixtures = this.fixturesForType(type);
5759
5866
 
5760
- Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
5867
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
5761
5868
 
5762
5869
  return this.simulateRemoteCall(function() {
5763
5870
  return fixtures;
@@ -5774,7 +5881,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5774
5881
  findQuery: function(store, type, query, array) {
5775
5882
  var fixtures = this.fixturesForType(type);
5776
5883
 
5777
- Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
5884
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
5778
5885
 
5779
5886
  fixtures = this.queryFixtures(fixtures, query, type);
5780
5887
 
@@ -5914,17 +6021,53 @@ DS.FixtureAdapter = DS.Adapter.extend({
5914
6021
  */
5915
6022
 
5916
6023
  var get = Ember.get, set = Ember.set;
5917
-
5918
- DS.rejectionHandler = function(reason) {
5919
- Ember.Logger.assert([reason, reason.message, reason.stack]);
5920
-
5921
- throw reason;
5922
- };
6024
+ var forEach = Ember.ArrayPolyfills.forEach;
6025
+ var map = Ember.ArrayPolyfills.map;
5923
6026
 
5924
6027
  function coerceId(id) {
5925
6028
  return id == null ? null : id+'';
5926
6029
  }
5927
6030
 
6031
+ /**
6032
+ Normally, applications will use the `RESTSerializer` by implementing
6033
+ the `normalize` method and individual normalizations under
6034
+ `normalizeHash`.
6035
+
6036
+ This allows you to do whatever kind of munging you need, and is
6037
+ especially useful if your server is inconsistent and you need to
6038
+ do munging differently for many different kinds of responses.
6039
+
6040
+ See the `normalize` documentation for more information.
6041
+
6042
+ ## Across the Board Normalization
6043
+
6044
+ There are also a number of hooks that you might find useful to defined
6045
+ across-the-board rules for your payload. These rules will be useful
6046
+ if your server is consistent, or if you're building an adapter for
6047
+ an infrastructure service, like Parse, and want to encode service
6048
+ conventions.
6049
+
6050
+ For example, if all of your keys are underscored and all-caps, but
6051
+ otherwise consistent with the names you use in your models, you
6052
+ can implement across-the-board rules for how to convert an attribute
6053
+ name in your model to a key in your JSON.
6054
+
6055
+ ```js
6056
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6057
+ keyForAttribute: function(attr) {
6058
+ return Ember.String.underscore(attr).toUpperCase();
6059
+ }
6060
+ });
6061
+ ```
6062
+
6063
+ You can also implement `keyForRelationship`, which takes the name
6064
+ of the relationship as the first parameter, and the kind of
6065
+ relationship (`hasMany` or `belongsTo`) as the second parameter.
6066
+
6067
+ @class RESTSerializer
6068
+ @namespace DS
6069
+ @extends DS.JSONSerializer
6070
+ */
5928
6071
  DS.RESTSerializer = DS.JSONSerializer.extend({
5929
6072
  /**
5930
6073
  Normalizes a part of the JSON payload returned by
@@ -5990,15 +6133,42 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
5990
6133
  @param {Object} hash
5991
6134
  @returns Object
5992
6135
  */
5993
- normalize: function(type, prop, hash) {
6136
+ normalize: function(type, hash, prop) {
5994
6137
  this.normalizeId(hash);
5995
- this.normalizeAttributes(hash);
6138
+ this.normalizeUsingDeclaredMapping(type, hash);
6139
+ this.normalizeAttributes(type, hash);
6140
+ this.normalizeRelationships(type, hash);
5996
6141
 
5997
6142
  if (this.normalizeHash && this.normalizeHash[prop]) {
5998
6143
  return this.normalizeHash[prop](hash);
5999
6144
  }
6000
6145
 
6001
- return hash;
6146
+ return this._super(type, hash, prop);
6147
+ },
6148
+
6149
+ /**
6150
+ You can use this method to normalize all payloads, regardless of whether they
6151
+ represent single records or an array.
6152
+
6153
+ For example, you might want to remove some extraneous data from the payload:
6154
+
6155
+ ```js
6156
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6157
+ normalizePayload: function(type, payload) {
6158
+ delete payload.version;
6159
+ delete payload.status;
6160
+ return payload;
6161
+ }
6162
+ });
6163
+ ```
6164
+
6165
+ @method normalizePayload
6166
+ @param {subclass of DS.Model} type
6167
+ @param {Object} hash
6168
+ @returns Object the normalized payload
6169
+ */
6170
+ normalizePayload: function(type, payload) {
6171
+ return payload;
6002
6172
  },
6003
6173
 
6004
6174
  /**
@@ -6014,20 +6184,56 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6014
6184
  delete hash[primaryKey];
6015
6185
  },
6016
6186
 
6187
+ /**
6188
+ @method normalizeUsingDeclaredMapping
6189
+ @private
6190
+ */
6191
+ normalizeUsingDeclaredMapping: function(type, hash) {
6192
+ var attrs = get(this, 'attrs'), payloadKey, key;
6193
+
6194
+ if (attrs) {
6195
+ for (key in attrs) {
6196
+ payloadKey = attrs[key];
6197
+
6198
+ hash[key] = hash[payloadKey];
6199
+ delete hash[payloadKey];
6200
+ }
6201
+ }
6202
+ },
6203
+
6017
6204
  /**
6018
6205
  @method normalizeAttributes
6019
6206
  @private
6020
6207
  */
6021
- normalizeAttributes: function(hash) {
6022
- var attrs = get(this, 'attrs');
6208
+ normalizeAttributes: function(type, hash) {
6209
+ var payloadKey, key;
6210
+
6211
+ if (this.keyForAttribute) {
6212
+ type.eachAttribute(function(key) {
6213
+ payloadKey = this.keyForAttribute(key);
6214
+ if (key === payloadKey) { return; }
6215
+
6216
+ hash[key] = hash[payloadKey];
6217
+ delete hash[payloadKey];
6218
+ }, this);
6219
+ }
6220
+ },
6023
6221
 
6024
- if (!attrs) { return; }
6222
+ /**
6223
+ @method normalizeRelationships
6224
+ @private
6225
+ */
6226
+ normalizeRelationships: function(type, hash) {
6227
+ var payloadKey, key;
6025
6228
 
6026
- for (var key in attrs) {
6027
- var payloadKey = attrs[key];
6229
+ if (this.keyForRelationship) {
6230
+ type.eachRelationship(function(key, relationship) {
6231
+ payloadKey = this.keyForRelationship(key, relationship.kind);
6232
+ if (key === payloadKey) { return; }
6028
6233
 
6029
- hash[key] = hash[payloadKey];
6030
- delete hash[payloadKey];
6234
+ hash[key] = hash[payloadKey];
6235
+ delete hash[payloadKey];
6236
+ }, this);
6031
6237
  }
6032
6238
  },
6033
6239
 
@@ -6100,6 +6306,7 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6100
6306
  for the first time or updated (`createRecord` or `updateRecord`). In
6101
6307
  particular, it will update the properties of the record that was saved.
6102
6308
 
6309
+ @method extractSingle
6103
6310
  @param {DS.Store} store
6104
6311
  @param {subclass of DS.Model} type
6105
6312
  @param {Object} payload
@@ -6108,25 +6315,33 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6108
6315
  @returns Object the primary response to the original request
6109
6316
  */
6110
6317
  extractSingle: function(store, primaryType, payload, recordId, requestType) {
6318
+ payload = this.normalizePayload(primaryType, payload);
6319
+
6111
6320
  var primaryTypeName = primaryType.typeKey,
6112
6321
  primaryRecord;
6113
6322
 
6114
6323
  for (var prop in payload) {
6115
- // legacy support for singular names
6116
- if (prop === primaryTypeName) {
6117
- primaryRecord = this.normalize(primaryType, prop, payload[prop]);
6324
+ var typeName = this.typeForRoot(prop),
6325
+ isPrimary = typeName === primaryTypeName;
6326
+
6327
+ // legacy support for singular resources
6328
+ if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) {
6329
+ primaryRecord = this.normalize(primaryType, payload[prop], prop);
6118
6330
  continue;
6119
6331
  }
6120
6332
 
6121
- var typeName = this.singularize(prop),
6122
- type = store.modelFor(typeName);
6333
+ var type = store.modelFor(typeName);
6123
6334
 
6124
6335
  /*jshint loopfunc:true*/
6125
- payload[prop].forEach(function(hash) {
6126
- hash = this.normalize(type, prop, hash);
6336
+ forEach.call(payload[prop], function(hash) {
6337
+ var typeName = this.typeForRoot(prop),
6338
+ type = store.modelFor(typeName),
6339
+ typeSerializer = store.serializerFor(type);
6340
+
6341
+ hash = typeSerializer.normalize(type, hash, prop);
6127
6342
 
6128
- var isFirstCreatedRecord = typeName === primaryTypeName && !recordId && !primaryRecord,
6129
- isUpdatedRecord = typeName === primaryTypeName && coerceId(hash.id) === recordId;
6343
+ var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord,
6344
+ isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId;
6130
6345
 
6131
6346
  // find the primary record.
6132
6347
  //
@@ -6233,6 +6448,12 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6233
6448
  or `findHasMany`. In particular, the primary array will become the
6234
6449
  list of records in the record array that kicked off the request.
6235
6450
 
6451
+ If your primary array contains secondary (embedded) records of the same type,
6452
+ you cannot place these into the primary array `posts`. Instead, place the
6453
+ secondary items into an underscore prefixed property `_posts`, which will
6454
+ push these items into the store and will not affect the resulting query.
6455
+
6456
+ @method extractArray
6236
6457
  @param {DS.Store} store
6237
6458
  @param {subclass of DS.Model} type
6238
6459
  @param {Object} payload
@@ -6241,17 +6462,28 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6241
6462
  to the original query.
6242
6463
  */
6243
6464
  extractArray: function(store, primaryType, payload) {
6465
+ payload = this.normalizePayload(primaryType, payload);
6466
+
6244
6467
  var primaryTypeName = primaryType.typeKey,
6245
6468
  primaryArray;
6246
6469
 
6247
6470
  for (var prop in payload) {
6248
- var typeName = this.singularize(prop),
6471
+ var typeKey = prop,
6472
+ forcedSecondary = false;
6473
+
6474
+ if (prop.charAt(0) === '_') {
6475
+ forcedSecondary = true;
6476
+ typeKey = prop.substr(1);
6477
+ }
6478
+
6479
+ var typeName = this.typeForRoot(typeKey),
6249
6480
  type = store.modelFor(typeName),
6250
- isPrimary = typeName === primaryTypeName;
6481
+ typeSerializer = store.serializerFor(type),
6482
+ isPrimary = (!forcedSecondary && (typeName === primaryTypeName));
6251
6483
 
6252
6484
  /*jshint loopfunc:true*/
6253
- var normalizedArray = payload[prop].map(function(hash) {
6254
- return this.normalize(type, prop, hash);
6485
+ var normalizedArray = map.call(payload[prop], function(hash) {
6486
+ return typeSerializer.normalize(type, hash, prop);
6255
6487
  }, this);
6256
6488
 
6257
6489
  if (isPrimary) {
@@ -6265,21 +6497,73 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6265
6497
  },
6266
6498
 
6267
6499
  /**
6268
- @private
6269
- @method pluralize
6270
- @param {String} key
6500
+ This method allows you to push a payload containing top-level
6501
+ collections of records organized per type.
6502
+
6503
+ ```js
6504
+ {
6505
+ "posts": [{
6506
+ "id": "1",
6507
+ "title": "Rails is omakase",
6508
+ "author", "1",
6509
+ "comments": [ "1" ]
6510
+ }],
6511
+ "comments": [{
6512
+ "id": "1",
6513
+ "body": "FIRST
6514
+ }],
6515
+ "users": [{
6516
+ "id": "1",
6517
+ "name": "@d2h"
6518
+ }]
6519
+ }
6520
+ ```
6521
+
6522
+ It will first normalize the payload, so you can use this to push
6523
+ in data streaming in from your server structured the same way
6524
+ that fetches and saves are structured.
6525
+
6526
+ @param {DS.Store} store
6527
+ @param {Object} payload
6271
6528
  */
6272
- pluralize: function(key) {
6273
- return Ember.String.pluralize(key);
6529
+ pushPayload: function(store, payload) {
6530
+ payload = this.normalizePayload(null, payload);
6531
+
6532
+ for (var prop in payload) {
6533
+ var typeName = this.typeForRoot(prop),
6534
+ type = store.modelFor(typeName);
6535
+
6536
+ /*jshint loopfunc:true*/
6537
+ var normalizedArray = map.call(payload[prop], function(hash) {
6538
+ return this.normalize(type, hash, prop);
6539
+ }, this);
6540
+
6541
+ store.pushMany(typeName, normalizedArray);
6542
+ }
6274
6543
  },
6275
6544
 
6276
6545
  /**
6277
- @private
6278
- @method singularize
6279
- @param {String} key
6546
+ You can use this method to normalize the JSON root keys returned
6547
+ into the model type expected by your store.
6548
+
6549
+ For example, your server may return underscored root keys rather than
6550
+ the expected camelcased versions.
6551
+
6552
+ ```js
6553
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6554
+ typeForRoot: function(root) {
6555
+ var camelized = Ember.String.camelize(root);
6556
+ return Ember.String.singularize(camelized);
6557
+ }
6558
+ });
6559
+ ```
6560
+
6561
+ @method typeForRoot
6562
+ @param {String} root
6563
+ @returns String the model's typeKey
6280
6564
  */
6281
- singularize: function(key) {
6282
- return Ember.String.singularize(key);
6565
+ typeForRoot: function(root) {
6566
+ return Ember.String.singularize(root);
6283
6567
  },
6284
6568
 
6285
6569
  // SERIALIZE
@@ -6421,12 +6705,69 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6421
6705
  }
6422
6706
  });
6423
6707
  ```
6708
+
6709
+ @method serialize
6710
+ @param record
6711
+ @param options
6424
6712
  */
6425
6713
  serialize: function(record, options) {
6426
6714
  return this._super.apply(this, arguments);
6715
+ },
6716
+
6717
+ /**
6718
+ You can use this method to customize the root keys serialized into the JSON.
6719
+ By default the REST Serializer sends camelized root keys.
6720
+ For example, your server may expect underscored root objects.
6721
+
6722
+ ```js
6723
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6724
+ serializeIntoHash: function(data, type, record, options) {
6725
+ var root = Ember.String.decamelize(type.typeKey);
6726
+ data[root] = this.serialize(record, options);
6727
+ }
6728
+ });
6729
+ ```
6730
+
6731
+ @method serializeIntoHash
6732
+ @param {Object} hash
6733
+ @param {subclass of DS.Model} type
6734
+ @param {DS.Model} record
6735
+ @param {Object} options
6736
+ */
6737
+ serializeIntoHash: function(hash, type, record, options) {
6738
+ hash[type.typeKey] = this.serialize(record, options);
6739
+ },
6740
+
6741
+ /**
6742
+ You can use this method to customize how polymorphic objects are serialized.
6743
+ By default the JSON Serializer creates the key by appending `Type` to
6744
+ the attribute and value from the model's camelcased model name.
6745
+
6746
+ @method serializePolymorphicType
6747
+ @param {DS.Model} record
6748
+ @param {Object} json
6749
+ @param relationship
6750
+ */
6751
+ serializePolymorphicType: function(record, json, relationship) {
6752
+ var key = relationship.key,
6753
+ belongsTo = get(record, key);
6754
+ key = this.keyForAttribute ? this.keyForAttribute(key) : key;
6755
+ json[key + "Type"] = belongsTo.constructor.typeKey;
6427
6756
  }
6428
6757
  });
6429
6758
 
6759
+ })();
6760
+
6761
+
6762
+
6763
+ (function() {
6764
+ /**
6765
+ @module ember-data
6766
+ */
6767
+
6768
+ var get = Ember.get, set = Ember.set;
6769
+ var forEach = Ember.ArrayPolyfills.forEach;
6770
+
6430
6771
  /**
6431
6772
  The REST adapter allows your store to communicate with an HTTP server by
6432
6773
  transmitting JSON via XHR. Most Ember.js apps that consume a JSON API
@@ -6457,7 +6798,7 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6457
6798
 
6458
6799
  ### Conventional Names
6459
6800
 
6460
- Attribute names in your JSON payload should be the underscored versions of
6801
+ Attribute names in your JSON payload should be the camelcased versions of
6461
6802
  the attributes in your Ember.js models.
6462
6803
 
6463
6804
  For example, if you have a `Person` model:
@@ -6475,8 +6816,8 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6475
6816
  ```js
6476
6817
  {
6477
6818
  "person": {
6478
- "first_name": "Barack",
6479
- "last_name": "Obama",
6819
+ "firstName": "Barack",
6820
+ "lastName": "Obama",
6480
6821
  "occupation": "President"
6481
6822
  }
6482
6823
  }
@@ -6498,11 +6839,11 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6498
6839
 
6499
6840
  ### Host customization
6500
6841
 
6501
- An adapter can target other hosts by setting the `url` property.
6842
+ An adapter can target other hosts by setting the `host` property.
6502
6843
 
6503
6844
  ```js
6504
6845
  DS.RESTAdapter.reopen({
6505
- url: 'https://api.example.com'
6846
+ host: 'https://api.example.com'
6506
6847
  });
6507
6848
  ```
6508
6849
 
@@ -6544,7 +6885,7 @@ DS.RESTAdapter = DS.Adapter.extend({
6544
6885
  @returns Promise
6545
6886
  */
6546
6887
  find: function(store, type, id) {
6547
- return this.ajax(this.buildURL(type, id), 'GET');
6888
+ return this.ajax(this.buildURL(type.typeKey, id), 'GET');
6548
6889
  },
6549
6890
 
6550
6891
  /**
@@ -6559,10 +6900,17 @@ DS.RESTAdapter = DS.Adapter.extend({
6559
6900
  @see RESTAdapter/ajax
6560
6901
  @param {DS.Store} store
6561
6902
  @param {subclass of DS.Model} type
6903
+ @param {String} sinceToken
6562
6904
  @returns Promise
6563
6905
  */
6564
- findAll: function(store, type) {
6565
- return this.ajax(this.buildURL(type), 'GET');
6906
+ findAll: function(store, type, sinceToken) {
6907
+ var query;
6908
+
6909
+ if (sinceToken) {
6910
+ query = { since: sinceToken };
6911
+ }
6912
+
6913
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });
6566
6914
  },
6567
6915
 
6568
6916
  /**
@@ -6584,7 +6932,7 @@ DS.RESTAdapter = DS.Adapter.extend({
6584
6932
  @returns Promise
6585
6933
  */
6586
6934
  findQuery: function(store, type, query) {
6587
- return this.ajax(this.buildURL(type), 'GET', query);
6935
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });
6588
6936
  },
6589
6937
 
6590
6938
  /**
@@ -6623,8 +6971,8 @@ DS.RESTAdapter = DS.Adapter.extend({
6623
6971
  @param {Array<String>} ids
6624
6972
  @returns Promise
6625
6973
  */
6626
- findMany: function(store, type, ids) {
6627
- return this.ajax(this.buildURL(type), 'GET', { ids: ids });
6974
+ findMany: function(store, type, ids, owner) {
6975
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } });
6628
6976
  },
6629
6977
 
6630
6978
  /**
@@ -6657,7 +7005,46 @@ DS.RESTAdapter = DS.Adapter.extend({
6657
7005
  @returns Promise
6658
7006
  */
6659
7007
  findHasMany: function(store, record, url) {
6660
- return this.ajax(url, 'GET');
7008
+ var id = get(record, 'id'),
7009
+ type = record.constructor.typeKey;
7010
+
7011
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
7012
+ },
7013
+
7014
+ /**
7015
+ Called by the store in order to fetch a JSON array for
7016
+ the unloaded records in a belongs-to relationship that were originally
7017
+ specified as a URL (inside of `links`).
7018
+
7019
+ For example, if your original payload looks like this:
7020
+
7021
+ ```js
7022
+ {
7023
+ "person": {
7024
+ "id": 1,
7025
+ "name": "Tom Dale",
7026
+ "links": { "group": "/people/1/group" }
7027
+ }
7028
+ }
7029
+ ```
7030
+
7031
+ This method will be called with the parent record and `/people/1/group`.
7032
+
7033
+ It will make an Ajax request to the originally specified URL.
7034
+
7035
+ @method findBelongsTo
7036
+ @see RESTAdapter/buildURL
7037
+ @see RESTAdapter/ajax
7038
+ @param {DS.Store} store
7039
+ @param {DS.Model} record
7040
+ @param {String} url
7041
+ @returns Promise
7042
+ */
7043
+ findBelongsTo: function(store, record, url) {
7044
+ var id = get(record, 'id'),
7045
+ type = record.constructor.typeKey;
7046
+
7047
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
6661
7048
  },
6662
7049
 
6663
7050
  /**
@@ -6680,9 +7067,11 @@ DS.RESTAdapter = DS.Adapter.extend({
6680
7067
  */
6681
7068
  createRecord: function(store, type, record) {
6682
7069
  var data = {};
6683
- data[type.typeKey] = this.serializerFor(type.typeKey).serialize(record, { includeId: true });
7070
+ var serializer = store.serializerFor(type.typeKey);
7071
+
7072
+ serializer.serializeIntoHash(data, type, record, { includeId: true });
6684
7073
 
6685
- return this.ajax(this.buildURL(type), "POST", { data: data });
7074
+ return this.ajax(this.buildURL(type.typeKey), "POST", { data: data });
6686
7075
  },
6687
7076
 
6688
7077
  /**
@@ -6704,11 +7093,13 @@ DS.RESTAdapter = DS.Adapter.extend({
6704
7093
  */
6705
7094
  updateRecord: function(store, type, record) {
6706
7095
  var data = {};
6707
- data[type.typeKey] = this.serializerFor(type.typeKey).serialize(record);
7096
+ var serializer = store.serializerFor(type.typeKey);
7097
+
7098
+ serializer.serializeIntoHash(data, type, record);
6708
7099
 
6709
7100
  var id = get(record, 'id');
6710
7101
 
6711
- return this.ajax(this.buildURL(type, id), "PUT", { data: data });
7102
+ return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data });
6712
7103
  },
6713
7104
 
6714
7105
  /**
@@ -6728,7 +7119,7 @@ DS.RESTAdapter = DS.Adapter.extend({
6728
7119
  deleteRecord: function(store, type, record) {
6729
7120
  var id = get(record, 'id');
6730
7121
 
6731
- return this.ajax(this.buildURL(type, id), "DELETE");
7122
+ return this.ajax(this.buildURL(type.typeKey, id), "DELETE");
6732
7123
  },
6733
7124
 
6734
7125
  /**
@@ -6737,41 +7128,116 @@ DS.RESTAdapter = DS.Adapter.extend({
6737
7128
  By default, it pluralizes the type's name (for example,
6738
7129
  'post' becomes 'posts' and 'person' becomes 'people').
6739
7130
 
6740
- If an ID is specified, it adds the ID to the plural form
6741
- of the type, separated by a `/`.
7131
+ If an ID is specified, it adds the ID to the path generated
7132
+ for the type, separated by a `/`.
6742
7133
 
6743
7134
  @method buildURL
6744
- @param {subclass of DS.Model} type
7135
+ @param {String} type
6745
7136
  @param {String} id
6746
7137
  @returns String
6747
7138
  */
6748
7139
  buildURL: function(type, id) {
6749
- var url = "/" + Ember.String.pluralize(type.typeKey);
6750
- if (id) { url += "/" + id; }
7140
+ var url = [],
7141
+ host = get(this, 'host'),
7142
+ prefix = this.urlPrefix();
7143
+
7144
+ if (type) { url.push(this.pathForType(type)); }
7145
+ if (id) { url.push(id); }
7146
+
7147
+ if (prefix) { url.unshift(prefix); }
7148
+
7149
+ url = url.join('/');
7150
+ if (!host && url) { url = '/' + url; }
6751
7151
 
6752
7152
  return url;
6753
7153
  },
6754
7154
 
6755
- serializerFor: function(type) {
6756
- // This logic has to be kept in sync with DS.Store#serializerFor
6757
- return this.container.lookup('serializer:' + type) ||
6758
- this.container.lookup('serializer:application') ||
6759
- this.container.lookup('serializer:_rest');
6760
- },
7155
+ urlPrefix: function(path, parentURL) {
7156
+ var host = get(this, 'host'),
7157
+ namespace = get(this, 'namespace'),
7158
+ url = [];
7159
+
7160
+ if (path) {
7161
+ // Absolute path
7162
+ if (path.charAt(0) === '/') {
7163
+ if (host) {
7164
+ path = path.slice(1);
7165
+ url.push(host);
7166
+ }
7167
+ // Relative path
7168
+ } else if (!/^http(s)?:\/\//.test(path)) {
7169
+ url.push(parentURL);
7170
+ }
7171
+ } else {
7172
+ if (host) { url.push(host); }
7173
+ if (namespace) { url.push(namespace); }
7174
+ }
7175
+
7176
+ if (path) {
7177
+ url.push(path);
7178
+ }
6761
7179
 
7180
+ return url.join('/');
7181
+ },
6762
7182
 
6763
7183
  /**
6764
- Takes a URL, an HTTP method and a hash of data, and makes an
6765
- HTTP request.
7184
+ Determines the pathname for a given type.
6766
7185
 
6767
- When the server responds with a payload, Ember Data will call into `extractSingle`
6768
- or `extractArray` (depending on whether the original query was for one record or
6769
- many records).
7186
+ By default, it pluralizes the type's name (for example,
7187
+ 'post' becomes 'posts' and 'person' becomes 'people').
6770
7188
 
6771
- By default, it has the following behavior:
7189
+ ### Pathname customization
6772
7190
 
6773
- * It sets the response `dataType` to `"json"`
6774
- * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be
7191
+ For example if you have an object LineItem with an
7192
+ endpoint of "/line_items/".
7193
+
7194
+ ```js
7195
+ DS.RESTAdapter.reopen({
7196
+ pathForType: function(type) {
7197
+ var decamelized = Ember.String.decamelize(type);
7198
+ return Ember.String.pluralize(decamelized);
7199
+ };
7200
+ });
7201
+ ```
7202
+
7203
+ @method pathForType
7204
+ @param {String} type
7205
+ @returns String
7206
+ **/
7207
+ pathForType: function(type) {
7208
+ return Ember.String.pluralize(type);
7209
+ },
7210
+
7211
+ /**
7212
+ Takes an ajax response, and returns a relavant error.
7213
+
7214
+ By default, it has the following behavior:
7215
+
7216
+ * It simply returns the ajax response.
7217
+
7218
+ @method ajaxError
7219
+ @param jqXHR
7220
+ */
7221
+ ajaxError: function(jqXHR) {
7222
+ if (jqXHR) {
7223
+ jqXHR.then = null;
7224
+ }
7225
+
7226
+ return jqXHR;
7227
+ },
7228
+
7229
+ /**
7230
+ Takes a URL, an HTTP method and a hash of data, and makes an
7231
+ HTTP request.
7232
+
7233
+ When the server responds with a payload, Ember Data will call into `extractSingle`
7234
+ or `extractArray` (depending on whether the original query was for one record or
7235
+ many records).
7236
+
7237
+ By default, it has the following behavior:
7238
+
7239
+ * It sets the response `dataType` to `"json"`
7240
+ * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be
6775
7241
  `application/json; charset=utf-8`
6776
7242
  * If the HTTP method is not `"GET"`, it stringifies the data passed in. The
6777
7243
  data is the serialized record in the case of a save.
@@ -6801,7 +7267,7 @@ DS.RESTAdapter = DS.Adapter.extend({
6801
7267
  if (adapter.headers !== undefined) {
6802
7268
  var headers = adapter.headers;
6803
7269
  hash.beforeSend = function (xhr) {
6804
- Ember.keys(headers).forEach(function(key) {
7270
+ forEach.call(Ember.keys(headers), function(key) {
6805
7271
  xhr.setRequestHeader(key, headers[key]);
6806
7272
  });
6807
7273
  };
@@ -6812,11 +7278,7 @@ DS.RESTAdapter = DS.Adapter.extend({
6812
7278
  };
6813
7279
 
6814
7280
  hash.error = function(jqXHR, textStatus, errorThrown) {
6815
- if (jqXHR) {
6816
- jqXHR.then = null;
6817
- }
6818
-
6819
- Ember.run(null, reject, jqXHR);
7281
+ Ember.run(null, reject, adapter.ajaxError(jqXHR));
6820
7282
  };
6821
7283
 
6822
7284
  Ember.$.ajax(hash);
@@ -6842,16 +7304,20 @@ DS.RESTAdapter = DS.Adapter.extend({
6842
7304
  DS.Model.reopen({
6843
7305
 
6844
7306
  /**
6845
- Provides info about the model for debugging purposes
6846
- by grouping the properties into more semantic groups.
7307
+ Provides info about the model for debugging purposes
7308
+ by grouping the properties into more semantic groups.
6847
7309
 
6848
- Meant to be used by debugging tools such as the Chrome Ember Extension.
7310
+ Meant to be used by debugging tools such as the Chrome Ember Extension.
6849
7311
 
6850
- - Groups all attributes in "Attributes" group.
6851
- - Groups all belongsTo relationships in "Belongs To" group.
6852
- - Groups all hasMany relationships in "Has Many" group.
6853
- - Groups all flags in "Flags" group.
6854
- - Flags relationship CPs as expensive properties.
7312
+ - Groups all attributes in "Attributes" group.
7313
+ - Groups all belongsTo relationships in "Belongs To" group.
7314
+ - Groups all hasMany relationships in "Has Many" group.
7315
+ - Groups all flags in "Flags" group.
7316
+ - Flags relationship CPs as expensive properties.
7317
+
7318
+ @method _debugInfo
7319
+ @for DS.Model
7320
+ @private
6855
7321
  */
6856
7322
  _debugInfo: function() {
6857
7323
  var attributes = ['id'],
@@ -6871,7 +7337,7 @@ DS.Model.reopen({
6871
7337
  {
6872
7338
  name: 'Attributes',
6873
7339
  properties: attributes,
6874
- expand: true,
7340
+ expand: true
6875
7341
  },
6876
7342
  {
6877
7343
  name: 'Belongs To',
@@ -6945,3 +7411,695 @@ DS.Model.reopen({
6945
7411
 
6946
7412
  })();
6947
7413
 
7414
+ (function() {
7415
+ Ember.String.pluralize = function(word) {
7416
+ return Ember.Inflector.inflector.pluralize(word);
7417
+ };
7418
+
7419
+ Ember.String.singularize = function(word) {
7420
+ return Ember.Inflector.inflector.singularize(word);
7421
+ };
7422
+
7423
+ })();
7424
+
7425
+
7426
+
7427
+ (function() {
7428
+ var BLANK_REGEX = /^\s*$/;
7429
+
7430
+ function loadUncountable(rules, uncountable) {
7431
+ for (var i = 0, length = uncountable.length; i < length; i++) {
7432
+ rules.uncountable[uncountable[i]] = true;
7433
+ }
7434
+ }
7435
+
7436
+ function loadIrregular(rules, irregularPairs) {
7437
+ var pair;
7438
+
7439
+ for (var i = 0, length = irregularPairs.length; i < length; i++) {
7440
+ pair = irregularPairs[i];
7441
+
7442
+ rules.irregular[pair[0]] = pair[1];
7443
+ rules.irregularInverse[pair[1]] = pair[0];
7444
+ }
7445
+ }
7446
+
7447
+ /**
7448
+ Inflector.Ember provides a mechanism for supplying inflection rules for your
7449
+ application. Ember includes a default set of inflection rules, and provides an
7450
+ API for providing additional rules.
7451
+
7452
+ Examples:
7453
+
7454
+ Creating an inflector with no rules.
7455
+
7456
+ ```js
7457
+ var inflector = new Ember.Inflector();
7458
+ ```
7459
+
7460
+ Creating an inflector with the default ember ruleset.
7461
+
7462
+ ```js
7463
+ var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
7464
+
7465
+ inflector.pluralize('cow') //=> 'kine'
7466
+ inflector.singularize('kine') //=> 'cow'
7467
+ ```
7468
+
7469
+ Creating an inflector and adding rules later.
7470
+
7471
+ ```javascript
7472
+ var inflector = Ember.Inflector.inflector;
7473
+
7474
+ inflector.pluralize('advice') // => 'advices'
7475
+ inflector.uncountable('advice');
7476
+ inflector.pluralize('advice') // => 'advice'
7477
+
7478
+ inflector.pluralize('formula') // => 'formulas'
7479
+ inflector.irregular('formula', 'formulae');
7480
+ inflector.pluralize('formula') // => 'formulae'
7481
+
7482
+ // you would not need to add these as they are the default rules
7483
+ inflector.plural(/$/, 's');
7484
+ inflector.singular(/s$/i, '');
7485
+ ```
7486
+
7487
+ Creating an inflector with a nondefault ruleset.
7488
+
7489
+ ```javascript
7490
+ var rules = {
7491
+ plurals: [ /$/, 's' ],
7492
+ singular: [ /\s$/, '' ],
7493
+ irregularPairs: [
7494
+ [ 'cow', 'kine' ]
7495
+ ],
7496
+ uncountable: [ 'fish' ]
7497
+ };
7498
+
7499
+ var inflector = new Ember.Inflector(rules);
7500
+ ```
7501
+
7502
+ @class Inflector
7503
+ @namespace Ember
7504
+ */
7505
+ function Inflector(ruleSet) {
7506
+ ruleSet = ruleSet || {};
7507
+ ruleSet.uncountable = ruleSet.uncountable || {};
7508
+ ruleSet.irregularPairs= ruleSet.irregularPairs|| {};
7509
+
7510
+ var rules = this.rules = {
7511
+ plurals: ruleSet.plurals || [],
7512
+ singular: ruleSet.singular || [],
7513
+ irregular: {},
7514
+ irregularInverse: {},
7515
+ uncountable: {}
7516
+ };
7517
+
7518
+ loadUncountable(rules, ruleSet.uncountable);
7519
+ loadIrregular(rules, ruleSet.irregularPairs);
7520
+ }
7521
+
7522
+ Inflector.prototype = {
7523
+ /**
7524
+ @method plural
7525
+ @param {RegExp} regex
7526
+ @param {String} string
7527
+ */
7528
+ plural: function(regex, string) {
7529
+ this.rules.plurals.push([regex, string]);
7530
+ },
7531
+
7532
+ /**
7533
+ @method singular
7534
+ @param {RegExp} regex
7535
+ @param {String} string
7536
+ */
7537
+ singular: function(regex, string) {
7538
+ this.rules.singular.push([regex, string]);
7539
+ },
7540
+
7541
+ /**
7542
+ @method uncountable
7543
+ @param {String} regex
7544
+ */
7545
+ uncountable: function(string) {
7546
+ loadUncountable(this.rules, [string]);
7547
+ },
7548
+
7549
+ /**
7550
+ @method irregular
7551
+ @param {String} singular
7552
+ @param {String} plural
7553
+ */
7554
+ irregular: function (singular, plural) {
7555
+ loadIrregular(this.rules, [[singular, plural]]);
7556
+ },
7557
+
7558
+ /**
7559
+ @method pluralize
7560
+ @param {String} word
7561
+ */
7562
+ pluralize: function(word) {
7563
+ return this.inflect(word, this.rules.plurals, this.rules.irregular);
7564
+ },
7565
+
7566
+ /**
7567
+ @method singularize
7568
+ @param {String} word
7569
+ */
7570
+ singularize: function(word) {
7571
+ return this.inflect(word, this.rules.singular, this.rules.irregularInverse);
7572
+ },
7573
+
7574
+ /**
7575
+ @protected
7576
+
7577
+ @method inflect
7578
+ @param {String} word
7579
+ @param {Object} typeRules
7580
+ @param {Object} irregular
7581
+ */
7582
+ inflect: function(word, typeRules, irregular) {
7583
+ var inflection, substitution, result, lowercase, isBlank,
7584
+ isUncountable, isIrregular, isIrregularInverse, rule;
7585
+
7586
+ isBlank = BLANK_REGEX.test(word);
7587
+
7588
+ if (isBlank) {
7589
+ return word;
7590
+ }
7591
+
7592
+ lowercase = word.toLowerCase();
7593
+
7594
+ isUncountable = this.rules.uncountable[lowercase];
7595
+
7596
+ if (isUncountable) {
7597
+ return word;
7598
+ }
7599
+
7600
+ isIrregular = irregular && irregular[lowercase];
7601
+
7602
+ if (isIrregular) {
7603
+ return isIrregular;
7604
+ }
7605
+
7606
+ for (var i = typeRules.length, min = 0; i > min; i--) {
7607
+ inflection = typeRules[i-1];
7608
+ rule = inflection[0];
7609
+
7610
+ if (rule.test(word)) {
7611
+ break;
7612
+ }
7613
+ }
7614
+
7615
+ inflection = inflection || [];
7616
+
7617
+ rule = inflection[0];
7618
+ substitution = inflection[1];
7619
+
7620
+ result = word.replace(rule, substitution);
7621
+
7622
+ return result;
7623
+ }
7624
+ };
7625
+
7626
+ Ember.Inflector = Inflector;
7627
+
7628
+ })();
7629
+
7630
+
7631
+
7632
+ (function() {
7633
+ Ember.Inflector.defaultRules = {
7634
+ plurals: [
7635
+ [/$/, 's'],
7636
+ [/s$/i, 's'],
7637
+ [/^(ax|test)is$/i, '$1es'],
7638
+ [/(octop|vir)us$/i, '$1i'],
7639
+ [/(octop|vir)i$/i, '$1i'],
7640
+ [/(alias|status)$/i, '$1es'],
7641
+ [/(bu)s$/i, '$1ses'],
7642
+ [/(buffal|tomat)o$/i, '$1oes'],
7643
+ [/([ti])um$/i, '$1a'],
7644
+ [/([ti])a$/i, '$1a'],
7645
+ [/sis$/i, 'ses'],
7646
+ [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'],
7647
+ [/(hive)$/i, '$1s'],
7648
+ [/([^aeiouy]|qu)y$/i, '$1ies'],
7649
+ [/(x|ch|ss|sh)$/i, '$1es'],
7650
+ [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'],
7651
+ [/^(m|l)ouse$/i, '$1ice'],
7652
+ [/^(m|l)ice$/i, '$1ice'],
7653
+ [/^(ox)$/i, '$1en'],
7654
+ [/^(oxen)$/i, '$1'],
7655
+ [/(quiz)$/i, '$1zes']
7656
+ ],
7657
+
7658
+ singular: [
7659
+ [/s$/i, ''],
7660
+ [/(ss)$/i, '$1'],
7661
+ [/(n)ews$/i, '$1ews'],
7662
+ [/([ti])a$/i, '$1um'],
7663
+ [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'],
7664
+ [/(^analy)(sis|ses)$/i, '$1sis'],
7665
+ [/([^f])ves$/i, '$1fe'],
7666
+ [/(hive)s$/i, '$1'],
7667
+ [/(tive)s$/i, '$1'],
7668
+ [/([lr])ves$/i, '$1f'],
7669
+ [/([^aeiouy]|qu)ies$/i, '$1y'],
7670
+ [/(s)eries$/i, '$1eries'],
7671
+ [/(m)ovies$/i, '$1ovie'],
7672
+ [/(x|ch|ss|sh)es$/i, '$1'],
7673
+ [/^(m|l)ice$/i, '$1ouse'],
7674
+ [/(bus)(es)?$/i, '$1'],
7675
+ [/(o)es$/i, '$1'],
7676
+ [/(shoe)s$/i, '$1'],
7677
+ [/(cris|test)(is|es)$/i, '$1is'],
7678
+ [/^(a)x[ie]s$/i, '$1xis'],
7679
+ [/(octop|vir)(us|i)$/i, '$1us'],
7680
+ [/(alias|status)(es)?$/i, '$1'],
7681
+ [/^(ox)en/i, '$1'],
7682
+ [/(vert|ind)ices$/i, '$1ex'],
7683
+ [/(matr)ices$/i, '$1ix'],
7684
+ [/(quiz)zes$/i, '$1'],
7685
+ [/(database)s$/i, '$1']
7686
+ ],
7687
+
7688
+ irregularPairs: [
7689
+ ['person', 'people'],
7690
+ ['man', 'men'],
7691
+ ['child', 'children'],
7692
+ ['sex', 'sexes'],
7693
+ ['move', 'moves'],
7694
+ ['cow', 'kine'],
7695
+ ['zombie', 'zombies']
7696
+ ],
7697
+
7698
+ uncountable: [
7699
+ 'equipment',
7700
+ 'information',
7701
+ 'rice',
7702
+ 'money',
7703
+ 'species',
7704
+ 'series',
7705
+ 'fish',
7706
+ 'sheep',
7707
+ 'jeans',
7708
+ 'police'
7709
+ ]
7710
+ };
7711
+
7712
+ })();
7713
+
7714
+
7715
+
7716
+ (function() {
7717
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
7718
+ /**
7719
+ See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}}
7720
+
7721
+ @method pluralize
7722
+ @for String
7723
+ */
7724
+ String.prototype.pluralize = function() {
7725
+ return Ember.String.pluralize(this);
7726
+ };
7727
+
7728
+ /**
7729
+ See {{#crossLink "Ember.String/singularize"}}{{/crossLink}}
7730
+
7731
+ @method singularize
7732
+ @for String
7733
+ */
7734
+ String.prototype.singularize = function() {
7735
+ return Ember.String.singularize(this);
7736
+ };
7737
+ }
7738
+
7739
+ })();
7740
+
7741
+
7742
+
7743
+ (function() {
7744
+ Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
7745
+
7746
+ })();
7747
+
7748
+
7749
+
7750
+ (function() {
7751
+
7752
+ })();
7753
+
7754
+ (function() {
7755
+ /**
7756
+ @module ember-data
7757
+ */
7758
+
7759
+ var get = Ember.get;
7760
+ var forEach = Ember.EnumerableUtils.forEach;
7761
+
7762
+ DS.ActiveModelSerializer = DS.RESTSerializer.extend({
7763
+ // SERIALIZE
7764
+
7765
+ /**
7766
+ Converts camelcased attributes to underscored when serializing.
7767
+
7768
+ @method keyForAttribute
7769
+ @param {String} attribute
7770
+ @returns String
7771
+ */
7772
+ keyForAttribute: function(attr) {
7773
+ return Ember.String.decamelize(attr);
7774
+ },
7775
+
7776
+ /**
7777
+ Underscores relationship names and appends "_id" or "_ids" when serializing
7778
+ relationship keys.
7779
+
7780
+ @method keyForRelationship
7781
+ @param {String} key
7782
+ @param {String} kind
7783
+ @returns String
7784
+ */
7785
+ keyForRelationship: function(key, kind) {
7786
+ key = Ember.String.decamelize(key);
7787
+ if (kind === "belongsTo") {
7788
+ return key + "_id";
7789
+ } else if (kind === "hasMany") {
7790
+ return Ember.String.singularize(key) + "_ids";
7791
+ } else {
7792
+ return key;
7793
+ }
7794
+ },
7795
+
7796
+ /**
7797
+ Serialize has-may relationship when it is configured as embedded objects.
7798
+
7799
+ @method serializeHasMany
7800
+ */
7801
+ serializeHasMany: function(record, json, relationship) {
7802
+ var key = relationship.key,
7803
+ attrs = get(this, 'attrs'),
7804
+ embed = attrs && attrs[key] && attrs[key].embedded === 'always';
7805
+
7806
+ if (embed) {
7807
+ json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
7808
+ var data = relation.serialize(),
7809
+ primaryKey = get(this, 'primaryKey');
7810
+
7811
+ data[primaryKey] = get(relation, primaryKey);
7812
+
7813
+ return data;
7814
+ }, this);
7815
+ }
7816
+ },
7817
+
7818
+ /**
7819
+ Underscores the JSON root keys when serializing.
7820
+
7821
+ @method serializeIntoHash
7822
+ @param {Object} hash
7823
+ @param {subclass of DS.Model} type
7824
+ @param {DS.Model} record
7825
+ @param {Object} options
7826
+ */
7827
+ serializeIntoHash: function(data, type, record, options) {
7828
+ var root = Ember.String.decamelize(type.typeKey);
7829
+ data[root] = this.serialize(record, options);
7830
+ },
7831
+
7832
+ /**
7833
+ Serializes a polymorphic type as a fully capitalized model name.
7834
+
7835
+ @method serializePolymorphicType
7836
+ @param {DS.Model} record
7837
+ @param {Object} json
7838
+ @param relationship
7839
+ */
7840
+ serializePolymorphicType: function(record, json, relationship) {
7841
+ var key = relationship.key,
7842
+ belongsTo = get(record, key);
7843
+ key = this.keyForAttribute(key);
7844
+ json[key + "_type"] = Ember.String.capitalize(belongsTo.constructor.typeKey);
7845
+ },
7846
+
7847
+ // EXTRACT
7848
+
7849
+ /**
7850
+ Extracts the model typeKey from underscored root objects.
7851
+
7852
+ @method typeForRoot
7853
+ @param {String} root
7854
+ @returns String the model's typeKey
7855
+ */
7856
+ typeForRoot: function(root) {
7857
+ var camelized = Ember.String.camelize(root);
7858
+ return Ember.String.singularize(camelized);
7859
+ },
7860
+
7861
+ /**
7862
+ Normalize the polymorphic type from the JSON.
7863
+
7864
+ Normalize:
7865
+ ```js
7866
+ {
7867
+ id: "1"
7868
+ minion: { type: "evil_minion", id: "12"}
7869
+ }
7870
+ ```
7871
+
7872
+ To:
7873
+ ```js
7874
+ {
7875
+ id: "1"
7876
+ minion: { type: "evilMinion", id: "12"}
7877
+ }
7878
+ ```
7879
+
7880
+ @method normalizeRelationships
7881
+ @private
7882
+ */
7883
+ normalizeRelationships: function(type, hash) {
7884
+ var payloadKey, payload;
7885
+
7886
+ if (this.keyForRelationship) {
7887
+ type.eachRelationship(function(key, relationship) {
7888
+ if (relationship.options.polymorphic) {
7889
+ payloadKey = this.keyForAttribute(key);
7890
+ payload = hash[payloadKey];
7891
+ if (payload && payload.type) {
7892
+ payload.type = this.typeForRoot(payload.type);
7893
+ }
7894
+ } else {
7895
+ payloadKey = this.keyForRelationship(key, relationship.kind);
7896
+ payload = hash[payloadKey];
7897
+ }
7898
+
7899
+ hash[key] = payload;
7900
+
7901
+ if (key !== payloadKey) {
7902
+ delete hash[payloadKey];
7903
+ }
7904
+ }, this);
7905
+ }
7906
+ },
7907
+
7908
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
7909
+ var root = this.keyForAttribute(primaryType.typeKey),
7910
+ partial = payload[root];
7911
+
7912
+ updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
7913
+
7914
+ return this._super(store, primaryType, payload, recordId, requestType);
7915
+ },
7916
+
7917
+ extractArray: function(store, type, payload) {
7918
+ var root = this.keyForAttribute(type.typeKey),
7919
+ partials = payload[Ember.String.pluralize(root)];
7920
+
7921
+ forEach(partials, function(partial) {
7922
+ updatePayloadWithEmbedded(store, this, type, partial, payload);
7923
+ }, this);
7924
+
7925
+ return this._super(store, type, payload);
7926
+ }
7927
+ });
7928
+
7929
+ function updatePayloadWithEmbedded(store, serializer, type, partial, payload) {
7930
+ var attrs = get(serializer, 'attrs');
7931
+
7932
+ if (!attrs) {
7933
+ return;
7934
+ }
7935
+
7936
+ type.eachRelationship(function(key, relationship) {
7937
+ var expandedKey, embeddedTypeKey, attribute, ids,
7938
+ config = attrs[key],
7939
+ serializer = store.serializerFor(relationship.type.typeKey),
7940
+ primaryKey = get(serializer, "primaryKey");
7941
+
7942
+ if (relationship.kind !== "hasMany") {
7943
+ return;
7944
+ }
7945
+
7946
+ if (config && (config.embedded === 'always' || config.embedded === 'load')) {
7947
+ // underscore forces the embedded records to be side loaded.
7948
+ // it is needed when main type === relationship.type
7949
+ embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey);
7950
+ expandedKey = this.keyForRelationship(key, relationship.kind);
7951
+ attribute = this.keyForAttribute(key);
7952
+ ids = [];
7953
+
7954
+ if (!partial[attribute]) {
7955
+ return;
7956
+ }
7957
+
7958
+ payload[embeddedTypeKey] = payload[embeddedTypeKey] || [];
7959
+
7960
+ forEach(partial[attribute], function(data) {
7961
+ ids.push(data[primaryKey]);
7962
+ payload[embeddedTypeKey].push(data);
7963
+ });
7964
+
7965
+ partial[expandedKey] = ids;
7966
+ delete partial[attribute];
7967
+ }
7968
+ }, serializer);
7969
+ }
7970
+
7971
+ })();
7972
+
7973
+
7974
+
7975
+ (function() {
7976
+ /**
7977
+ @module ember-data
7978
+ */
7979
+
7980
+ var forEach = Ember.EnumerableUtils.forEach;
7981
+
7982
+ /**
7983
+ The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate
7984
+ with a JSON API that uses an underscored naming convention instead of camelcasing.
7985
+ It has been designed to work out of the box with the
7986
+ [active_model_serializers](http://github.com/rails-api/active_model_serializers)
7987
+ Ruby gem.
7988
+
7989
+ ## JSON Structure
7990
+
7991
+ The ActiveModelAdapter expects the JSON returned from your server to follow
7992
+ the REST adapter conventions substituting underscored keys for camelcased ones.
7993
+
7994
+ ### Conventional Names
7995
+
7996
+ Attribute names in your JSON payload should be the underscored versions of
7997
+ the attributes in your Ember.js models.
7998
+
7999
+ For example, if you have a `Person` model:
8000
+
8001
+ ```js
8002
+ App.FamousPerson = DS.Model.extend({
8003
+ firstName: DS.attr('string'),
8004
+ lastName: DS.attr('string'),
8005
+ occupation: DS.attr('string')
8006
+ });
8007
+ ```
8008
+
8009
+ The JSON returned should look like this:
8010
+
8011
+ ```js
8012
+ {
8013
+ "famous_person": {
8014
+ "first_name": "Barack",
8015
+ "last_name": "Obama",
8016
+ "occupation": "President"
8017
+ }
8018
+ }
8019
+ ```
8020
+
8021
+ @class ActiveModelAdapter
8022
+ @constructor
8023
+ @namespace DS
8024
+ @extends DS.Adapter
8025
+ **/
8026
+
8027
+ DS.ActiveModelAdapter = DS.RESTAdapter.extend({
8028
+ defaultSerializer: '_ams',
8029
+ /**
8030
+ The ActiveModelAdapter overrides the `pathForType` method
8031
+ to build underscored URLs.
8032
+
8033
+ ```js
8034
+ this.pathForType("famousPerson");
8035
+ //=> "famous_people"
8036
+ ```
8037
+
8038
+ @method pathForType
8039
+ @param {String} type
8040
+ @returns String
8041
+ */
8042
+ pathForType: function(type) {
8043
+ var decamelized = Ember.String.decamelize(type);
8044
+ return Ember.String.pluralize(decamelized);
8045
+ },
8046
+
8047
+ /**
8048
+ The ActiveModelAdapter overrides the `ajaxError` method
8049
+ to return a DS.InvalidError for all 422 Unprocessable Entity
8050
+ responses.
8051
+
8052
+ @method ajaxError
8053
+ @param jqXHR
8054
+ @returns error
8055
+ */
8056
+ ajaxError: function(jqXHR) {
8057
+ var error = this._super(jqXHR);
8058
+
8059
+ if (jqXHR && jqXHR.status === 422) {
8060
+ var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"],
8061
+ errors = {};
8062
+
8063
+ forEach(Ember.keys(jsonErrors), function(key) {
8064
+ errors[Ember.String.camelize(key)] = jsonErrors[key];
8065
+ });
8066
+
8067
+ return new DS.InvalidError(errors);
8068
+ } else {
8069
+ return error;
8070
+ }
8071
+ }
8072
+ });
8073
+
8074
+ })();
8075
+
8076
+
8077
+
8078
+ (function() {
8079
+
8080
+ })();
8081
+
8082
+
8083
+
8084
+ (function() {
8085
+ Ember.onLoad('Ember.Application', function(Application) {
8086
+ Application.initializer({
8087
+ name: "activeModelAdapter",
8088
+
8089
+ initialize: function(container, application) {
8090
+ application.register('serializer:_ams', DS.ActiveModelSerializer);
8091
+ application.register('adapter:_ams', DS.ActiveModelAdapter);
8092
+ }
8093
+ });
8094
+ });
8095
+
8096
+ })();
8097
+
8098
+
8099
+
8100
+ (function() {
8101
+
8102
+ })();
8103
+
8104
+
8105
+ })();