ember-data-source 1.0.0.beta.2 → 1.0.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ab50c82ea065fb1d9eb982e0fde381caa4184de
4
- data.tar.gz: 4f52807164f630f58e6144e3a11bc41e09b2c26c
3
+ metadata.gz: 795cc179a07951267a58b778b774cc94d20337e7
4
+ data.tar.gz: a29df0c6cab5e428cfe02e0f2c170aef0a277c4e
5
5
  SHA512:
6
- metadata.gz: 483401b7507bc40fc841672dbf04d3cad28886cae33f342957bb77bfe0ab654d53d0ec21869da0426aa15907c8a028a65e7aa7a14fcea999644287fb50ce3111
7
- data.tar.gz: b4a40bc1be9f8fcc995b2e5a99710ce824dad66b345f9cfd5f16e82687f3a99bf84971817e88565ef7473843ac4e80066e0cbb1f89a968c00d2d5607f6e76dbf
6
+ metadata.gz: 7ad91411d815db80f3311bbb917bfd7de5fc2423e9bd0f5d5531f2babdfac8798d3855edeb46ad4bfc695d7da7202c73a49c349d8f2d48f10bffcf46a11111a7
7
+ data.tar.gz: 447131c2a03505bf4c087b86d5aba7c7f55390b2ccd698cc89c51abadebaf940f06f7e136b86e8c2198f59558366094d6691ef914dd6e58948f2c30b9da9a962
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0-beta.2
1
+ 1.0.0-beta.3
@@ -1,5 +1,14 @@
1
- // Version: v1.0.0-beta.2
2
- // Last commit: d1158e2 (2013-09-04 16:03:06 -0700)
1
+ // ==========================================================================
2
+ // Project: Ember Data
3
+ // Copyright: Copyright 2011-2013 Tilde Inc. and contributors.
4
+ // Portions Copyright 2011 LivingSocial Inc.
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+
8
+
9
+
10
+ // Version: v1.0.0-beta.3-48-gb47afef
11
+ // Last commit: b47afef (2013-10-08 19:07:59 -0700)
3
12
 
4
13
 
5
14
  (function() {
@@ -55,12 +64,16 @@ var define, requireModule;
55
64
 
56
65
  if ('undefined' === typeof DS) {
57
66
  DS = Ember.Namespace.create({
58
- VERSION: '1.0.0-beta.1'
67
+ VERSION: '1.0.0-beta.2'
59
68
  });
60
69
 
61
70
  if ('undefined' !== typeof window) {
62
71
  window.DS = DS;
63
72
  }
73
+
74
+ if (Ember.libraries) {
75
+ Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION);
76
+ }
64
77
  }
65
78
  })();
66
79
 
