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

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.
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
  })();