@@ -99,8 +112,6 @@ DS.JSONSerializer = Ember.Object.extend({
99
112
  // SERIALIZE
100
113
 
101
114
  serialize: function(record, options) {
102
- var store = get(this, 'store');
103
-
104
115
  var json = {};
105
116
 
106
117
  if (options && options.includeId) {
@@ -137,7 +148,7 @@ DS.JSONSerializer = Ember.Object.extend({
137
148
 
138
149
  // if provided, use the mapping provided by `attrs` in
139
150
  // the serializer
140
- key = attrs && attrs[key] || key;
151
+ key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key);
141
152
 
142
153
  json[key] = value;
143
154
  },
@@ -147,12 +158,16 @@ DS.JSONSerializer = Ember.Object.extend({
147
158
 
148
159
  var belongsTo = get(record, key);
149
160
 
150
- if (isNone(belongsTo)) { return; }
161
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
151
162
 
152
- json[key] = get(belongsTo, 'id');
163
+ if (isNone(belongsTo)) {
164
+ json[key] = belongsTo;
165
+ } else {
166
+ json[key] = get(belongsTo, 'id');
167
+ }
153
168
 
154
169
  if (relationship.options.polymorphic) {
155
- json[key + "_type"] = belongsTo.constructor.typeKey;
170
+ this.serializePolymorphicType(record, json, relationship);
156
171
  }
157
172
  },
158
173
 
@@ -167,6 +182,11 @@ DS.JSONSerializer = Ember.Object.extend({
167
182
  }
168
183
  },
169
184
 
185
+ /**
186
+ You can use this method to customize how polymorphic objects are serialized.
187
+ */
188
+ serializePolymorphicType: Ember.K,
189
+
170
190
  // EXTRACT
171
191
 
172
192
  extract: function(store, type, payload, id, requestType) {
@@ -186,6 +206,7 @@ DS.JSONSerializer = Ember.Object.extend({
186
206
  extractDeleteRecord: aliasMethod('extractSave'),
187
207
 
188
208
  extractFind: aliasMethod('extractSingle'),
209
+ extractFindBelongsTo: aliasMethod('extractSingle'),
189
210
  extractSave: aliasMethod('extractSingle'),
190
211
 
191
212
  extractSingle: function(store, type, payload) {
@@ -205,16 +226,10 @@ DS.JSONSerializer = Ember.Object.extend({
205
226
 
206
227
  // HELPERS
207
228
 
208
- typeFor: function(relationship, key, data) {
209
- if (relationship.options.polymorphic) {
210
- return data[key + "_type"];
211
- } else {
212
- return relationship.type;
213
- }
214
- },
215
-
216
- transformFor: function(attributeType) {
217
- return this.container.lookup('transform:' + attributeType);
229
+ transformFor: function(attributeType, skipAssertion) {
230
+ var transform = this.container.lookup('transform:' + attributeType);
231
+ Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform);
232
+ return transform;
218
233
  }
219
234
  });
220
235
 
@@ -230,6 +245,11 @@ var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.St
230
245
 
231
246
  /**
232
247
  Extend `Ember.DataAdapter` with ED specific code.
248
+
249
+ @class DebugAdapter
250
+ @namespace DS
251
+ @extends Ember.DataAdapter
252
+ @private
233
253
  */
234
254
  DS.DebugAdapter = Ember.DataAdapter.extend({
235
255
  getFilters: function() {
@@ -246,7 +266,7 @@ DS.DebugAdapter = Ember.DataAdapter.extend({
246
266
 
247
267
  columnsForType: function(type) {
248
268
  var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this;
249
- Ember.A(get(type, 'attributes')).forEach(function(name, meta) {
269
+ get(type, 'attributes').forEach(function(name, meta) {
250
270
  if (count++ > self.attributeLimit) { return false; }
251
271
  var desc = capitalize(underscore(name).replace('_', ' '));
252
272
  columns.push({ name: name, desc: desc });
@@ -442,7 +462,7 @@ DS.NumberTransform = DS.Transform.extend({
442
462
 
443
463
 
444
464
  (function() {
445
- var none = Ember.isNone, empty = Ember.isEmpty;
465
+ var none = Ember.isNone;
446
466
 
447
467
  DS.StringTransform = DS.Transform.extend({
448
468
 
@@ -455,6 +475,7 @@ DS.StringTransform = DS.Transform.extend({
455
475
  }
456
476
 
457
477
  });
478
+
458
479
  })();
459
480
 
460
481
 
@@ -688,6 +709,14 @@ DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, {
688
709
 
689
710
  removeRecord: function(record) {
690
711
  get(this, 'content').removeObject(record);
712
+ },
713
+
714
+ save: function() {
715
+ var promise = Ember.RSVP.all(this.invoke("save")).then(function(array) {
716
+ return Ember.A(array);
717
+ });
718
+
719
+ return DS.PromiseArray.create({ promise: promise });
691
720
  }
692
721
  });
693
722
 
@@ -749,11 +778,13 @@ DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
749
778
  load: function(data) {
750
779
  var store = get(this, 'store'),
751
780
  type = get(this, 'type'),
752
- records = store.pushMany(type, data);
781
+ records = store.pushMany(type, data),
782
+ meta = store.metadataFor(type);
753
783
 
754
784
  this.setProperties({
755
785
  content: Ember.A(records),
756
- isLoaded: true
786
+ isLoaded: true,
787
+ meta: meta
757
788
  });
758
789
 
759
790
  // TODO: does triggering didLoad event should be the last action of the runLoop?
@@ -787,11 +818,11 @@ var map = Ember.EnumerableUtils.map;
787
818
  defined:
788
819
 
789
820
  App.Post = DS.Model.extend({
790
- comments: DS.hasMany('App.Comment')
821
+ comments: DS.hasMany('comment')
791
822
  });
792
823
 
793
824
  App.Comment = DS.Model.extend({
794
- post: DS.belongsTo('App.Post')
825
+ post: DS.belongsTo('post')
795
826
  });
796
827
 
797
828
  If you created a new instance of `App.Post` and added
@@ -848,10 +879,11 @@ DS.ManyArray = DS.RecordArray.extend({
848
879
  fetch: function() {
849
880
  var records = get(this, 'content'),
850
881
  store = get(this, 'store'),
851
- owner = get(this, 'owner');
882
+ owner = get(this, 'owner'),
883
+ resolver = Ember.RSVP.defer();
852
884
 
853
885
  var unloadedRecords = records.filterProperty('isEmpty', true);
854
- store.fetchMany(unloadedRecords, owner);
886
+ store.fetchMany(unloadedRecords, owner, resolver);
855
887
  },
856
888
 
857
889
  // Overrides Ember.Array's replace method to implement
@@ -866,7 +898,7 @@ DS.ManyArray = DS.RecordArray.extend({
866
898
  },
867
899
 
868
900
  arrangedContentDidChange: function() {
869
- this.fetch();
901
+ Ember.run.once(this, 'fetch');
870
902
  },
871
903
 
872
904
  arrayContentWillChange: function(index, removed, added) {
@@ -1136,7 +1168,6 @@ var isNone = Ember.isNone;
1136
1168
  var forEach = Ember.EnumerableUtils.forEach;
1137
1169
  var indexOf = Ember.EnumerableUtils.indexOf;
1138
1170
  var map = Ember.EnumerableUtils.map;
1139
- var OrderedSet = Ember.OrderedSet;
1140
1171
  var resolve = Ember.RSVP.resolve;
1141
1172
 
1142
1173
  // Implementors Note:
@@ -1347,7 +1378,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1347
1378
  @returns String if the adapter can generate one, an ID
1348
1379
  */
1349
1380
  _generateId: function(type) {
1350
- var adapter = this.adapterForType(type);
1381
+ var adapter = this.adapterFor(type);
1351
1382
 
1352
1383
  if (adapter && adapter.generateIdForRecord) {
1353
1384
  return adapter.generateIdForRecord(this);
@@ -1451,12 +1482,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1451
1482
  findById: function(type, id) {
1452
1483
  type = this.modelFor(type);
1453
1484
 
1454
- var record = this.getById(type, id);
1455
- if (get(record, 'isEmpty')) {
1456
- return promiseObject(this.fetchRecord(record));
1457
- } else {
1458
- return promiseObject(resolve(record));
1459
- }
1485
+ var record = this.recordForId(type, id);
1486
+
1487
+ var promise = this.fetchRecord(record) || resolve(record);
1488
+ return promiseObject(promise);
1460
1489
  },
1461
1490
 
1462
1491
  /**
@@ -1489,13 +1518,17 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1489
1518
  @returns Promise
1490
1519
  */
1491
1520
  fetchRecord: function(record) {
1521
+ if (isNone(record)) { return null; }
1522
+ if (record._loadingPromise) { return record._loadingPromise; }
1523
+ if (!get(record, 'isEmpty')) { return null; }
1524
+
1492
1525
  var type = record.constructor,
1493
1526
  id = get(record, 'id'),
1494
1527
  resolver = Ember.RSVP.defer();
1495
1528
 
1496
- record.loadingData();
1529
+ record.loadingData(resolver.promise);
1497
1530
 
1498
- var adapter = this.adapterForType(type);
1531
+ var adapter = this.adapterFor(type);
1499
1532
 
1500
1533
  Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter);
1501
1534
  Ember.assert("You tried to find a record but your adapter (for " + type + ") does not implement 'find'", adapter.find);
@@ -1509,7 +1542,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1509
1542
  Get a record by a given type and ID without triggering a fetch.
1510
1543
 
1511
1544
  This method will synchronously return the record if it's available.
1512
- Otherwise, it will return undefined.
1545
+ Otherwise, it will return null.
1513
1546
 
1514
1547
  ```js
1515
1548
  var post = store.getById('post', 1);
@@ -1525,7 +1558,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1525
1558
  if (this.hasRecordForId(type, id)) {
1526
1559
  return this.recordForId(type, id);
1527
1560
  } else {
1528
- return this.buildRecord(type, id);
1561
+ return null;
1529
1562
  }
1530
1563
  },
1531
1564
 
@@ -1544,8 +1577,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1544
1577
  */
1545
1578
  reloadRecord: function(record, resolver) {
1546
1579
  var type = record.constructor,
1547
- adapter = this.adapterForType(type),
1548
- store = this,
1580
+ adapter = this.adapterFor(type),
1549
1581
  id = get(record, 'id');
1550
1582
 
1551
1583
  Ember.assert("You cannot reload a record without an ID", id);
@@ -1586,7 +1618,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1586
1618
 
1587
1619
  forEach(recordsByTypeMap, function(type, records) {
1588
1620
  var ids = records.mapProperty('id'),
1589
- adapter = this.adapterForType(type);
1621
+ adapter = this.adapterFor(type);
1590
1622
 
1591
1623
  Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
1592
1624
  Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany);
@@ -1598,7 +1630,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1598
1630
  /**
1599
1631
  Returns true if a record for a given type and ID is already loaded.
1600
1632
 
1601
- @param {String} type
1633
+ @method hasRecordForId
1634
+ @param {DS.Model} type
1602
1635
  @param {String|Integer} id
1603
1636
  @returns Boolean
1604
1637
  */
@@ -1612,6 +1645,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1612
1645
  Returns id record for a given type and ID. If one isn't already loaded,
1613
1646
  it builds a new record and leaves it in the `empty` state.
1614
1647
 
1648
+ @method recordForId
1615
1649
  @param {String} type
1616
1650
  @param {String|Integer} id
1617
1651
  @returns DS.Model
@@ -1679,6 +1713,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1679
1713
  The usual use-case is for the server to register a URL as a link, and
1680
1714
  then use that URL in the future to make a request for the relationship.
1681
1715
 
1716
+ @method findHasMany
1682
1717
  @private
1683
1718
  @param {DS.Model} owner
1684
1719
  @param {any} link
@@ -1687,7 +1722,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1687
1722
  @return DS.ManyArray
1688
1723
  */
1689
1724
  findHasMany: function(owner, link, relationship, resolver) {
1690
- var adapter = this.adapterForType(owner.constructor);
1725
+ var adapter = this.adapterFor(owner.constructor);
1691
1726
 
1692
1727
  Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter);
1693
1728
  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);
@@ -1697,6 +1732,15 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1697
1732
  return records;
1698
1733
  },
1699
1734
 
1735
+ findBelongsTo: function(owner, link, relationship, resolver) {
1736
+ var adapter = this.adapterFor(owner.constructor);
1737
+
1738
+ Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter);
1739
+ 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);
1740
+
1741
+ _findBelongsTo(adapter, this, owner, link, relationship, resolver);
1742
+ },
1743
+
1700
1744
  /**
1701
1745
  This method delegates a query to the adapter. This is the one place where
1702
1746
  adapter-level semantics are exposed to the application.
@@ -1724,7 +1768,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1724
1768
  store: this
1725
1769
  });
1726
1770
 
1727
- var adapter = this.adapterForType(type),
1771
+ var adapter = this.adapterFor(type),
1728
1772
  resolver = Ember.RSVP.defer();
1729
1773
 
1730
1774
  Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter);
@@ -1759,7 +1803,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1759
1803
  @returns Promise
1760
1804
  */
1761
1805
  fetchAll: function(type, array) {
1762
- var adapter = this.adapterForType(type),
1806
+ var adapter = this.adapterFor(type),
1763
1807
  sinceToken = this.typeMapFor(type).metadata.since,
1764
1808
  resolver = Ember.RSVP.defer();
1765
1809
 
@@ -1817,6 +1861,24 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1817
1861
  return array;
1818
1862
  },
1819
1863
 
1864
+
1865
+ /**
1866
+ This method unloads all of the known records for a given type.
1867
+
1868
+ @method unloadAll
1869
+ @param {Class} type
1870
+ */
1871
+ unloadAll: function(type) {
1872
+ type = this.modelFor(type);
1873
+
1874
+ var typeMap = this.typeMapFor(type),
1875
+ records = typeMap.records, record;
1876
+
1877
+ while(record = records.pop()) {
1878
+ record.unloadRecord();
1879
+ }
1880
+ },
1881
+
1820
1882
  /**
1821
1883
  Takes a type and filter function, and returns a live RecordArray that
1822
1884
  remains up to date as new records are loaded into the store or created
@@ -1833,14 +1895,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1833
1895
  filter function will be invoked again to determine whether it should
1834
1896
  still be in the array.
1835
1897
 
1836
- Note that the existence of a filter on a type will trigger immediate
1837
- materialization of all loaded data for a given type, so you might
1838
- not want to use filters for a type if you are loading many records
1839
- into the store, many of which are not active at any given time.
1840
-
1841
- In this scenario, you might want to consider filtering the raw
1842
- data before loading it into the store.
1843
-
1844
1898
  @method filter
1845
1899
  @param {Class} type
1846
1900
  @param {Function} filter
@@ -1890,6 +1944,18 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1890
1944
  return !get(this.recordForId(type, id), 'isEmpty');
1891
1945
  },
1892
1946
 
1947
+ /**
1948
+ This method returns the metadata for a specific type.
1949
+
1950
+ @method metadataFor
1951
+ @param {string} type
1952
+ @return {object}
1953
+ */
1954
+ metadataFor: function(type) {
1955
+ type = this.modelFor(type);
1956
+ return this.typeMapFor(type).metadata;
1957
+ },
1958
+
1893
1959
  // ............
1894
1960
  // . UPDATING .
1895
1961
  // ............
@@ -1959,7 +2025,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1959
2025
 
1960
2026
  forEach(pending, function(tuple) {
1961
2027
  var record = tuple[0], resolver = tuple[1],
1962
- adapter = this.adapterForType(record.constructor),
2028
+ adapter = this.adapterFor(record.constructor),
1963
2029
  operation;
1964
2030
 
1965
2031
  if (get(record, 'isNew')) {
@@ -1989,6 +2055,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1989
2055
  */
1990
2056
  didSaveRecord: function(record, data) {
1991
2057
  if (data) {
2058
+ // normalize relationship IDs into records
2059
+ data = normalizeRelationships(this, record.constructor, data, record);
2060
+
1992
2061
  this.updateId(record, data);
1993
2062
  }
1994
2063
 
@@ -2082,7 +2151,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2082
2151
  @param {DS.Model} type
2083
2152
  @param {Object} data
2084
2153
  @param {Boolean} partial the data should be merged into
2085
- the existing fata, not replace it.
2154
+ the existing data, not replace it.
2086
2155
  */
2087
2156
  _load: function(type, data, partial) {
2088
2157
  var id = coerceId(data.id),
@@ -2099,21 +2168,23 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2099
2168
  methods that take a type key (like `find`, `createRecord`,
2100
2169
  etc.)
2101
2170
 
2102
- @param {String} key
2171
+ @method modelFor
2172
+ @param {String or subclass of DS.Model} key
2103
2173
  @returns {subclass of DS.Model}
2104
2174
  */
2105
2175
  modelFor: function(key) {
2106
- if (typeof key !== 'string') {
2107
- return key;
2108
- }
2109
-
2110
- var factory = this.container.lookupFactory('model:'+key);
2176
+ var factory;
2111
2177
 
2112
- Ember.assert("No model was found for '" + key + "'", factory);
2178
+ if (typeof key === 'string') {
2179
+ factory = this.container.lookupFactory('model:' + key);
2180
+ Ember.assert("No model was found for '" + key + "'", factory);
2181
+ factory.typeKey = key;
2182
+ } else {
2183
+ // A factory already supplied.
2184
+ factory = key;
2185
+ }
2113
2186
 
2114
2187
  factory.store = this;
2115
- factory.typeKey = key;
2116
-
2117
2188
  return factory;
2118
2189
  },
2119
2190
 
@@ -2183,7 +2254,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2183
2254
  // If passed, it means that the data should be
2184
2255
  // merged into the existing data, not replace it.
2185
2256
 
2186
- var serializer = this.serializerFor(type);
2257
+ Ember.assert("You must include an `id` in a hash passed to `push`", data.id != null);
2258
+
2187
2259
  type = this.modelFor(type);
2188
2260
 
2189
2261
  // normalize relationship IDs into records
@@ -2194,7 +2266,47 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2194
2266
  return this.recordForId(type, data.id);
2195
2267
  },
2196
2268
 
2269
+ /**
2270
+ Push some raw data into the store.
2271
+
2272
+ The data will be automatically deserialized using the
2273
+ serializer for the `type` param.
2274
+
2275
+ This method can be used both to push in brand new
2276
+ records, as well as to update existing records.
2277
+
2278
+ You can push in more than one type of object at once.
2279
+ All objects should be in the format expected by the
2280
+ serializer.
2281
+
2282
+ ```js
2283
+ App.ApplicationSerializer = DS.ActiveModelSerializer;
2284
+
2285
+ var pushData = {
2286
+ posts: [
2287
+ {id: 1, post_title: "Great post", comment_ids: [2]}
2288
+ ],
2289
+ comments: [
2290
+ {id: 2, comment_body: "Insightful comment"}
2291
+ ]
2292
+ }
2293
+
2294
+ store.pushPayload('post', pushData);
2295
+ ```
2296
+
2297
+ @method push
2298
+ @param {String} type
2299
+ @param {Object} payload
2300
+ */
2301
+
2302
+ pushPayload: function (type, payload) {
2303
+ var serializer = this.serializerFor(type);
2304
+ serializer.pushPayload(this, payload);
2305
+ },
2306
+
2197
2307
  update: function(type, data) {
2308
+ Ember.assert("You must include an `id` in a hash passed to `update`", data.id != null);
2309
+
2198
2310
  return this.push(type, data, true);
2199
2311
  },
2200
2312
 
@@ -2245,9 +2357,12 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2245
2357
 
2246
2358
  Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]);
2247
2359
 
2360
+ // lookupFactory should really return an object that creates
2361
+ // instances with the injections applied
2248
2362
  var record = type._create({
2249
2363
  id: id,
2250
2364
  store: this,
2365
+ container: this.container
2251
2366
  });
2252
2367
 
2253
2368
  if (data) {
@@ -2350,12 +2465,12 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2350
2465
  /**
2351
2466
  Returns the adapter for a given type.
2352
2467
 
2353
- @method adapterForType
2468
+ @method adapterFor
2354
2469
  @private
2355
2470
  @param {subclass of DS.Model} type
2356
2471
  @returns DS.Adapter
2357
2472
  */
2358
- adapterForType: function(type) {
2473
+ adapterFor: function(type) {
2359
2474
  var container = this.container, adapter;
2360
2475
 
2361
2476
  if (container) {
@@ -2387,29 +2502,31 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2387
2502
  */
2388
2503
  serializerFor: function(type) {
2389
2504
  type = this.modelFor(type);
2390
- var adapter = this.adapterForType(type);
2505
+ var adapter = this.adapterFor(type);
2391
2506
 
2392
2507
  return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer);
2393
2508
  }
2394
2509
  });
2395
2510
 
2396
- function normalizeRelationships(store, type, data) {
2511
+ function normalizeRelationships(store, type, data, record) {
2397
2512
  type.eachRelationship(function(key, relationship) {
2398
2513
  // A link (usually a URL) was already provided in
2399
2514
  // normalized form
2400
2515
  if (data.links && data.links[key]) {
2516
+ if (record && relationship.options.async) { record._relationships[key] = null; }
2401
2517
  return;
2402
2518
  }
2403
2519
 
2404
- var type = relationship.type,
2520
+ var kind = relationship.kind,
2405
2521
  value = data[key];
2406
2522
 
2407
2523
  if (value == null) { return; }
2408
2524
 
2409
- if (relationship.kind === 'belongsTo') {
2525
+ if (kind === 'belongsTo') {
2410
2526
  deserializeRecordId(store, data, key, relationship, value);
2411
- } else if (relationship.kind === 'hasMany') {
2527
+ } else if (kind === 'hasMany') {
2412
2528
  deserializeRecordIds(store, data, key, relationship, value);
2529
+ addUnsavedRecords(record, key, value);
2413
2530
  }
2414
2531
  });
2415
2532
 
@@ -2434,7 +2551,7 @@ function deserializeRecordId(store, data, key, relationship, id) {
2434
2551
 
2435
2552
  function typeFor(relationship, key, data) {
2436
2553
  if (relationship.options.polymorphic) {
2437
- return data[key + "_type"];
2554
+ return data[key + "Type"];
2438
2555
  } else {
2439
2556
  return relationship.type;
2440
2557
  }
@@ -2446,6 +2563,14 @@ function deserializeRecordIds(store, data, key, relationship, ids) {
2446
2563
  }
2447
2564
  }
2448
2565
 
2566
+ // If there are any unsaved records that are in a hasMany they won't be
2567
+ // in the payload, so add them back in manually.
2568
+ function addUnsavedRecords(record, key, data) {
2569
+ if(record) {
2570
+ data.pushObjects(record.get(key).filterBy('isNew'));
2571
+ }
2572
+ }
2573
+
2449
2574
  // Delegation to the adapter and promise management
2450
2575
 
2451
2576
  DS.PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin);
@@ -2497,6 +2622,10 @@ function _find(adapter, store, type, id, resolver) {
2497
2622
  payload = serializer.extract(store, type, payload, id, 'find');
2498
2623
 
2499
2624
  return store.push(type, payload);
2625
+ }, function(error) {
2626
+ var record = store.getById(type, id);
2627
+ record.notFound();
2628
+ throw error;
2500
2629
  }).then(resolver.resolve, resolver.reject);
2501
2630
  }
2502
2631
 
@@ -2507,6 +2636,8 @@ function _findMany(adapter, store, type, ids, owner, resolver) {
2507
2636
  return resolve(promise).then(function(payload) {
2508
2637
  payload = serializer.extract(store, type, payload, null, 'findMany');
2509
2638
 
2639
+ Ember.assert("The response from a findMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2640
+
2510
2641
  store.pushMany(type, payload);
2511
2642
  }).then(resolver.resolve, resolver.reject);
2512
2643
  }
@@ -2518,11 +2649,26 @@ function _findHasMany(adapter, store, record, link, relationship, resolver) {
2518
2649
  return resolve(promise).then(function(payload) {
2519
2650
  payload = serializer.extract(store, relationship.type, payload, null, 'findHasMany');
2520
2651
 
2652
+ Ember.assert("The response from a findHasMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2653
+
2521
2654
  var records = store.pushMany(relationship.type, payload);
2522
2655
  record.updateHasMany(relationship.key, records);
2523
2656
  }).then(resolver.resolve, resolver.reject);
2524
2657
  }
2525
2658
 
2659
+ function _findBelongsTo(adapter, store, record, link, relationship, resolver) {
2660
+ var promise = adapter.findBelongsTo(store, record, link, relationship),
2661
+ serializer = serializerForAdapter(adapter, relationship.type);
2662
+
2663
+ return resolve(promise).then(function(payload) {
2664
+ payload = serializer.extract(store, relationship.type, payload, null, 'findBelongsTo');
2665
+
2666
+ var record = store.push(relationship.type, payload);
2667
+ record.updateBelongsTo(relationship.key, record);
2668
+ return record;
2669
+ }).then(resolver.resolve, resolver.reject);
2670
+ }
2671
+
2526
2672
  function _findAll(adapter, store, type, sinceToken, resolver) {
2527
2673
  var promise = adapter.findAll(store, type, sinceToken),
2528
2674
  serializer = serializerForAdapter(adapter, type);
@@ -2530,6 +2676,8 @@ function _findAll(adapter, store, type, sinceToken, resolver) {
2530
2676
  return resolve(promise).then(function(payload) {
2531
2677
  payload = serializer.extract(store, type, payload, null, 'findAll');
2532
2678
 
2679
+ Ember.assert("The response from a findAll must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2680
+
2533
2681
  store.pushMany(type, payload);
2534
2682
  store.didUpdateAll(type);
2535
2683
  return store.all(type);
@@ -2543,6 +2691,8 @@ function _findQuery(adapter, store, type, query, recordArray, resolver) {
2543
2691
  return resolve(promise).then(function(payload) {
2544
2692
  payload = serializer.extract(store, type, payload, null, 'findAll');
2545
2693
 
2694
+ Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
2695
+
2546
2696
  recordArray.load(payload);
2547
2697
  return recordArray;
2548
2698
  }).then(resolver.resolve, resolver.reject);
@@ -2556,7 +2706,7 @@ function _commit(adapter, store, operation, record, resolver) {
2556
2706
  Ember.assert("Your adapter's '" + operation + "' method must return a promise, but it returned " + promise, isThenable(promise));
2557
2707
 
2558
2708
  return promise.then(function(payload) {
2559
- payload = serializer.extract(store, type, payload, get(record, 'id'), operation);
2709
+ if (payload) { payload = serializer.extract(store, type, payload, get(record, 'id'), operation); }
2560
2710
  store.didSaveRecord(record, payload);
2561
2711
  return record;
2562
2712
  }, function(reason) {
@@ -2579,8 +2729,7 @@ function _commit(adapter, store, operation, record, resolver) {
2579
2729
  @module ember-data
2580
2730
  */
2581
2731
 
2582
- var get = Ember.get, set = Ember.set,
2583
- once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
2732
+ var get = Ember.get, set = Ember.set;
2584
2733
 
2585
2734
  /*
2586
2735
  WARNING: Much of these docs are inaccurate as of bf8497.
@@ -2748,10 +2897,14 @@ var hasDefinedProperties = function(object) {
2748
2897
  };
2749
2898
 
2750
2899
  var didSetProperty = function(record, context) {
2751
- if (context.value !== context.oldValue) {
2900
+ if (context.value === context.originalValue) {
2901
+ delete record._attributes[context.name];
2902
+ record.send('propertyWasReset', context.name);
2903
+ } else if (context.value !== context.oldValue) {
2752
2904
  record.send('becomeDirty');
2753
- record.updateRecordArraysLater();
2754
2905
  }
2906
+
2907
+ record.updateRecordArraysLater();
2755
2908
  };
2756
2909
 
2757
2910
  // Implementation notes:
@@ -2809,10 +2962,20 @@ var DirtyState = {
2809
2962
  // This means that there are local pending changes, but they
2810
2963
  // have not yet begun to be saved, and are not invalid.
2811
2964
  uncommitted: {
2812
-
2813
2965
  // EVENTS
2814
2966
  didSetProperty: didSetProperty,
2815
2967
 
2968
+ propertyWasReset: function(record, name) {
2969
+ var stillDirty = false;
2970
+
2971
+ for (var prop in record._attributes) {
2972
+ stillDirty = true;
2973
+ break;
2974
+ }
2975
+
2976
+ if (!stillDirty) { record.send('rolledBack'); }
2977
+ },
2978
+
2816
2979
  pushedData: Ember.K,
2817
2980
 
2818
2981
  becomeDirty: Ember.K,
@@ -2997,6 +3160,8 @@ var RootState = {
2997
3160
  // you out of the in-flight state.
2998
3161
  rolledBack: Ember.K,
2999
3162
 
3163
+ propertyWasReset: Ember.K,
3164
+
3000
3165
  // SUBSTATES
3001
3166
 
3002
3167
  // A record begins its lifecycle in the `empty` state.
@@ -3008,7 +3173,8 @@ var RootState = {
3008
3173
  isEmpty: true,
3009
3174
 
3010
3175
  // EVENTS
3011
- loadingData: function(record) {
3176
+ loadingData: function(record, promise) {
3177
+ record._loadingPromise = promise;
3012
3178
  record.transitionTo('loading');
3013
3179
  },
3014
3180
 
@@ -3022,6 +3188,7 @@ var RootState = {
3022
3188
 
3023
3189
  pushedData: function(record) {
3024
3190
  record.transitionTo('loaded.saved');
3191
+ record.triggerLater('didLoad');
3025
3192
  }
3026
3193
  },
3027
3194
 
@@ -3035,6 +3202,10 @@ var RootState = {
3035
3202
  // FLAGS
3036
3203
  isLoading: true,
3037
3204
 
3205
+ exit: function(record) {
3206
+ record._loadingPromise = null;
3207
+ },
3208
+
3038
3209
  // EVENTS
3039
3210
  pushedData: function(record) {
3040
3211
  record.transitionTo('loaded.saved');
@@ -3044,6 +3215,10 @@ var RootState = {
3044
3215
 
3045
3216
  becameError: function(record) {
3046
3217
  record.triggerLater('becameError', record);
3218
+ },
3219
+
3220
+ notFound: function(record) {
3221
+ record.transitionTo('empty');
3047
3222
  }
3048
3223
  },
3049
3224
 
@@ -3108,7 +3283,7 @@ var RootState = {
3108
3283
 
3109
3284
  didCommit: function(record) {
3110
3285
  record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType'));
3111
- },
3286
+ }
3112
3287
 
3113
3288
  },
3114
3289
 
@@ -3219,8 +3394,6 @@ var RootState = {
3219
3394
  }
3220
3395
  };
3221
3396
 
3222
- var hasOwnProp = {}.hasOwnProperty;
3223
-
3224
3397
  function wireState(object, parent, name) {
3225
3398
  /*jshint proto:true*/
3226
3399
  // TODO: Use Object.create and copy instead
@@ -3251,11 +3424,9 @@ DS.RootState = RootState;
3251
3424
  @module ember-data
3252
3425
  */
3253
3426
 
3254
- var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map,
3427
+ var get = Ember.get, set = Ember.set,
3255
3428
  merge = Ember.merge, once = Ember.run.once;
3256
3429
 
3257
- var arrayMap = Ember.ArrayPolyfills.map;
3258
-
3259
3430
  var retrieveFromCurrentState = Ember.computed(function(key, value) {
3260
3431
  return get(get(this, 'currentState'), key);
3261
3432
  }).property('currentState').readOnly();
@@ -3319,7 +3490,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3319
3490
  @returns {Object} A JSON representation of the object.
3320
3491
  */
3321
3492
  toJSON: function(options) {
3322
- var serializer = DS.JSONSerializer.create();
3493
+ // container is for lazy transform lookups
3494
+ var serializer = DS.JSONSerializer.create({ container: this.container });
3323
3495
  return serializer.serialize(this, options);
3324
3496
  },
3325
3497
 
@@ -3437,6 +3609,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3437
3609
  for (i=0, l=setups.length; i<l; i++) {
3438
3610
  setups[i].setup(this);
3439
3611
  }
3612
+
3613
+ this.updateRecordArraysLater();
3440
3614
  },
3441
3615
 
3442
3616
  _unhandledEvent: function(state, name, context) {
@@ -3456,22 +3630,45 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3456
3630
  if (transaction) { fn(transaction); }
3457
3631
  },
3458
3632
 
3459
- loadingData: function() {
3460
- this.send('loadingData');
3633
+ loadingData: function(promise) {
3634
+ this.send('loadingData', promise);
3461
3635
  },
3462
3636
 
3463
3637
  loadedData: function() {
3464
3638
  this.send('loadedData');
3465
3639
  },
3466
3640
 
3641
+ notFound: function() {
3642
+ this.send('notFound');
3643
+ },
3644
+
3467
3645
  pushedData: function() {
3468
3646
  this.send('pushedData');
3469
3647
  },
3470
3648
 
3649
+ /**
3650
+ Marks the record as deleted but does not save it. You must call
3651
+ `save` afterwards if you want to persist it. You might use this
3652
+ method if you want to allow the user to still `rollback()` a
3653
+ delete after it was made.
3654
+
3655
+ @method deleteRecord
3656
+ */
3471
3657
  deleteRecord: function() {
3472
3658
  this.send('deleteRecord');
3473
3659
  },
3474
3660
 
3661
+ /**
3662
+ Same as `deleteRecord`, but saves the record immediately.
3663
+
3664
+ @method destroyRecord
3665
+ @returns Promise
3666
+ */
3667
+ destroyRecord: function() {
3668
+ this.deleteRecord();
3669
+ return this.save();
3670
+ },
3671
+
3475
3672
  unloadRecord: function() {
3476
3673
  Ember.assert("You can only unload a loaded, non-dirty record.", !get(this, 'isDirty'));
3477
3674
 
@@ -3496,6 +3693,27 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3496
3693
  }
3497
3694
  },
3498
3695
 
3696
+ /**
3697
+ Gets the diff for the current model.
3698
+
3699
+ @method changedAttributes
3700
+
3701
+ @returns {Object} an object, whose keys are changed properties,
3702
+ and value is an [oldProp, newProp] array.
3703
+ */
3704
+ changedAttributes: function() {
3705
+ var oldData = get(this, '_data'),
3706
+ newData = get(this, '_attributes'),
3707
+ diffData = {},
3708
+ prop;
3709
+
3710
+ for (prop in newData) {
3711
+ diffData[prop] = [oldData[prop], newData[prop]];
3712
+ }
3713
+
3714
+ return diffData;
3715
+ },
3716
+
3499
3717
  adapterWillCommit: function() {
3500
3718
  this.send('willCommit');
3501
3719
  },
@@ -3541,6 +3759,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3541
3759
  var relationships = get(this.constructor, 'relationshipsByName');
3542
3760
  this.updateRecordArraysLater();
3543
3761
  relationships.forEach(function(name, relationship) {
3762
+ if (this._data.links && this._data.links[name]) { return; }
3544
3763
  if (relationship.kind === 'hasMany') {
3545
3764
  this.hasManyDidChange(relationship.key);
3546
3765
  }
@@ -3551,8 +3770,6 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3551
3770
  var hasMany = this._relationships[key];
3552
3771
 
3553
3772
  if (hasMany) {
3554
- var type = get(this.constructor, 'relationshipsByName').get(key).type;
3555
- var store = get(this, 'store');
3556
3773
  var records = this._data[key] || [];
3557
3774
 
3558
3775
  set(hasMany, 'content', Ember.A(records));
@@ -3575,6 +3792,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3575
3792
  var relationships = this._relationships;
3576
3793
 
3577
3794
  this.eachRelationship(function(name, rel) {
3795
+ if (data.links && data.links[name]) { return; }
3578
3796
  if (rel.options.async) { relationships[name] = null; }
3579
3797
  });
3580
3798
 
@@ -3603,8 +3821,18 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3603
3821
  this.hasManyDidChange(name);
3604
3822
  },
3605
3823
 
3824
+ updateBelongsTo: function(name, record) {
3825
+ this._data[name] = record;
3826
+ },
3827
+
3606
3828
  rollback: function() {
3607
3829
  this._attributes = {};
3830
+
3831
+ if (get(this, 'isError')) {
3832
+ this._inFlightAttributes = {};
3833
+ set(this, 'isError', false);
3834
+ }
3835
+
3608
3836
  this.send('rolledBack');
3609
3837
 
3610
3838
  this.suspendRelationshipObservers(function() {
@@ -3655,7 +3883,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
3655
3883
  @method save
3656
3884
  */
3657
3885
  save: function() {
3658
- var resolver = Ember.RSVP.defer(), record = this;
3886
+ var resolver = Ember.RSVP.defer();
3659
3887
 
3660
3888
  this.get('store').scheduleSave(this, resolver);
3661
3889
  this._inFlightAttributes = this._attributes;
@@ -3887,12 +4115,11 @@ DS.attr = function(type, options) {
3887
4115
  options: options
3888
4116
  };
3889
4117
 
3890
- return Ember.computed(function(key, value, oldValue) {
3891
- var currentValue;
3892
-
4118
+ return Ember.computed(function(key, value) {
3893
4119
  if (arguments.length > 1) {
3894
4120
  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');
3895
- this.send('didSetProperty', { name: key, oldValue: this._attributes[key] || this._inFlightAttributes[key] || this._data[key], value: value });
4121
+ var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key];
4122
+ this.send('didSetProperty', { name: key, oldValue: oldValue, originalValue: this._data[key], value: value });
3896
4123
  this._attributes[key] = value;
3897
4124
  return value;
3898
4125
  } else if (hasValue(this, key)) {
@@ -4445,18 +4672,23 @@ var get = Ember.get, set = Ember.set,
4445
4672
  function asyncBelongsTo(type, options, meta) {
4446
4673
  return Ember.computed(function(key, value) {
4447
4674
  var data = get(this, 'data'),
4448
- store = get(this, 'store'),
4449
- belongsTo;
4675
+ store = get(this, 'store');
4450
4676
 
4451
4677
  if (arguments.length === 2) {
4452
4678
  Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type));
4453
- return value === undefined ? null : value;
4679
+ return value === undefined ? null : DS.PromiseObject.create({ promise: Ember.RSVP.resolve(value) });
4454
4680
  }
4455
4681
 
4456
- belongsTo = data[key];
4682
+ var link = data.links && data.links[key],
4683
+ belongsTo = data[key];
4457
4684
 
4458
- if(!isNone(belongsTo) && get(belongsTo, 'isEmpty')) {
4459
- return store.fetchRecord(belongsTo);
4685
+ if(!isNone(belongsTo)) {
4686
+ var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo);
4687
+ return DS.PromiseObject.create({promise: promise});
4688
+ } else if (link) {
4689
+ var resolver = Ember.RSVP.defer();
4690
+ store.findBelongsTo(this, link, meta, resolver);
4691
+ return DS.PromiseObject.create({ promise: resolver.promise });
4460
4692
  } else {
4461
4693
  return null;
4462
4694
  }
@@ -4464,7 +4696,12 @@ function asyncBelongsTo(type, options, meta) {
4464
4696
  }
4465
4697
 
4466
4698
  DS.belongsTo = function(type, options) {
4467
- 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)));
4699
+ if (typeof type === 'object') {
4700
+ options = type;
4701
+ type = undefined;
4702
+ } else {
4703
+ 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)));
4704
+ }
4468
4705
 
4469
4706
  options = options || {};
4470
4707
 
@@ -4479,11 +4716,7 @@ DS.belongsTo = function(type, options) {
4479
4716
  store = get(this, 'store'), belongsTo, typeClass;
4480
4717
 
4481
4718
  if (typeof type === 'string') {
4482
- if (type.indexOf(".") === -1) {
4483
- typeClass = store.modelFor(type);
4484
- } else {
4485
- typeClass = get(Ember.lookup, type);
4486
- }
4719
+ typeClass = store.modelFor(type);
4487
4720
  } else {
4488
4721
  typeClass = type;
4489
4722
  }
@@ -4497,9 +4730,7 @@ DS.belongsTo = function(type, options) {
4497
4730
 
4498
4731
  if (isNone(belongsTo)) { return null; }
4499
4732
 
4500
- if (get(belongsTo, 'isEmpty')) {
4501
- store.fetchRecord(belongsTo);
4502
- }
4733
+ store.fetchRecord(belongsTo);
4503
4734
 
4504
4735
  return belongsTo;
4505
4736
  }).property('data').meta(meta);
@@ -4523,11 +4754,12 @@ DS.Model.reopen({
4523
4754
  */
4524
4755
  belongsToWillChange: Ember.beforeObserver(function(record, key) {
4525
4756
  if (get(record, 'isLoaded')) {
4526
- var oldParent = get(record, key),
4527
- store = get(record, 'store');
4757
+ var oldParent = get(record, key);
4758
+
4759
+ if (oldParent) {
4760
+ var store = get(record, 'store'),
4761
+ change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" });
4528
4762
 
4529
- if (oldParent){
4530
- var change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" });
4531
4763
  change.sync();
4532
4764
  this._changesToSync[key] = change;
4533
4765
  }
@@ -4544,7 +4776,8 @@ DS.Model.reopen({
4544
4776
  belongsToDidChange: Ember.immediateObserver(function(record, key) {
4545
4777
  if (get(record, 'isLoaded')) {
4546
4778
  var newParent = get(record, key);
4547
- if(newParent){
4779
+
4780
+ if (newParent) {
4548
4781
  var store = get(record, 'store'),
4549
4782
  change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" });
4550
4783
 
@@ -4566,10 +4799,11 @@ DS.Model.reopen({
4566
4799
  */
4567
4800
 
4568
4801
  var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties;
4569
- var forEach = Ember.EnumerableUtils.forEach;
4570
4802
 
4571
4803
  function asyncHasMany(type, options, meta) {
4572
4804
  return Ember.computed(function(key, value) {
4805
+ if (this._relationships[key]) { return this._relationships[key]; }
4806
+
4573
4807
  var resolver = Ember.RSVP.defer();
4574
4808
 
4575
4809
  var relationship = buildRelationship(this, key, options, function(store, data) {
@@ -4617,14 +4851,17 @@ function hasRelationship(type, options) {
4617
4851
  return Ember.computed(function(key, value) {
4618
4852
  return buildRelationship(this, key, options, function(store, data) {
4619
4853
  var records = data[key];
4620
- 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));
4854
+ 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));
4621
4855
  return store.findMany(this, data[key], meta.type);
4622
4856
  });
4623
4857
  }).property('data').meta(meta);
4624
4858
  }
4625
4859
 
4626
4860
  DS.hasMany = function(type, options) {
4627
- Ember.assert("The type passed to DS.hasMany must be defined", !!type);
4861
+ if (typeof type === 'object') {
4862
+ options = type;
4863
+ type = undefined;
4864
+ }
4628
4865
  return hasRelationship(type, options);
4629
4866
  };
4630
4867
 
@@ -4663,7 +4900,7 @@ DS.Model.reopen({
4663
4900
  being defined. So, for example, when the user does this:
4664
4901
 
4665
4902
  DS.Model.extend({
4666
- parent: DS.belongsTo(App.User)
4903
+ parent: DS.belongsTo('user')
4667
4904
  });
4668
4905
 
4669
4906
  This hook would be called with "parent" as the key and the computed
@@ -4717,7 +4954,7 @@ DS.Model.reopenClass({
4717
4954
  For example, if you define a model like this:
4718
4955
 
4719
4956
  App.Post = DS.Model.extend({
4720
- comments: DS.hasMany(App.Comment)
4957
+ comments: DS.hasMany('comment')
4721
4958
  });
4722
4959
 
4723
4960
  Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`.
@@ -4791,9 +5028,9 @@ DS.Model.reopenClass({
4791
5028
  For example, given the following model definition:
4792
5029
 
4793
5030
  App.Blog = DS.Model.extend({
4794
- users: DS.hasMany(App.User),
4795
- owner: DS.belongsTo(App.User),
4796
- posts: DS.hasMany(App.Post)
5031
+ users: DS.hasMany('user'),
5032
+ owner: DS.belongsTo('user'),
5033
+ posts: DS.hasMany('post')
4797
5034
  });
4798
5035
 
4799
5036
  This computed property would return a map describing these
@@ -4823,7 +5060,7 @@ DS.Model.reopenClass({
4823
5060
  // it to the map.
4824
5061
  if (meta.isRelationship) {
4825
5062
  if (typeof meta.type === 'string') {
4826
- meta.type = Ember.get(Ember.lookup, meta.type);
5063
+ meta.type = this.store.modelFor(meta.type);
4827
5064
  }
4828
5065
 
4829
5066
  var relationshipsForType = map.get(meta.type);
@@ -4841,10 +5078,10 @@ DS.Model.reopenClass({
4841
5078
  definition:
4842
5079
 
4843
5080
  App.Blog = DS.Model.extend({
4844
- users: DS.hasMany(App.User),
4845
- owner: DS.belongsTo(App.User),
5081
+ users: DS.hasMany('user'),
5082
+ owner: DS.belongsTo('user'),
4846
5083
 
4847
- posts: DS.hasMany(App.Post)
5084
+ posts: DS.hasMany('post')
4848
5085
  });
4849
5086
 
4850
5087
  This property would contain the following:
@@ -4880,9 +5117,10 @@ DS.Model.reopenClass({
4880
5117
  For example, given a model with this definition:
4881
5118
 
4882
5119
  App.Blog = DS.Model.extend({
4883
- users: DS.hasMany(App.User),
4884
- owner: DS.belongsTo(App.User),
4885
- posts: DS.hasMany(App.Post)
5120
+ users: DS.hasMany('user'),
5121
+ owner: DS.belongsTo('user'),
5122
+
5123
+ posts: DS.hasMany('post')
4886
5124
  });
4887
5125
 
4888
5126
  This property would contain the following:
@@ -4907,7 +5145,7 @@ DS.Model.reopenClass({
4907
5145
  type = meta.type;
4908
5146
 
4909
5147
  if (typeof type === 'string') {
4910
- type = get(this, type, false) || get(Ember.lookup, type);
5148
+ type = get(this, type, false) || this.store.modelFor(type);
4911
5149
  }
4912
5150
 
4913
5151
  Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type);
@@ -4930,10 +5168,10 @@ DS.Model.reopenClass({
4930
5168
  definition:
4931
5169
 
4932
5170
  App.Blog = DS.Model.extend({
4933
- users: DS.hasMany(App.User),
4934
- owner: DS.belongsTo(App.User),
5171
+ users: DS.hasMany('user'),
5172
+ owner: DS.belongsTo('user'),
4935
5173
 
4936
- posts: DS.hasMany(App.Post)
5174
+ posts: DS.hasMany('post')
4937
5175
  });
4938
5176
 
4939
5177
  This property would contain the following:
@@ -4957,6 +5195,12 @@ DS.Model.reopenClass({
4957
5195
  meta.key = name;
4958
5196
  type = meta.type;
4959
5197
 
5198
+ if (!type && meta.kind === 'hasMany') {
5199
+ type = Ember.String.singularize(name);
5200
+ } else if (!type) {
5201
+ type = name;
5202
+ }
5203
+
4960
5204
  if (typeof type === 'string') {
4961
5205
  meta.type = this.store.modelFor(type);
4962
5206
  }
@@ -4976,10 +5220,10 @@ DS.Model.reopenClass({
4976
5220
  For example:
4977
5221
 
4978
5222
  App.Blog = DS.Model.extend({
4979
- users: DS.hasMany(App.User),
4980
- owner: DS.belongsTo(App.User),
5223
+ users: DS.hasMany('user'),
5224
+ owner: DS.belongsTo('user'),
4981
5225
 
4982
- posts: DS.hasMany(App.Post),
5226
+ posts: DS.hasMany('post'),
4983
5227
 
4984
5228
  title: DS.attr('string')
4985
5229
  });
@@ -5285,9 +5529,8 @@ DS.RecordArrayManager = Ember.Object.extend({
5285
5529
  @module ember-data
5286
5530
  */
5287
5531
 
5288
- var get = Ember.get, set = Ember.set, merge = Ember.merge;
5532
+ var get = Ember.get, set = Ember.set;
5289
5533
  var map = Ember.ArrayPolyfills.map;
5290
- var resolve = Ember.RSVP.resolve;
5291
5534
 
5292
5535
  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
5293
5536
 
@@ -5301,18 +5544,6 @@ DS.InvalidError = function(errors) {
5301
5544
  };
5302
5545
  DS.InvalidError.prototype = Ember.create(Error.prototype);
5303
5546
 
5304
- function isThenable(object) {
5305
- return object && typeof object.then === 'function';
5306
- }
5307
-
5308
- // Simple dispatcher to support overriding the aliased
5309
- // method in subclasses.
5310
- function aliasMethod(methodName) {
5311
- return function() {
5312
- return this[methodName].apply(this, arguments);
5313
- };
5314
- }
5315
-
5316
5547
  /**
5317
5548
  An adapter is an object that receives requests from a store and
5318
5549
  translates them into the appropriate action to take against your
@@ -5366,8 +5597,8 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5366
5597
  The `find()` method is invoked when the store is asked for a record that
5367
5598
  has not previously been loaded. In response to `find()` being called, you
5368
5599
  should query your persistence layer for a record with the given ID. Once
5369
- found, you can asynchronously call the store's `load()` method to load
5370
- the record.
5600
+ found, you can asynchronously call the store's `push()` method to push
5601
+ the record into the store.
5371
5602
 
5372
5603
  Here is an example `find` implementation:
5373
5604
 
@@ -5378,8 +5609,8 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5378
5609
  jQuery.getJSON(url, function(data) {
5379
5610
  // data is a hash of key/value pairs. If your server returns a
5380
5611
  // root, simply do something like:
5381
- // store.load(type, id, data.person)
5382
- store.load(type, id, data);
5612
+ // store.push(type, id, data.person)
5613
+ store.push(type, id, data);
5383
5614
  });
5384
5615
  }
5385
5616
 
@@ -5454,9 +5685,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5454
5685
  method on success or `didError` method on failure.
5455
5686
 
5456
5687
  @method createRecord
5457
- @property {DS.Store} store
5458
- @property {subclass of DS.Model} type the DS.Model class of the record
5459
- @property {DS.Model} record
5688
+ @param {DS.Store} store
5689
+ @param {subclass of DS.Model} type the DS.Model class of the record
5690
+ @param {DS.Model} record
5460
5691
  */
5461
5692
  createRecord: Ember.required(Function),
5462
5693
 
@@ -5467,9 +5698,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5467
5698
  Serializes the record update and send it to the server.
5468
5699
 
5469
5700
  @method updateRecord
5470
- @property {DS.Store} store
5471
- @property {subclass of DS.Model} type the DS.Model class of the record
5472
- @property {DS.Model} record
5701
+ @param {DS.Store} store
5702
+ @param {subclass of DS.Model} type the DS.Model class of the record
5703
+ @param {DS.Model} record
5473
5704
  */
5474
5705
  updateRecord: Ember.required(Function),
5475
5706
 
@@ -5480,9 +5711,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5480
5711
  Sends a delete request for the record to the server.
5481
5712
 
5482
5713
  @method deleteRecord
5483
- @property {DS.Store} store
5484
- @property {subclass of DS.Model} type the DS.Model class of the record
5485
- @property {DS.Model} record
5714
+ @param {DS.Store} store
5715
+ @param {subclass of DS.Model} type the DS.Model class of the record
5716
+ @param {DS.Model} record
5486
5717
  */
5487
5718
  deleteRecord: Ember.required(Function),
5488
5719
 
@@ -5494,9 +5725,9 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
5494
5725
  server requests.
5495
5726
 
5496
5727
  @method findMany
5497
- @property {DS.Store} store
5498
- @property {subclass of DS.Model} type the DS.Model class of the records
5499
- @property {Array} ids
5728
+ @param {DS.Store} store
5729
+ @param {subclass of DS.Model} type the DS.Model class of the records
5730
+ @param {Array} ids
5500
5731
  */
5501
5732
  findMany: function(store, type, ids) {
5502
5733
  var promises = map.call(ids, function(id) {
@@ -5593,7 +5824,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5593
5824
  },
5594
5825
 
5595
5826
  /**
5596
- Implement this method in order to provide provide json for CRUD methods
5827
+ Implement this method in order to provide json for CRUD methods
5597
5828
 
5598
5829
  @method mockJSON
5599
5830
  @param type
@@ -5609,7 +5840,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5609
5840
  @param record
5610
5841
  */
5611
5842
  generateIdForRecord: function(store) {
5612
- return counter++;
5843
+ return "fixture-" + counter++;
5613
5844
  },
5614
5845
 
5615
5846
  /**
@@ -5825,6 +6056,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
5825
6056
 
5826
6057
  var get = Ember.get, set = Ember.set;
5827
6058
  var forEach = Ember.ArrayPolyfills.forEach;
6059
+ var map = Ember.ArrayPolyfills.map;
5828
6060
 
5829
6061
  function coerceId(id) {
5830
6062
  return id == null ? null : id+'';
@@ -5865,6 +6097,10 @@ function coerceId(id) {
5865
6097
  You can also implement `keyForRelationship`, which takes the name
5866
6098
  of the relationship as the first parameter, and the kind of
5867
6099
  relationship (`hasMany` or `belongsTo`) as the second parameter.
6100
+
6101
+ @class RESTSerializer
6102
+ @namespace DS
6103
+ @extends DS.JSONSerializer
5868
6104
  */
5869
6105
  DS.RESTSerializer = DS.JSONSerializer.extend({
5870
6106
  /**
@@ -5944,6 +6180,31 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
5944
6180
  return this._super(type, hash, prop);
5945
6181
  },
5946
6182
 
6183
+ /**
6184
+ You can use this method to normalize all payloads, regardless of whether they
6185
+ represent single records or an array.
6186
+
6187
+ For example, you might want to remove some extraneous data from the payload:
6188
+
6189
+ ```js
6190
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6191
+ normalizePayload: function(type, payload) {
6192
+ delete payload.version;
6193
+ delete payload.status;
6194
+ return payload;
6195
+ }
6196
+ });
6197
+ ```
6198
+
6199
+ @method normalizePayload
6200
+ @param {subclass of DS.Model} type
6201
+ @param {Object} hash
6202
+ @returns Object the normalized payload
6203
+ */
6204
+ normalizePayload: function(type, payload) {
6205
+ return payload;
6206
+ },
6207
+
5947
6208
  /**
5948
6209
  @method normalizeId
5949
6210
  @private
@@ -6079,6 +6340,7 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6079
6340
  for the first time or updated (`createRecord` or `updateRecord`). In
6080
6341
  particular, it will update the properties of the record that was saved.
6081
6342
 
6343
+ @method extractSingle
6082
6344
  @param {DS.Store} store
6083
6345
  @param {subclass of DS.Model} type
6084
6346
  @param {Object} payload
@@ -6087,25 +6349,33 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6087
6349
  @returns Object the primary response to the original request
6088
6350
  */
6089
6351
  extractSingle: function(store, primaryType, payload, recordId, requestType) {
6352
+ payload = this.normalizePayload(primaryType, payload);
6353
+
6090
6354
  var primaryTypeName = primaryType.typeKey,
6091
6355
  primaryRecord;
6092
6356
 
6093
6357
  for (var prop in payload) {
6094
- // legacy support for singular names
6095
- if (prop === primaryTypeName) {
6358
+ var typeName = this.typeForRoot(prop),
6359
+ isPrimary = typeName === primaryTypeName;
6360
+
6361
+ // legacy support for singular resources
6362
+ if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) {
6096
6363
  primaryRecord = this.normalize(primaryType, payload[prop], prop);
6097
6364
  continue;
6098
6365
  }
6099
6366
 
6100
- var typeName = this.singularize(prop),
6101
- type = store.modelFor(typeName);
6367
+ var type = store.modelFor(typeName);
6102
6368
 
6103
6369
  /*jshint loopfunc:true*/
6104
6370
  forEach.call(payload[prop], function(hash) {
6105
- hash = this.normalize(type, hash, prop);
6371
+ var typeName = this.typeForRoot(prop),
6372
+ type = store.modelFor(typeName),
6373
+ typeSerializer = store.serializerFor(type);
6374
+
6375
+ hash = typeSerializer.normalize(type, hash, prop);
6106
6376
 
6107
- var isFirstCreatedRecord = typeName === primaryTypeName && !recordId && !primaryRecord,
6108
- isUpdatedRecord = typeName === primaryTypeName && coerceId(hash.id) === recordId;
6377
+ var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord,
6378
+ isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId;
6109
6379
 
6110
6380
  // find the primary record.
6111
6381
  //
@@ -6212,6 +6482,12 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6212
6482
  or `findHasMany`. In particular, the primary array will become the
6213
6483
  list of records in the record array that kicked off the request.
6214
6484
 
6485
+ If your primary array contains secondary (embedded) records of the same type,
6486
+ you cannot place these into the primary array `posts`. Instead, place the
6487
+ secondary items into an underscore prefixed property `_posts`, which will
6488
+ push these items into the store and will not affect the resulting query.
6489
+
6490
+ @method extractArray
6215
6491
  @param {DS.Store} store
6216
6492
  @param {subclass of DS.Model} type
6217
6493
  @param {Object} payload
@@ -6220,17 +6496,28 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6220
6496
  to the original query.
6221
6497
  */
6222
6498
  extractArray: function(store, primaryType, payload) {
6499
+ payload = this.normalizePayload(primaryType, payload);
6500
+
6223
6501
  var primaryTypeName = primaryType.typeKey,
6224
6502
  primaryArray;
6225
6503
 
6226
6504
  for (var prop in payload) {
6227
- var typeName = this.singularize(prop),
6505
+ var typeKey = prop,
6506
+ forcedSecondary = false;
6507
+
6508
+ if (prop.charAt(0) === '_') {
6509
+ forcedSecondary = true;
6510
+ typeKey = prop.substr(1);
6511
+ }
6512
+
6513
+ var typeName = this.typeForRoot(typeKey),
6228
6514
  type = store.modelFor(typeName),
6229
- isPrimary = typeName === primaryTypeName;
6515
+ typeSerializer = store.serializerFor(type),
6516
+ isPrimary = (!forcedSecondary && (typeName === primaryTypeName));
6230
6517
 
6231
6518
  /*jshint loopfunc:true*/
6232
- var normalizedArray = payload[prop].map(function(hash) {
6233
- return this.normalize(type, hash, prop);
6519
+ var normalizedArray = map.call(payload[prop], function(hash) {
6520
+ return typeSerializer.normalize(type, hash, prop);
6234
6521
  }, this);
6235
6522
 
6236
6523
  if (isPrimary) {
@@ -6244,28 +6531,81 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6244
6531
  },
6245
6532
 
6246
6533
  /**
6247
- @private
6248
- @method pluralize
6249
- @param {String} key
6250
- */
6251
- pluralize: function(key) {
6252
- return Ember.String.pluralize(key);
6253
- },
6534
+ This method allows you to push a payload containing top-level
6535
+ collections of records organized per type.
6254
6536
 
6255
- /**
6256
- @private
6257
- @method singularize
6258
- @param {String} key
6259
- */
6260
- singularize: function(key) {
6261
- return Ember.String.singularize(key);
6262
- },
6537
+ ```js
6538
+ {
6539
+ "posts": [{
6540
+ "id": "1",
6541
+ "title": "Rails is omakase",
6542
+ "author", "1",
6543
+ "comments": [ "1" ]
6544
+ }],
6545
+ "comments": [{
6546
+ "id": "1",
6547
+ "body": "FIRST
6548
+ }],
6549
+ "users": [{
6550
+ "id": "1",
6551
+ "name": "@d2h"
6552
+ }]
6553
+ }
6554
+ ```
6263
6555
 
6264
- // SERIALIZE
6556
+ It will first normalize the payload, so you can use this to push
6557
+ in data streaming in from your server structured the same way
6558
+ that fetches and saves are structured.
6265
6559
 
6266
- /**
6267
- Called when a record is saved in order to convert the
6268
- record into JSON.
6560
+ @method pushPayload
6561
+ @param {DS.Store} store
6562
+ @param {Object} payload
6563
+ */
6564
+ pushPayload: function(store, payload) {
6565
+ payload = this.normalizePayload(null, payload);
6566
+
6567
+ for (var prop in payload) {
6568
+ var typeName = this.typeForRoot(prop),
6569
+ type = store.modelFor(typeName);
6570
+
6571
+ /*jshint loopfunc:true*/
6572
+ var normalizedArray = map.call(payload[prop], function(hash) {
6573
+ return this.normalize(type, hash, prop);
6574
+ }, this);
6575
+
6576
+ store.pushMany(typeName, normalizedArray);
6577
+ }
6578
+ },
6579
+
6580
+ /**
6581
+ You can use this method to normalize the JSON root keys returned
6582
+ into the model type expected by your store.
6583
+
6584
+ For example, your server may return underscored root keys rather than
6585
+ the expected camelcased versions.
6586
+
6587
+ ```js
6588
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6589
+ typeForRoot: function(root) {
6590
+ var camelized = Ember.String.camelize(root);
6591
+ return Ember.String.singularize(camelized);
6592
+ }
6593
+ });
6594
+ ```
6595
+
6596
+ @method typeForRoot
6597
+ @param {String} root
6598
+ @returns String the model's typeKey
6599
+ */
6600
+ typeForRoot: function(root) {
6601
+ return Ember.String.singularize(root);
6602
+ },
6603
+
6604
+ // SERIALIZE
6605
+
6606
+ /**
6607
+ Called when a record is saved in order to convert the
6608
+ record into JSON.
6269
6609
 
6270
6610
  By default, it creates a JSON object with a key for
6271
6611
  each attribute and belongsTo relationship.
@@ -6400,9 +6740,54 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
6400
6740
  }
6401
6741
  });
6402
6742
  ```
6743
+
6744
+ @method serialize
6745
+ @param record
6746
+ @param options
6403
6747
  */
6404
6748
  serialize: function(record, options) {
6405
6749
  return this._super.apply(this, arguments);
6750
+ },
6751
+
6752
+ /**
6753
+ You can use this method to customize the root keys serialized into the JSON.
6754
+ By default the REST Serializer sends camelized root keys.
6755
+ For example, your server may expect underscored root objects.
6756
+
6757
+ ```js
6758
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
6759
+ serializeIntoHash: function(data, type, record, options) {
6760
+ var root = Ember.String.decamelize(type.typeKey);
6761
+ data[root] = this.serialize(record, options);
6762
+ }
6763
+ });
6764
+ ```
6765
+
6766
+ @method serializeIntoHash
6767
+ @param {Object} hash
6768
+ @param {subclass of DS.Model} type
6769
+ @param {DS.Model} record
6770
+ @param {Object} options
6771
+ */
6772
+ serializeIntoHash: function(hash, type, record, options) {
6773
+ hash[type.typeKey] = this.serialize(record, options);
6774
+ },
6775
+
6776
+ /**
6777
+ You can use this method to customize how polymorphic objects are serialized.
6778
+ By default the JSON Serializer creates the key by appending `Type` to
6779
+ the attribute and value from the model's camelcased model name.
6780
+
6781
+ @method serializePolymorphicType
6782
+ @param {DS.Model} record
6783
+ @param {Object} json
6784
+ @param relationship
6785
+ */
6786
+ serializePolymorphicType: function(record, json, relationship) {
6787
+ var key = relationship.key,
6788
+ belongsTo = get(record, key);
6789
+ key = this.keyForAttribute ? this.keyForAttribute(key) : key;
6790
+ json[key + "Type"] = belongsTo.constructor.typeKey;
6406
6791
  }
6407
6792
  });
6408
6793
 
@@ -6448,7 +6833,7 @@ var forEach = Ember.ArrayPolyfills.forEach;
6448
6833
 
6449
6834
  ### Conventional Names
6450
6835
 
6451
- Attribute names in your JSON payload should be the underscored versions of
6836
+ Attribute names in your JSON payload should be the camelcased versions of
6452
6837
  the attributes in your Ember.js models.
6453
6838
 
6454
6839
  For example, if you have a `Person` model:
@@ -6466,8 +6851,8 @@ var forEach = Ember.ArrayPolyfills.forEach;
6466
6851
  ```js
6467
6852
  {
6468
6853
  "person": {
6469
- "first_name": "Barack",
6470
- "last_name": "Obama",
6854
+ "firstName": "Barack",
6855
+ "lastName": "Obama",
6471
6856
  "occupation": "President"
6472
6857
  }
6473
6858
  }
@@ -6645,6 +7030,8 @@ DS.RESTAdapter = DS.Adapter.extend({
6645
7030
  This method will be called with the parent record and `/posts/1/comments`.
6646
7031
 
6647
7032
  It will make an Ajax request to the originally specified URL.
7033
+ If the URL is host-relative (starting with a single slash), the
7034
+ request will use the host specified on the adapter (if any).
6648
7035
 
6649
7036
  @method findHasMany
6650
7037
  @see RESTAdapter/buildURL
@@ -6655,7 +7042,51 @@ DS.RESTAdapter = DS.Adapter.extend({
6655
7042
  @returns Promise
6656
7043
  */
6657
7044
  findHasMany: function(store, record, url) {
6658
- return this.ajax(url, 'GET');
7045
+ var host = get(this, 'host'),
7046
+ id = get(record, 'id'),
7047
+ type = record.constructor.typeKey;
7048
+
7049
+ if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') {
7050
+ url = host + url;
7051
+ }
7052
+
7053
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
7054
+ },
7055
+
7056
+ /**
7057
+ Called by the store in order to fetch a JSON array for
7058
+ the unloaded records in a belongs-to relationship that were originally
7059
+ specified as a URL (inside of `links`).
7060
+
7061
+ For example, if your original payload looks like this:
7062
+
7063
+ ```js
7064
+ {
7065
+ "person": {
7066
+ "id": 1,
7067
+ "name": "Tom Dale",
7068
+ "links": { "group": "/people/1/group" }
7069
+ }
7070
+ }
7071
+ ```
7072
+
7073
+ This method will be called with the parent record and `/people/1/group`.
7074
+
7075
+ It will make an Ajax request to the originally specified URL.
7076
+
7077
+ @method findBelongsTo
7078
+ @see RESTAdapter/buildURL
7079
+ @see RESTAdapter/ajax
7080
+ @param {DS.Store} store
7081
+ @param {DS.Model} record
7082
+ @param {String} url
7083
+ @returns Promise
7084
+ */
7085
+ findBelongsTo: function(store, record, url) {
7086
+ var id = get(record, 'id'),
7087
+ type = record.constructor.typeKey;
7088
+
7089
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
6659
7090
  },
6660
7091
 
6661
7092
  /**
@@ -6678,7 +7109,9 @@ DS.RESTAdapter = DS.Adapter.extend({
6678
7109
  */
6679
7110
  createRecord: function(store, type, record) {
6680
7111
  var data = {};
6681
- data[type.typeKey] = store.serializerFor(type.typeKey).serialize(record, { includeId: true });
7112
+ var serializer = store.serializerFor(type.typeKey);
7113
+
7114
+ serializer.serializeIntoHash(data, type, record, { includeId: true });
6682
7115
 
6683
7116
  return this.ajax(this.buildURL(type.typeKey), "POST", { data: data });
6684
7117
  },
@@ -6702,7 +7135,9 @@ DS.RESTAdapter = DS.Adapter.extend({
6702
7135
  */
6703
7136
  updateRecord: function(store, type, record) {
6704
7137
  var data = {};
6705
- data[type.typeKey] = store.serializerFor(type.typeKey).serialize(record);
7138
+ var serializer = store.serializerFor(type.typeKey);
7139
+
7140
+ serializer.serializeIntoHash(data, type, record);
6706
7141
 
6707
7142
  var id = get(record, 'id');
6708
7143
 
@@ -6732,7 +7167,10 @@ DS.RESTAdapter = DS.Adapter.extend({
6732
7167
  /**
6733
7168
  Builds a URL for a given type and optional ID.
6734
7169
 
6735
- If an ID is specified, it adds the ID to the root generated
7170
+ By default, it pluralizes the type's name (for example,
7171
+ 'post' becomes 'posts' and 'person' becomes 'people').
7172
+
7173
+ If an ID is specified, it adds the ID to the path generated
6736
7174
  for the type, separated by a `/`.
6737
7175
 
6738
7176
  @method buildURL
@@ -6741,50 +7179,95 @@ DS.RESTAdapter = DS.Adapter.extend({
6741
7179
  @returns String
6742
7180
  */
6743
7181
  buildURL: function(type, id) {
6744
- var host = get(this, 'host'),
6745
- namespace = get(this, 'namespace'),
6746
- url = [];
6747
-
6748
- if (host) { url.push(host); }
6749
- if (namespace) { url.push(namespace); }
7182
+ var url = [],
7183
+ host = get(this, 'host'),
7184
+ prefix = this.urlPrefix();
6750
7185
 
6751
- url.push(this.rootForType(type));
7186
+ if (type) { url.push(this.pathForType(type)); }
6752
7187
  if (id) { url.push(id); }
6753
7188
 
7189
+ if (prefix) { url.unshift(prefix); }
7190
+
6754
7191
  url = url.join('/');
6755
- if (!host) { url = '/' + url; }
7192
+ if (!host && url) { url = '/' + url; }
6756
7193
 
6757
7194
  return url;
6758
7195
  },
6759
7196
 
7197
+ urlPrefix: function(path, parentURL) {
7198
+ var host = get(this, 'host'),
7199
+ namespace = get(this, 'namespace'),
7200
+ url = [];
7201
+
7202
+ if (path) {
7203
+ // Absolute path
7204
+ if (path.charAt(0) === '/') {
7205
+ if (host) {
7206
+ path = path.slice(1);
7207
+ url.push(host);
7208
+ }
7209
+ // Relative path
7210
+ } else if (!/^http(s)?:\/\//.test(path)) {
7211
+ url.push(parentURL);
7212
+ }
7213
+ } else {
7214
+ if (host) { url.push(host); }
7215
+ if (namespace) { url.push(namespace); }
7216
+ }
7217
+
7218
+ if (path) {
7219
+ url.push(path);
7220
+ }
7221
+
7222
+ return url.join('/');
7223
+ },
7224
+
6760
7225
  /**
6761
- Determines the pathname root for a given type.
7226
+ Determines the pathname for a given type.
6762
7227
 
6763
7228
  By default, it pluralizes the type's name (for example,
6764
7229
  'post' becomes 'posts' and 'person' becomes 'people').
6765
7230
 
6766
- ### Pathname root customization
7231
+ ### Pathname customization
6767
7232
 
6768
7233
  For example if you have an object LineItem with an
6769
7234
  endpoint of "/line_items/".
6770
7235
 
6771
7236
  ```js
6772
7237
  DS.RESTAdapter.reopen({
6773
- rootForType: function(type) {
7238
+ pathForType: function(type) {
6774
7239
  var decamelized = Ember.String.decamelize(type);
6775
7240
  return Ember.String.pluralize(decamelized);
6776
7241
  };
6777
7242
  });
6778
7243
  ```
6779
7244
 
6780
- @method rootForType
7245
+ @method pathForType
6781
7246
  @param {String} type
6782
7247
  @returns String
6783
7248
  **/
6784
- rootForType: function(type) {
7249
+ pathForType: function(type) {
6785
7250
  return Ember.String.pluralize(type);
6786
7251
  },
6787
7252
 
7253
+ /**
7254
+ Takes an ajax response, and returns a relavant error.
7255
+
7256
+ By default, it has the following behavior:
7257
+
7258
+ * It simply returns the ajax response.
7259
+
7260
+ @method ajaxError
7261
+ @param jqXHR
7262
+ */
7263
+ ajaxError: function(jqXHR) {
7264
+ if (jqXHR) {
7265
+ jqXHR.then = null;
7266
+ }
7267
+
7268
+ return jqXHR;
7269
+ },
7270
+
6788
7271
  /**
6789
7272
  Takes a URL, an HTTP method and a hash of data, and makes an
6790
7273
  HTTP request.
@@ -6812,40 +7295,43 @@ DS.RESTAdapter = DS.Adapter.extend({
6812
7295
  var adapter = this;
6813
7296
 
6814
7297
  return new Ember.RSVP.Promise(function(resolve, reject) {
6815
- hash = hash || {};
6816
- hash.url = url;
6817
- hash.type = type;
6818
- hash.dataType = 'json';
6819
- hash.context = adapter;
6820
-
6821
- if (hash.data && type !== 'GET') {
6822
- hash.contentType = 'application/json; charset=utf-8';
6823
- hash.data = JSON.stringify(hash.data);
6824
- }
6825
-
6826
- if (adapter.headers !== undefined) {
6827
- var headers = adapter.headers;
6828
- hash.beforeSend = function (xhr) {
6829
- forEach.call(Ember.keys(headers), function(key) {
6830
- xhr.setRequestHeader(key, headers[key]);
6831
- });
6832
- };
6833
- }
7298
+ hash = adapter.ajaxOptions(url, type, hash);
6834
7299
 
6835
7300
  hash.success = function(json) {
6836
7301
  Ember.run(null, resolve, json);
6837
7302
  };
6838
7303
 
6839
7304
  hash.error = function(jqXHR, textStatus, errorThrown) {
6840
- if (jqXHR) {
6841
- jqXHR.then = null;
6842
- }
6843
-
6844
- Ember.run(null, reject, jqXHR);
7305
+ Ember.run(null, reject, adapter.ajaxError(jqXHR));
6845
7306
  };
6846
7307
 
6847
7308
  Ember.$.ajax(hash);
6848
7309
  });
7310
+ },
7311
+
7312
+ ajaxOptions: function(url, type, hash) {
7313
+ hash = hash || {};
7314
+ hash.url = url;
7315
+ hash.type = type;
7316
+ hash.dataType = 'json';
7317
+ hash.context = this;
7318
+
7319
+ if (hash.data && type !== 'GET') {
7320
+ hash.contentType = 'application/json; charset=utf-8';
7321
+ hash.data = JSON.stringify(hash.data);
7322
+ }
7323
+
7324
+ if (this.headers !== undefined) {
7325
+ var headers = this.headers;
7326
+ hash.beforeSend = function (xhr) {
7327
+ forEach.call(Ember.keys(headers), function(key) {
7328
+ xhr.setRequestHeader(key, headers[key]);
7329
+ });
7330
+ };
7331
+ }
7332
+
7333
+
7334
+ return hash;
6849
7335
  }
6850
7336
 
6851
7337
  });
@@ -6867,16 +7353,20 @@ DS.RESTAdapter = DS.Adapter.extend({
6867
7353
  DS.Model.reopen({
6868
7354
 
6869
7355
  /**
6870
- Provides info about the model for debugging purposes
6871
- by grouping the properties into more semantic groups.
7356
+ Provides info about the model for debugging purposes
7357
+ by grouping the properties into more semantic groups.
6872
7358
 
6873
- Meant to be used by debugging tools such as the Chrome Ember Extension.
7359
+ Meant to be used by debugging tools such as the Chrome Ember Extension.
6874
7360
 
6875
- - Groups all attributes in "Attributes" group.
6876
- - Groups all belongsTo relationships in "Belongs To" group.
6877
- - Groups all hasMany relationships in "Has Many" group.
6878
- - Groups all flags in "Flags" group.
6879
- - Flags relationship CPs as expensive properties.
7361
+ - Groups all attributes in "Attributes" group.
7362
+ - Groups all belongsTo relationships in "Belongs To" group.
7363
+ - Groups all hasMany relationships in "Has Many" group.
7364
+ - Groups all flags in "Flags" group.
7365
+ - Flags relationship CPs as expensive properties.
7366
+
7367
+ @method _debugInfo
7368
+ @for DS.Model
7369
+ @private
6880
7370
  */
6881
7371
  _debugInfo: function() {
6882
7372
  var attributes = ['id'],
@@ -6896,7 +7386,7 @@ DS.Model.reopen({
6896
7386
  {
6897
7387
  name: 'Attributes',
6898
7388
  properties: attributes,
6899
- expand: true,
7389
+ expand: true
6900
7390
  },
6901
7391
  {
6902
7392
  name: 'Belongs To',
@@ -6941,26 +7431,6 @@ DS.Model.reopen({
6941
7431
 
6942
7432
 
6943
7433
  (function() {
6944
- //Copyright (C) 2011 by Living Social, Inc.
6945
-
6946
- //Permission is hereby granted, free of charge, to any person obtaining a copy of
6947
- //this software and associated documentation files (the "Software"), to deal in
6948
- //the Software without restriction, including without limitation the rights to
6949
- //use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
6950
- //of the Software, and to permit persons to whom the Software is furnished to do
6951
- //so, subject to the following conditions:
6952
-
6953
- //The above copyright notice and this permission notice shall be included in all
6954
- //copies or substantial portions of the Software.
6955
-
6956
- //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6957
- //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6958
- //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
6959
- //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
6960
- //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
6961
- //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
6962
- //SOFTWARE.
6963
-
6964
7434
  /**
6965
7435
  Ember Data
6966
7436
 
@@ -7003,6 +7473,64 @@ function loadIrregular(rules, irregularPairs) {
7003
7473
  }
7004
7474
  }
7005
7475
 
7476
+ /**
7477
+ Inflector.Ember provides a mechanism for supplying inflection rules for your
7478
+ application. Ember includes a default set of inflection rules, and provides an
7479
+ API for providing additional rules.
7480
+
7481
+ Examples:
7482
+
7483
+ Creating an inflector with no rules.
7484
+
7485
+ ```js
7486
+ var inflector = new Ember.Inflector();
7487
+ ```
7488
+
7489
+ Creating an inflector with the default ember ruleset.
7490
+
7491
+ ```js
7492
+ var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
7493
+
7494
+ inflector.pluralize('cow') //=> 'kine'
7495
+ inflector.singularize('kine') //=> 'cow'
7496
+ ```
7497
+
7498
+ Creating an inflector and adding rules later.
7499
+
7500
+ ```javascript
7501
+ var inflector = Ember.Inflector.inflector;
7502
+
7503
+ inflector.pluralize('advice') // => 'advices'
7504
+ inflector.uncountable('advice');
7505
+ inflector.pluralize('advice') // => 'advice'
7506
+
7507
+ inflector.pluralize('formula') // => 'formulas'
7508
+ inflector.irregular('formula', 'formulae');
7509
+ inflector.pluralize('formula') // => 'formulae'
7510
+
7511
+ // you would not need to add these as they are the default rules
7512
+ inflector.plural(/$/, 's');
7513
+ inflector.singular(/s$/i, '');
7514
+ ```
7515
+
7516
+ Creating an inflector with a nondefault ruleset.
7517
+
7518
+ ```javascript
7519
+ var rules = {
7520
+ plurals: [ /$/, 's' ],
7521
+ singular: [ /\s$/, '' ],
7522
+ irregularPairs: [
7523
+ [ 'cow', 'kine' ]
7524
+ ],
7525
+ uncountable: [ 'fish' ]
7526
+ };
7527
+
7528
+ var inflector = new Ember.Inflector(rules);
7529
+ ```
7530
+
7531
+ @class Inflector
7532
+ @namespace Ember
7533
+ */
7006
7534
  function Inflector(ruleSet) {
7007
7535
  ruleSet = ruleSet || {};
7008
7536
  ruleSet.uncountable = ruleSet.uncountable || {};
@@ -7021,15 +7549,66 @@ function Inflector(ruleSet) {
7021
7549
  }
7022
7550
 
7023
7551
  Inflector.prototype = {
7552
+ /**
7553
+ @method plural
7554
+ @param {RegExp} regex
7555
+ @param {String} string
7556
+ */
7557
+ plural: function(regex, string) {
7558
+ this.rules.plurals.push([regex, string]);
7559
+ },
7560
+
7561
+ /**
7562
+ @method singular
7563
+ @param {RegExp} regex
7564
+ @param {String} string
7565
+ */
7566
+ singular: function(regex, string) {
7567
+ this.rules.singular.push([regex, string]);
7568
+ },
7569
+
7570
+ /**
7571
+ @method uncountable
7572
+ @param {String} regex
7573
+ */
7574
+ uncountable: function(string) {
7575
+ loadUncountable(this.rules, [string]);
7576
+ },
7577
+
7578
+ /**
7579
+ @method irregular
7580
+ @param {String} singular
7581
+ @param {String} plural
7582
+ */
7583
+ irregular: function (singular, plural) {
7584
+ loadIrregular(this.rules, [[singular, plural]]);
7585
+ },
7586
+
7587
+ /**
7588
+ @method pluralize
7589
+ @param {String} word
7590
+ */
7024
7591
  pluralize: function(word) {
7025
- return this.inflect(word, this.rules.plurals);
7592
+ return this.inflect(word, this.rules.plurals, this.rules.irregular);
7026
7593
  },
7027
7594
 
7595
+ /**
7596
+ @method singularize
7597
+ @param {String} word
7598
+ */
7028
7599
  singularize: function(word) {
7029
- return this.inflect(word, this.rules.singular);
7600
+ return this.inflect(word, this.rules.singular, this.rules.irregularInverse);
7030
7601
  },
7031
7602
 
7032
- inflect: function(word, typeRules) {
7603
+ /**
7604
+ @protected
7605
+
7606
+ @method inflect
7607
+ @param {String} word
7608
+ @param {Object} typeRules
7609
+ @param {Object} irregular
7610
+ */
7611
+ inflect: function(word, typeRules, irregular) {
7033
7612
  var inflection, substitution, result, lowercase, isBlank,
7034
7613
  isUncountable, isIrregular, isIrregularInverse, rule;
7035
7614
 
@@ -7047,18 +7626,12 @@ Inflector.prototype = {
7047
7626
  return word;
7048
7627
  }
7049
7628
 
7050
- isIrregular = this.rules.irregular[lowercase];
7629
+ isIrregular = irregular && irregular[lowercase];
7051
7630
 
7052
7631
  if (isIrregular) {
7053
7632
  return isIrregular;
7054
7633
  }
7055
7634
 
7056
- isIrregularInverse = this.rules.irregularInverse[lowercase];
7057
-
7058
- if (isIrregularInverse) {
7059
- return isIrregularInverse;
7060
- }
7061
-
7062
7635
  for (var i = typeRules.length, min = 0; i > min; i--) {
7063
7636
  inflection = typeRules[i-1];
7064
7637
  rule = inflection[0];
@@ -7170,7 +7743,7 @@ Ember.Inflector.defaultRules = {
7170
7743
 
7171
7744
 
7172
7745
  (function() {
7173
- if (Ember.EXTEND_PROTOTYPES) {
7746
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
7174
7747
  /**
7175
7748
  See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}}
7176
7749
 
@@ -7203,6 +7776,363 @@ Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
7203
7776
 
7204
7777
 
7205
7778
 
7779
+ (function() {
7780
+
7781
+ })();
7782
+
7783
+ (function() {
7784
+ /**
7785
+ @module ember-data
7786
+ */
7787
+
7788
+ var get = Ember.get;
7789
+ var forEach = Ember.EnumerableUtils.forEach;
7790
+
7791
+ DS.ActiveModelSerializer = DS.RESTSerializer.extend({
7792
+ // SERIALIZE
7793
+
7794
+ /**
7795
+ Converts camelcased attributes to underscored when serializing.
7796
+
7797
+ @method keyForAttribute
7798
+ @param {String} attribute
7799
+ @returns String
7800
+ */
7801
+ keyForAttribute: function(attr) {
7802
+ return Ember.String.decamelize(attr);
7803
+ },
7804
+
7805
+ /**
7806
+ Underscores relationship names and appends "_id" or "_ids" when serializing
7807
+ relationship keys.
7808
+
7809
+ @method keyForRelationship
7810
+ @param {String} key
7811
+ @param {String} kind
7812
+ @returns String
7813
+ */
7814
+ keyForRelationship: function(key, kind) {
7815
+ key = Ember.String.decamelize(key);
7816
+ if (kind === "belongsTo") {
7817
+ return key + "_id";
7818
+ } else if (kind === "hasMany") {
7819
+ return Ember.String.singularize(key) + "_ids";
7820
+ } else {
7821
+ return key;
7822
+ }
7823
+ },
7824
+
7825
+ /**
7826
+ Serialize has-may relationship when it is configured as embedded objects.
7827
+
7828
+ @method serializeHasMany
7829
+ */
7830
+ serializeHasMany: function(record, json, relationship) {
7831
+ var key = relationship.key,
7832
+ attrs = get(this, 'attrs'),
7833
+ embed = attrs && attrs[key] && attrs[key].embedded === 'always';
7834
+
7835
+ if (embed) {
7836
+ json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
7837
+ var data = relation.serialize(),
7838
+ primaryKey = get(this, 'primaryKey');
7839
+
7840
+ data[primaryKey] = get(relation, primaryKey);
7841
+
7842
+ return data;
7843
+ }, this);
7844
+ }
7845
+ },
7846
+
7847
+ /**
7848
+ Underscores the JSON root keys when serializing.
7849
+
7850
+ @method serializeIntoHash
7851
+ @param {Object} hash
7852
+ @param {subclass of DS.Model} type
7853
+ @param {DS.Model} record
7854
+ @param {Object} options
7855
+ */
7856
+ serializeIntoHash: function(data, type, record, options) {
7857
+ var root = Ember.String.decamelize(type.typeKey);
7858
+ data[root] = this.serialize(record, options);
7859
+ },
7860
+
7861
+ /**
7862
+ Serializes a polymorphic type as a fully capitalized model name.
7863
+
7864
+ @method serializePolymorphicType
7865
+ @param {DS.Model} record
7866
+ @param {Object} json
7867
+ @param relationship
7868
+ */
7869
+ serializePolymorphicType: function(record, json, relationship) {
7870
+ var key = relationship.key,
7871
+ belongsTo = get(record, key);
7872
+ key = this.keyForAttribute(key);
7873
+ json[key + "_type"] = Ember.String.capitalize(belongsTo.constructor.typeKey);
7874
+ },
7875
+
7876
+ // EXTRACT
7877
+
7878
+ /**
7879
+ Extracts the model typeKey from underscored root objects.
7880
+
7881
+ @method typeForRoot
7882
+ @param {String} root
7883
+ @returns String the model's typeKey
7884
+ */
7885
+ typeForRoot: function(root) {
7886
+ var camelized = Ember.String.camelize(root);
7887
+ return Ember.String.singularize(camelized);
7888
+ },
7889
+
7890
+ /**
7891
+ Normalize the polymorphic type from the JSON.
7892
+
7893
+ Normalize:
7894
+ ```js
7895
+ {
7896
+ id: "1"
7897
+ minion: { type: "evil_minion", id: "12"}
7898
+ }
7899
+ ```
7900
+
7901
+ To:
7902
+ ```js
7903
+ {
7904
+ id: "1"
7905
+ minion: { type: "evilMinion", id: "12"}
7906
+ }
7907
+ ```
7908
+
7909
+ @method normalizeRelationships
7910
+ @private
7911
+ */
7912
+ normalizeRelationships: function(type, hash) {
7913
+ var payloadKey, payload;
7914
+
7915
+ if (this.keyForRelationship) {
7916
+ type.eachRelationship(function(key, relationship) {
7917
+ if (relationship.options.polymorphic) {
7918
+ payloadKey = this.keyForAttribute(key);
7919
+ payload = hash[payloadKey];
7920
+ if (payload && payload.type) {
7921
+ payload.type = this.typeForRoot(payload.type);
7922
+ } else if (payload && relationship.kind === "hasMany") {
7923
+ var self = this;
7924
+ forEach(payload, function(single) {
7925
+ single.type = self.typeForRoot(single.type);
7926
+ });
7927
+ }
7928
+ } else {
7929
+ payloadKey = this.keyForRelationship(key, relationship.kind);
7930
+ payload = hash[payloadKey];
7931
+ }
7932
+
7933
+ hash[key] = payload;
7934
+
7935
+ if (key !== payloadKey) {
7936
+ delete hash[payloadKey];
7937
+ }
7938
+ }, this);
7939
+ }
7940
+ },
7941
+
7942
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
7943
+ var root = this.keyForAttribute(primaryType.typeKey),
7944
+ partial = payload[root];
7945
+
7946
+ updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
7947
+
7948
+ return this._super(store, primaryType, payload, recordId, requestType);
7949
+ },
7950
+
7951
+ extractArray: function(store, type, payload) {
7952
+ var root = this.keyForAttribute(type.typeKey),
7953
+ partials = payload[Ember.String.pluralize(root)];
7954
+
7955
+ forEach(partials, function(partial) {
7956
+ updatePayloadWithEmbedded(store, this, type, partial, payload);
7957
+ }, this);
7958
+
7959
+ return this._super(store, type, payload);
7960
+ }
7961
+ });
7962
+
7963
+ function updatePayloadWithEmbedded(store, serializer, type, partial, payload) {
7964
+ var attrs = get(serializer, 'attrs');
7965
+
7966
+ if (!attrs) {
7967
+ return;
7968
+ }
7969
+
7970
+ type.eachRelationship(function(key, relationship) {
7971
+ var expandedKey, embeddedTypeKey, attribute, ids,
7972
+ config = attrs[key],
7973
+ serializer = store.serializerFor(relationship.type.typeKey),
7974
+ primaryKey = get(serializer, "primaryKey");
7975
+
7976
+ if (relationship.kind !== "hasMany") {
7977
+ return;
7978
+ }
7979
+
7980
+ if (config && (config.embedded === 'always' || config.embedded === 'load')) {
7981
+ // underscore forces the embedded records to be side loaded.
7982
+ // it is needed when main type === relationship.type
7983
+ embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey);
7984
+ expandedKey = this.keyForRelationship(key, relationship.kind);
7985
+ attribute = this.keyForAttribute(key);
7986
+ ids = [];
7987
+
7988
+ if (!partial[attribute]) {
7989
+ return;
7990
+ }
7991
+
7992
+ payload[embeddedTypeKey] = payload[embeddedTypeKey] || [];
7993
+
7994
+ forEach(partial[attribute], function(data) {
7995
+ var embeddedType = store.modelFor(relationship.type.typeKey);
7996
+ updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload);
7997
+ ids.push(data[primaryKey]);
7998
+ payload[embeddedTypeKey].push(data);
7999
+ });
8000
+
8001
+ partial[expandedKey] = ids;
8002
+ delete partial[attribute];
8003
+ }
8004
+ }, serializer);
8005
+ }
8006
+
8007
+ })();
8008
+
8009
+
8010
+
8011
+ (function() {
8012
+ /**
8013
+ @module ember-data
8014
+ */
8015
+
8016
+ var forEach = Ember.EnumerableUtils.forEach;
8017
+
8018
+ /**
8019
+ The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate
8020
+ with a JSON API that uses an underscored naming convention instead of camelcasing.
8021
+ It has been designed to work out of the box with the
8022
+ [active_model_serializers](http://github.com/rails-api/active_model_serializers)
8023
+ Ruby gem.
8024
+
8025
+ ## JSON Structure
8026
+
8027
+ The ActiveModelAdapter expects the JSON returned from your server to follow
8028
+ the REST adapter conventions substituting underscored keys for camelcased ones.
8029
+
8030
+ ### Conventional Names
8031
+
8032
+ Attribute names in your JSON payload should be the underscored versions of
8033
+ the attributes in your Ember.js models.
8034
+
8035
+ For example, if you have a `Person` model:
8036
+
8037
+ ```js
8038
+ App.FamousPerson = DS.Model.extend({
8039
+ firstName: DS.attr('string'),
8040
+ lastName: DS.attr('string'),
8041
+ occupation: DS.attr('string')
8042
+ });
8043
+ ```
8044
+
8045
+ The JSON returned should look like this:
8046
+
8047
+ ```js
8048
+ {
8049
+ "famous_person": {
8050
+ "first_name": "Barack",
8051
+ "last_name": "Obama",
8052
+ "occupation": "President"
8053
+ }
8054
+ }
8055
+ ```
8056
+
8057
+ @class ActiveModelAdapter
8058
+ @constructor
8059
+ @namespace DS
8060
+ @extends DS.Adapter
8061
+ **/
8062
+
8063
+ DS.ActiveModelAdapter = DS.RESTAdapter.extend({
8064
+ defaultSerializer: '_ams',
8065
+ /**
8066
+ The ActiveModelAdapter overrides the `pathForType` method
8067
+ to build underscored URLs.
8068
+
8069
+ ```js
8070
+ this.pathForType("famousPerson");
8071
+ //=> "famous_people"
8072
+ ```
8073
+
8074
+ @method pathForType
8075
+ @param {String} type
8076
+ @returns String
8077
+ */
8078
+ pathForType: function(type) {
8079
+ var decamelized = Ember.String.decamelize(type);
8080
+ return Ember.String.pluralize(decamelized);
8081
+ },
8082
+
8083
+ /**
8084
+ The ActiveModelAdapter overrides the `ajaxError` method
8085
+ to return a DS.InvalidError for all 422 Unprocessable Entity
8086
+ responses.
8087
+
8088
+ @method ajaxError
8089
+ @param jqXHR
8090
+ @returns error
8091
+ */
8092
+ ajaxError: function(jqXHR) {
8093
+ var error = this._super(jqXHR);
8094
+
8095
+ if (jqXHR && jqXHR.status === 422) {
8096
+ var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"],
8097
+ errors = {};
8098
+
8099
+ forEach(Ember.keys(jsonErrors), function(key) {
8100
+ errors[Ember.String.camelize(key)] = jsonErrors[key];
8101
+ });
8102
+
8103
+ return new DS.InvalidError(errors);
8104
+ } else {
8105
+ return error;
8106
+ }
8107
+ }
8108
+ });
8109
+
8110
+ })();
8111
+
8112
+
8113
+
8114
+ (function() {
8115
+
8116
+ })();
8117
+
8118
+
8119
+
8120
+ (function() {
8121
+ Ember.onLoad('Ember.Application', function(Application) {
8122
+ Application.initializer({
8123
+ name: "activeModelAdapter",
8124
+
8125
+ initialize: function(container, application) {
8126
+ application.register('serializer:_ams', DS.ActiveModelSerializer);
8127
+ application.register('adapter:_ams', DS.ActiveModelAdapter);
8128
+ }
8129
+ });
8130
+ });
8131
+
8132
+ })();
8133
+
8134
+
8135
+
7206
8136
  (function() {
7207
8137
 
7208
8138
  })();