ember-data-source 0.0.5 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
data/dist/ember-data.js CHANGED
@@ -1,10 +1,111 @@
1
- // Last commit: 73ad70c (2013-02-23 22:52:20 -0800)
1
+ // Last commit: 07fbd58 (2013-06-01 14:33:06 -0400)
2
2
 
3
3
 
4
4
  (function() {
5
- window.DS = Ember.Namespace.create({
6
- // this one goes to 11
7
- CURRENT_API_REVISION: 11
5
+ var define, requireModule;
6
+
7
+ (function() {
8
+ var registry = {}, seen = {};
9
+
10
+ define = function(name, deps, callback) {
11
+ registry[name] = { deps: deps, callback: callback };
12
+ };
13
+
14
+ requireModule = function(name) {
15
+ if (seen[name]) { return seen[name]; }
16
+ seen[name] = {};
17
+
18
+ var mod = registry[name],
19
+ deps = mod.deps,
20
+ callback = mod.callback,
21
+ reified = [],
22
+ exports;
23
+
24
+ for (var i=0, l=deps.length; i<l; i++) {
25
+ if (deps[i] === 'exports') {
26
+ reified.push(exports = {});
27
+ } else {
28
+ reified.push(requireModule(deps[i]));
29
+ }
30
+ }
31
+
32
+ var value = callback.apply(this, reified);
33
+ return seen[name] = exports || value;
34
+ };
35
+ })();
36
+ (function() {
37
+ /**
38
+ @module data
39
+ @main data
40
+ */
41
+
42
+ /**
43
+ All Ember Data methods and functions are defined inside of this namespace.
44
+
45
+ @class DS
46
+ @static
47
+ */
48
+
49
+ window.DS = Ember.Namespace.create();
50
+
51
+ })();
52
+
53
+
54
+
55
+ (function() {
56
+ var set = Ember.set;
57
+
58
+ /**
59
+ This code registers an injection for Ember.Application.
60
+
61
+ If an Ember.js developer defines a subclass of DS.Store on their application,
62
+ this code will automatically instantiate it and make it available on the
63
+ router.
64
+
65
+ Additionally, after an application's controllers have been injected, they will
66
+ each have the store made available to them.
67
+
68
+ For example, imagine an Ember.js application with the following classes:
69
+
70
+ App.Store = DS.Store.extend({
71
+ adapter: 'App.MyCustomAdapter'
72
+ });
73
+
74
+ App.PostsController = Ember.ArrayController.extend({
75
+ // ...
76
+ });
77
+
78
+ When the application is initialized, `App.Store` will automatically be
79
+ instantiated, and the instance of `App.PostsController` will have its `store`
80
+ property set to that instance.
81
+
82
+ Note that this code will only be run if the `ember-application` package is
83
+ loaded. If Ember Data is being used in an environment other than a
84
+ typical application (e.g., node.js where only `ember-runtime` is available),
85
+ this code will be ignored.
86
+ */
87
+
88
+ Ember.onLoad('Ember.Application', function(Application) {
89
+ Application.initializer({
90
+ name: "store",
91
+
92
+ initialize: function(container, application) {
93
+ application.register('store:main', application.Store);
94
+
95
+ // Eagerly generate the store so defaultStore is populated.
96
+ // TODO: Do this in a finisher hook
97
+ container.lookup('store:main');
98
+ }
99
+ });
100
+
101
+ Application.initializer({
102
+ name: "injectStore",
103
+
104
+ initialize: function(container, application) {
105
+ application.inject('controller', 'store', 'store:main');
106
+ application.inject('route', 'store', 'store:main');
107
+ }
108
+ });
8
109
  });
9
110
 
10
111
  })();
@@ -12,18 +113,81 @@ window.DS = Ember.Namespace.create({
12
113
 
13
114
 
14
115
  (function() {
15
- var DeferredMixin = Ember.DeferredMixin, // ember-runtime/mixins/deferred
16
- Evented = Ember.Evented, // ember-runtime/mixins/evented
116
+ /**
117
+ * Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
118
+ * © 2011 Colin Snover <http://zetafleet.com>
119
+ * Released under MIT license.
120
+ */
121
+
122
+ Ember.Date = Ember.Date || {};
123
+
124
+ var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
125
+ Ember.Date.parse = function (date) {
126
+ var timestamp, struct, minutesOffset = 0;
127
+
128
+ // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
129
+ // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
130
+ // implementations could be faster
131
+ // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
132
+ if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
133
+ // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
134
+ for (var i = 0, k; (k = numericKeys[i]); ++i) {
135
+ struct[k] = +struct[k] || 0;
136
+ }
137
+
138
+ // allow undefined days and months
139
+ struct[2] = (+struct[2] || 1) - 1;
140
+ struct[3] = +struct[3] || 1;
141
+
142
+ if (struct[8] !== 'Z' && struct[9] !== undefined) {
143
+ minutesOffset = struct[10] * 60 + struct[11];
144
+
145
+ if (struct[9] === '+') {
146
+ minutesOffset = 0 - minutesOffset;
147
+ }
148
+ }
149
+
150
+ timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
151
+ }
152
+ else {
153
+ timestamp = origParse ? origParse(date) : NaN;
154
+ }
155
+
156
+ return timestamp;
157
+ };
158
+
159
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) {
160
+ Date.parse = Ember.Date.parse;
161
+ }
162
+
163
+ })();
164
+
165
+
166
+
167
+ (function() {
168
+
169
+ })();
170
+
171
+
172
+
173
+ (function() {
174
+ var Evented = Ember.Evented, // ember-runtime/mixins/evented
175
+ Deferred = Ember.DeferredMixin, // ember-runtime/mixins/evented
17
176
  run = Ember.run, // ember-metal/run-loop
18
177
  get = Ember.get; // ember-metal/accessors
19
178
 
20
- var LoadPromise = Ember.Mixin.create(Evented, DeferredMixin, {
179
+ var LoadPromise = Ember.Mixin.create(Evented, Deferred, {
21
180
  init: function() {
22
181
  this._super.apply(this, arguments);
23
- this.one('didLoad', function() {
182
+
183
+ this.one('didLoad', this, function() {
24
184
  run(this, 'resolve', this);
25
185
  });
26
186
 
187
+ this.one('becameError', this, function() {
188
+ run(this, 'reject', this);
189
+ });
190
+
27
191
  if (get(this, 'isLoaded')) {
28
192
  this.trigger('didLoad');
29
193
  }
@@ -37,6 +201,9 @@ DS.LoadPromise = LoadPromise;
37
201
 
38
202
 
39
203
  (function() {
204
+ /**
205
+ */
206
+
40
207
  var get = Ember.get, set = Ember.set;
41
208
 
42
209
  var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
@@ -47,9 +214,19 @@ var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
47
214
  time. You should not create record arrays yourself. Instead, an instance of
48
215
  DS.RecordArray or its subclasses will be returned by your application's store
49
216
  in response to queries.
217
+
218
+ @module data
219
+ @submodule data-record-array
220
+ @main data-record-array
221
+
222
+ @class RecordArray
223
+ @namespace DS
224
+ @extends Ember.ArrayProxy
225
+ @uses Ember.Evented
226
+ @uses DS.LoadPromise
50
227
  */
51
228
 
52
- DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, LoadPromise, {
229
+ DS.RecordArray = Ember.ArrayProxy.extend(LoadPromise, {
53
230
  /**
54
231
  The model type contained by this record array.
55
232
 
@@ -111,8 +288,19 @@ DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, LoadPromise, {
111
288
 
112
289
 
113
290
  (function() {
291
+ /**
292
+ @module data
293
+ @submodule data-record-array
294
+ */
295
+
114
296
  var get = Ember.get;
115
297
 
298
+ /**
299
+ @class FilteredRecordArray
300
+ @namespace DS
301
+ @extends DS.RecordArray
302
+ @constructor
303
+ */
116
304
  DS.FilteredRecordArray = DS.RecordArray.extend({
117
305
  filterFunction: null,
118
306
  isLoaded: true,
@@ -123,8 +311,8 @@ DS.FilteredRecordArray = DS.RecordArray.extend({
123
311
  },
124
312
 
125
313
  updateFilter: Ember.observer(function() {
126
- var store = get(this, 'store');
127
- store.updateRecordArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
314
+ var manager = get(this, 'manager');
315
+ manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction'));
128
316
  }, 'filterFunction')
129
317
  });
130
318
 
@@ -133,8 +321,19 @@ DS.FilteredRecordArray = DS.RecordArray.extend({
133
321
 
134
322
 
135
323
  (function() {
324
+ /**
325
+ @module data
326
+ @submodule data-record-array
327
+ */
328
+
136
329
  var get = Ember.get, set = Ember.set;
137
330
 
331
+ /**
332
+ @class AdapterPopulatedRecordArray
333
+ @namespace DS
334
+ @extends DS.RecordArray
335
+ @constructor
336
+ */
138
337
  DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
139
338
  query: null,
140
339
 
@@ -144,18 +343,13 @@ DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
144
343
  },
145
344
 
146
345
  load: function(references) {
147
- var store = get(this, 'store'), type = get(this, 'type');
148
-
149
- this.beginPropertyChanges();
150
- set(this, 'content', Ember.A(references));
151
- set(this, 'isLoaded', true);
152
- this.endPropertyChanges();
346
+ this.setProperties({
347
+ content: Ember.A(references),
348
+ isLoaded: true
349
+ });
153
350
 
154
- var self = this;
155
351
  // TODO: does triggering didLoad event should be the last action of the runLoop?
156
- Ember.run.once(function() {
157
- self.trigger('didLoad');
158
- });
352
+ Ember.run.once(this, 'trigger', 'didLoad');
159
353
  }
160
354
  });
161
355
 
@@ -164,6 +358,11 @@ DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
164
358
 
165
359
 
166
360
  (function() {
361
+ /**
362
+ @module data
363
+ @submodule data-record-array
364
+ */
365
+
167
366
  var get = Ember.get, set = Ember.set;
168
367
 
169
368
  /**
@@ -195,6 +394,11 @@ var get = Ember.get, set = Ember.set;
195
394
 
196
395
  We call the record to which a relationship belongs the
197
396
  relationship's _owner_.
397
+
398
+ @class ManyArray
399
+ @namespace DS
400
+ @extends DS.RecordArray
401
+ @constructor
198
402
  */
199
403
  DS.ManyArray = DS.RecordArray.extend({
200
404
  init: function() {
@@ -211,6 +415,15 @@ DS.ManyArray = DS.RecordArray.extend({
211
415
  */
212
416
  owner: null,
213
417
 
418
+ /**
419
+ @private
420
+
421
+ `true` if the relationship is polymorphic, `false` otherwise.
422
+
423
+ @property {Boolean}
424
+ */
425
+ isPolymorphic: false,
426
+
214
427
  // LOADING STATE
215
428
 
216
429
  isLoaded: false,
@@ -230,17 +443,16 @@ DS.ManyArray = DS.RecordArray.extend({
230
443
  fetch: function() {
231
444
  var references = get(this, 'content'),
232
445
  store = get(this, 'store'),
233
- type = get(this, 'type'),
234
446
  owner = get(this, 'owner');
235
447
 
236
- store.fetchUnloadedReferences(type, references, owner);
448
+ store.fetchUnloadedReferences(references, owner);
237
449
  },
238
450
 
239
451
  // Overrides Ember.Array's replace method to implement
240
452
  replaceContent: function(index, removed, added) {
241
453
  // Map the array of record objects into an array of client ids.
242
454
  added = added.map(function(record) {
243
- Ember.assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this relationship.", !get(this, 'type') || (get(this, 'type') === record.constructor));
455
+ Ember.assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this relationship.", !get(this, 'type') || (get(this, 'type').detectInstance(record)) );
244
456
  return get(record, '_reference');
245
457
  }, this);
246
458
 
@@ -328,6 +540,8 @@ DS.ManyArray = DS.RecordArray.extend({
328
540
  type = get(this, 'type'),
329
541
  record;
330
542
 
543
+ Ember.assert("You can not create records of " + (get(this, 'type') && get(this, 'type').toString()) + " on this polymorphic relationship.", !get(this, 'isPolymorphic'));
544
+
331
545
  transaction = transaction || get(owner, 'transaction');
332
546
 
333
547
  record = store.createRecord.call(store, type, hash, transaction);
@@ -343,14 +557,22 @@ DS.ManyArray = DS.RecordArray.extend({
343
557
 
344
558
 
345
559
  (function() {
560
+ /**
561
+ @module data
562
+ @submodule data-record-array
563
+ */
346
564
 
347
565
  })();
348
566
 
349
567
 
350
568
 
351
569
  (function() {
352
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt,
353
- removeObject = Ember.EnumerableUtils.removeObject, forEach = Ember.EnumerableUtils.forEach;
570
+ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
571
+
572
+ /**
573
+ @module data
574
+ @submodule data-transaction
575
+ */
354
576
 
355
577
  /**
356
578
  A transaction allows you to collect multiple records into a unit of work
@@ -430,8 +652,6 @@ var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt,
430
652
  calling commit.
431
653
  */
432
654
 
433
- var arrayDefault = function() { return []; };
434
-
435
655
  DS.Transaction = Ember.Object.extend({
436
656
  /**
437
657
  @private
@@ -440,15 +660,7 @@ DS.Transaction = Ember.Object.extend({
440
660
  type.
441
661
  */
442
662
  init: function() {
443
- set(this, 'buckets', {
444
- clean: Ember.OrderedSet.create(),
445
- created: Ember.OrderedSet.create(),
446
- updated: Ember.OrderedSet.create(),
447
- deleted: Ember.OrderedSet.create(),
448
- inflight: Ember.OrderedSet.create()
449
- });
450
-
451
- set(this, 'relationships', Ember.OrderedSet.create());
663
+ set(this, 'records', Ember.OrderedSet.create());
452
664
  },
453
665
 
454
666
  /**
@@ -475,11 +687,11 @@ DS.Transaction = Ember.Object.extend({
475
687
 
476
688
  isDefault: Ember.computed(function() {
477
689
  return this === get(this, 'store.defaultTransaction');
478
- }),
690
+ }).volatile(),
479
691
 
480
692
  /**
481
693
  Adds an existing record to this transaction. Only records without
482
- modficiations (i.e., records whose `isDirty` property is `false`)
694
+ modificiations (i.e., records whose `isDirty` property is `false`)
483
695
  can be added to a transaction.
484
696
 
485
697
  @param {DS.Model} record the record to add to the transaction
@@ -487,29 +699,57 @@ DS.Transaction = Ember.Object.extend({
487
699
  add: function(record) {
488
700
  Ember.assert("You must pass a record into transaction.add()", record instanceof DS.Model);
489
701
 
490
- var recordTransaction = get(record, 'transaction'),
491
- defaultTransaction = get(this, 'store.defaultTransaction');
702
+ var store = get(this, 'store');
703
+ var adapter = get(store, '_adapter');
704
+ var serializer = get(adapter, 'serializer');
705
+ serializer.eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
706
+ if (embeddedType === 'load') { return; }
707
+
708
+ this.add(embeddedRecord);
709
+ }, this);
710
+
711
+ this.adoptRecord(record);
712
+ },
492
713
 
493
- // Make `add` idempotent
494
- if (recordTransaction === this) { return; }
714
+ relationships: Ember.computed(function() {
715
+ var relationships = Ember.OrderedSet.create(),
716
+ records = get(this, 'records'),
717
+ store = get(this, 'store');
495
718
 
496
- // XXX it should be possible to move a dirty transaction from the default transaction
719
+ records.forEach(function(record) {
720
+ var reference = get(record, '_reference');
721
+ var changes = store.relationshipChangesFor(reference);
722
+ for(var i = 0; i < changes.length; i++) {
723
+ relationships.add(changes[i]);
724
+ }
725
+ });
497
726
 
498
- // we could probably make this work if someone has a valid use case. Do you?
499
- Ember.assert("Once a record has changed, you cannot move it into a different transaction", !get(record, 'isDirty'));
727
+ return relationships;
728
+ }).volatile(),
500
729
 
501
- Ember.assert("Models cannot belong to more than one transaction at a time.", recordTransaction === defaultTransaction);
730
+ commitDetails: Ember.computed(function() {
731
+ var commitDetails = Ember.MapWithDefault.create({
732
+ defaultValue: function() {
733
+ return {
734
+ created: Ember.OrderedSet.create(),
735
+ updated: Ember.OrderedSet.create(),
736
+ deleted: Ember.OrderedSet.create()
737
+ };
738
+ }
739
+ });
502
740
 
503
- this.adoptRecord(record);
504
- },
741
+ var records = get(this, 'records'),
742
+ store = get(this, 'store');
505
743
 
506
- relationshipBecameDirty: function(relationship) {
507
- get(this, 'relationships').add(relationship);
508
- },
744
+ records.forEach(function(record) {
745
+ if(!get(record, 'isDirty')) return;
746
+ record.send('willCommit');
747
+ var adapter = store.adapterForType(record.constructor);
748
+ commitDetails.get(adapter)[get(record, 'dirtyType')].add(record);
749
+ });
509
750
 
510
- relationshipBecameClean: function(relationship) {
511
- get(this, 'relationships').remove(relationship);
512
- },
751
+ return commitDetails;
752
+ }).volatile(),
513
753
 
514
754
  /**
515
755
  Commits the transaction, which causes all of the modified records that
@@ -522,36 +762,22 @@ DS.Transaction = Ember.Object.extend({
522
762
  */
523
763
  commit: function() {
524
764
  var store = get(this, 'store');
525
- var adapter = get(store, '_adapter');
526
- var defaultTransaction = get(store, 'defaultTransaction');
527
-
528
- var iterate = function(records) {
529
- var set = records.copy();
530
- set.forEach(function (record) {
531
- record.send('willCommit');
532
- });
533
- return set;
534
- };
535
-
536
- var relationships = get(this, 'relationships');
537
-
538
- var commitDetails = {
539
- created: iterate(this.bucketForType('created')),
540
- updated: iterate(this.bucketForType('updated')),
541
- deleted: iterate(this.bucketForType('deleted')),
542
- relationships: relationships
543
- };
544
765
 
545
- if (this === defaultTransaction) {
766
+ if (get(this, 'isDefault')) {
546
767
  set(store, 'defaultTransaction', store.transaction());
547
768
  }
548
769
 
549
770
  this.removeCleanRecords();
550
771
 
551
- if (!commitDetails.created.isEmpty() || !commitDetails.updated.isEmpty() || !commitDetails.deleted.isEmpty() || !relationships.isEmpty()) {
552
- if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
553
- else { throw fmt("Adapter is either null or does not implement `commit` method", this); }
554
- }
772
+ var commitDetails = get(this, 'commitDetails'),
773
+ relationships = get(this, 'relationships');
774
+
775
+ commitDetails.forEach(function(adapter, commitDetails) {
776
+ Ember.assert("You tried to commit records but you have no adapter", adapter);
777
+ Ember.assert("You tried to commit records but your adapter does not implement `commit`", adapter.commit);
778
+
779
+ adapter.commit(store, commitDetails);
780
+ });
555
781
 
556
782
  // Once we've committed the transaction, there is no need to
557
783
  // keep the OneToManyChanges around. Destroy them so they
@@ -575,21 +801,38 @@ DS.Transaction = Ember.Object.extend({
575
801
  current transaction should not be used again.
576
802
  */
577
803
  rollback: function() {
578
- // Loop through all of the records in each of the dirty states
579
- // and initiate a rollback on them. As a side effect of telling
580
- // the record to roll back, it should also move itself out of
581
- // the dirty bucket and into the clean bucket.
582
- ['created', 'updated', 'deleted', 'inflight'].forEach(function(bucketType) {
583
- var records = this.bucketForType(bucketType);
584
- forEach(records, function(record) {
585
- record.send('rollback');
586
- });
587
- records.clear();
588
- }, this);
804
+ var store = get(this, 'store');
805
+
806
+ // Destroy all relationship changes and compute
807
+ // all references affected
808
+ var references = Ember.OrderedSet.create();
809
+ var relationships = get(this, 'relationships');
810
+ relationships.forEach(function(r) {
811
+ references.add(r.firstRecordReference);
812
+ references.add(r.secondRecordReference);
813
+ r.destroy();
814
+ });
815
+
816
+ var records = get(this, 'records');
817
+ records.forEach(function(record) {
818
+ if (!record.get('isDirty')) return;
819
+ record.send('rollback');
820
+ });
589
821
 
590
822
  // Now that all records in the transaction are guaranteed to be
591
823
  // clean, migrate them all to the store's default transaction.
592
824
  this.removeCleanRecords();
825
+
826
+ // Remaining associated references are not part of the transaction, but
827
+ // can still have hasMany's which have not been reloaded
828
+ references.forEach(function(r) {
829
+ if (r && r.record) {
830
+ var record = r.record;
831
+ record.suspendRelationshipObservers(function() {
832
+ record.reloadHasManys();
833
+ });
834
+ }
835
+ }, this);
593
836
  },
594
837
 
595
838
  /**
@@ -615,27 +858,12 @@ DS.Transaction = Ember.Object.extend({
615
858
  Removes all of the records in the transaction's clean bucket.
616
859
  */
617
860
  removeCleanRecords: function() {
618
- var clean = this.bucketForType('clean');
619
- clean.forEach(function(record) {
620
- this.remove(record);
621
- }, this);
622
- clean.clear();
623
- },
624
-
625
- /**
626
- @private
627
-
628
- Returns the bucket for the given bucket type. For example, you might call
629
- `this.bucketForType('updated')` to get the `Ember.Map` that contains all
630
- of the records that have changes pending.
631
-
632
- @param {String} bucketType the type of bucket
633
- @returns Ember.Map
634
- */
635
- bucketForType: function(bucketType) {
636
- var buckets = get(this, 'buckets');
637
-
638
- return get(buckets, bucketType);
861
+ var records = get(this, 'records');
862
+ records.forEach(function(record) {
863
+ if(!record.get('isDirty')) {
864
+ this.remove(record);
865
+ }
866
+ }, this);
639
867
  },
640
868
 
641
869
  /**
@@ -656,81 +884,51 @@ DS.Transaction = Ember.Object.extend({
656
884
  var oldTransaction = get(record, 'transaction');
657
885
 
658
886
  if (oldTransaction) {
659
- oldTransaction.removeFromBucket('clean', record);
887
+ oldTransaction.removeRecord(record);
660
888
  }
661
889
 
662
- this.addToBucket('clean', record);
890
+ get(this, 'records').add(record);
663
891
  set(record, 'transaction', this);
664
892
  },
665
893
 
666
894
  /**
667
- @private
668
-
669
- Adds a record to the named bucket.
670
-
671
- @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
672
- */
673
- addToBucket: function(bucketType, record) {
674
- this.bucketForType(bucketType).add(record);
675
- },
676
-
677
- /**
678
- @private
679
-
680
- Removes a record from the named bucket.
681
-
682
- @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
683
- */
684
- removeFromBucket: function(bucketType, record) {
685
- this.bucketForType(bucketType).remove(record);
686
- },
687
-
688
- /**
689
- @private
690
-
691
- Called by a record's state manager to indicate that the record has entered
692
- a dirty state. The record will be moved from the `clean` bucket and into
693
- the appropriate dirty bucket.
895
+ @private
694
896
 
695
- @param {String} bucketType one of `created`, `updated`, or `deleted`
897
+ Removes the record without performing the normal checks
898
+ to ensure that the record is re-added to the store's
899
+ default transaction.
696
900
  */
697
- recordBecameDirty: function(bucketType, record) {
698
- this.removeFromBucket('clean', record);
699
- this.addToBucket(bucketType, record);
700
- },
701
-
702
- /**
703
- @private
704
-
705
- Called by a record's state manager to indicate that the record has entered
706
- inflight state. The record will be moved from its current dirty bucket and into
707
- the `inflight` bucket.
901
+ removeRecord: function(record) {
902
+ get(this, 'records').remove(record);
903
+ }
708
904
 
709
- @param {String} bucketType one of `created`, `updated`, or `deleted`
710
- */
711
- recordBecameInFlight: function(kind, record) {
712
- this.removeFromBucket(kind, record);
713
- this.addToBucket('inflight', record);
714
- },
905
+ });
715
906
 
716
- recordIsMoving: function(kind, record) {
717
- this.removeFromBucket(kind, record);
718
- this.addToBucket('clean', record);
719
- },
907
+ DS.Transaction.reopenClass({
908
+ ensureSameTransaction: function(records){
909
+ var transactions = Ember.A();
910
+ forEach( records, function(record){
911
+ if (record){ transactions.pushObject(get(record, 'transaction')); }
912
+ });
720
913
 
721
- /**
722
- @private
914
+ var transaction = transactions.reduce(function(prev, t) {
915
+ if (!get(t, 'isDefault')) {
916
+ if (prev === null) { return t; }
917
+ Ember.assert("All records in a changed relationship must be in the same transaction. You tried to change the relationship between records when one is in " + t + " and the other is in " + prev, t === prev);
918
+ }
723
919
 
724
- Called by a record's state manager to indicate that the record has entered
725
- a clean state. The record will be moved from its current dirty or inflight bucket and into
726
- the `clean` bucket.
920
+ return prev;
921
+ }, null);
727
922
 
728
- @param {String} bucketType one of `created`, `updated`, or `deleted`
729
- */
730
- recordBecameClean: function(kind, record) {
731
- this.removeFromBucket(kind, record);
732
- this.remove(record);
733
- }
923
+ if (transaction) {
924
+ forEach( records, function(record){
925
+ if (record){ transaction.add(record); }
926
+ });
927
+ } else {
928
+ transaction = transactions.objectAt(0);
929
+ }
930
+ return transaction;
931
+ }
734
932
  });
735
933
 
736
934
  })();
@@ -738,18 +936,16 @@ DS.Transaction = Ember.Object.extend({
738
936
 
739
937
 
740
938
  (function() {
741
- var classify = Ember.String.classify, get = Ember.get;
939
+ var get = Ember.get;
742
940
 
743
941
  /**
744
- @private
745
-
746
942
  The Mappable mixin is designed for classes that would like to
747
943
  behave as a map for configuration purposes.
748
944
 
749
945
  For example, the DS.Adapter class can behave like a map, with
750
946
  more semantic API, via the `map` API:
751
947
 
752
- DS.Adapter.map('App.Person', { firstName: { keyName: 'FIRST' } });
948
+ DS.Adapter.map('App.Person', { firstName: { key: 'FIRST' } });
753
949
 
754
950
  Class configuration via a map-like API has a few common requirements
755
951
  that differentiate it from the standard Ember.Map implementation.
@@ -791,11 +987,16 @@ var classify = Ember.String.classify, get = Ember.get;
791
987
  })
792
988
  });
793
989
 
794
- The function passed to `generateMapFunctionFor` is invoked every time a
795
- new value is added to the map.
990
+ The function passed to `generateMapFunctionFor` is invoked every time a
991
+ new value is added to the map.
992
+
993
+ @class _Mappable
994
+ @private
995
+ @namespace DS
996
+ @extends Ember.Mixin
796
997
  **/
797
998
 
798
- var resolveMapConflict = function(oldValue, newValue, mappingsKey) {
999
+ var resolveMapConflict = function(oldValue, newValue) {
799
1000
  return oldValue;
800
1001
  };
801
1002
 
@@ -809,7 +1010,7 @@ var transformMapValue = function(key, value) {
809
1010
 
810
1011
  DS._Mappable = Ember.Mixin.create({
811
1012
  createInstanceMapFor: function(mapName) {
812
- var instanceMeta = Ember.metaPath(this, ['DS.Mappable'], true);
1013
+ var instanceMeta = getMappableMeta(this);
813
1014
 
814
1015
  instanceMeta.values = instanceMeta.values || {};
815
1016
 
@@ -829,7 +1030,7 @@ DS._Mappable = Ember.Mixin.create({
829
1030
  },
830
1031
 
831
1032
  _copyMap: function(mapName, klass, instanceMap) {
832
- var classMeta = Ember.metaPath(klass, ['DS.Mappable'], true);
1033
+ var classMeta = getMappableMeta(klass);
833
1034
 
834
1035
  var classMap = classMeta[mapName];
835
1036
  if (classMap) {
@@ -844,7 +1045,7 @@ DS._Mappable = Ember.Mixin.create({
844
1045
  var newValue = transformedValue;
845
1046
 
846
1047
  if (oldValue) {
847
- newValue = (this.constructor.resolveMapConflict || resolveMapConflict)(oldValue, newValue, mapName);
1048
+ newValue = (this.constructor.resolveMapConflict || resolveMapConflict)(oldValue, newValue);
848
1049
  }
849
1050
 
850
1051
  instanceMap.set(transformedKey, newValue);
@@ -856,7 +1057,8 @@ DS._Mappable = Ember.Mixin.create({
856
1057
 
857
1058
  DS._Mappable.generateMapFunctionFor = function(mapName, transform) {
858
1059
  return function(key, value) {
859
- var meta = Ember.metaPath(this, ['DS.Mappable'], true);
1060
+ var meta = getMappableMeta(this);
1061
+
860
1062
  var map = meta[mapName] || Ember.MapWithDefault.create({
861
1063
  defaultValue: function() { return {}; }
862
1064
  });
@@ -867,15 +1069,38 @@ DS._Mappable.generateMapFunctionFor = function(mapName, transform) {
867
1069
  };
868
1070
  };
869
1071
 
870
- })();
1072
+ function getMappableMeta(obj) {
1073
+ var meta = Ember.meta(obj, true),
1074
+ keyName = 'DS.Mappable',
1075
+ value = meta[keyName];
871
1076
 
1077
+ if (!value) { meta[keyName] = {}; }
872
1078
 
1079
+ if (!meta.hasOwnProperty(keyName)) {
1080
+ meta[keyName] = Ember.create(meta[keyName]);
1081
+ }
873
1082
 
874
- (function() {
875
- /*globals Ember*/
876
- /*jshint eqnull:true*/
877
- var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt, once = Ember.run.once;
1083
+ return meta[keyName];
1084
+ }
1085
+
1086
+ })();
1087
+
1088
+
1089
+
1090
+ (function() {
1091
+ /*globals Ember*/
1092
+ /*jshint eqnull:true*/
1093
+ /**
1094
+ @module data
1095
+ @submodule data-store
1096
+ */
1097
+
1098
+ var get = Ember.get, set = Ember.set;
1099
+ var once = Ember.run.once;
1100
+ var isNone = Ember.isNone;
878
1101
  var forEach = Ember.EnumerableUtils.forEach;
1102
+ var map = Ember.EnumerableUtils.map;
1103
+
879
1104
  // These values are used in the data cache when clientIds are
880
1105
  // needed but the underlying data has not yet been loaded by
881
1106
  // the server.
@@ -890,10 +1115,13 @@ var CREATED = { created: true };
890
1115
  // scheme:
891
1116
  //
892
1117
  // * +id+ means an identifier managed by an external source, provided inside
893
- // the data provided by that source.
1118
+ // the data provided by that source. These are always coerced to be strings
1119
+ // before being used internally.
894
1120
  // * +clientId+ means a transient numerical identifier generated at runtime by
895
1121
  // the data store. It is important primarily because newly created objects may
896
1122
  // not yet have an externally generated id.
1123
+ // * +reference+ means a record reference object, which holds metadata about a
1124
+ // record, even if it has not yet been fully materialized.
897
1125
  // * +type+ means a subclass of DS.Model.
898
1126
 
899
1127
  // Used by the store to normalize IDs entering the store. Despite the fact
@@ -906,32 +1134,48 @@ var coerceId = function(id) {
906
1134
  return id == null ? null : id+'';
907
1135
  };
908
1136
 
909
- var map = Ember.EnumerableUtils.map;
910
1137
 
911
1138
  /**
912
1139
  The store contains all of the data for records loaded from the server.
913
- It is also responsible for creating instances of DS.Model that wraps
1140
+ It is also responsible for creating instances of DS.Model that wrap
914
1141
  the individual data for a record, so that they can be bound to in your
915
1142
  Handlebars templates.
916
1143
 
917
- Create a new store like this:
1144
+ Define your application's store like this:
918
1145
 
919
- MyApp.store = DS.Store.create();
1146
+ MyApp.Store = DS.Store.extend();
920
1147
 
921
- You can retrieve DS.Model instances from the store in several ways. To retrieve
922
- a record for a specific id, use the `find()` method:
1148
+ Most Ember.js applications will only have a single `DS.Store` that is
1149
+ automatically created by their `Ember.Application`.
923
1150
 
924
- var record = MyApp.store.find(MyApp.Contact, 123);
1151
+ You can retrieve models from the store in several ways. To retrieve a record
1152
+ for a specific id, use `DS.Model`'s `find()` method:
925
1153
 
926
- By default, the store will talk to your backend using a standard REST mechanism.
927
- You can customize how the store talks to your backend by specifying a custom adapter:
1154
+ var person = App.Person.find(123);
1155
+
1156
+ If your application has multiple `DS.Store` instances (an unusual case), you can
1157
+ specify which store should be used:
1158
+
1159
+ var person = store.find(App.Person, 123);
1160
+
1161
+ In general, you should retrieve models using the methods on `DS.Model`; you should
1162
+ rarely need to interact with the store directly.
1163
+
1164
+ By default, the store will talk to your backend using a standard REST mechanism.
1165
+ You can customize how the store talks to your backend by specifying a custom adapter:
928
1166
 
929
1167
  MyApp.store = DS.Store.create({
930
1168
  adapter: 'MyApp.CustomAdapter'
931
1169
  });
932
1170
 
933
- You can learn more about writing a custom adapter by reading the `DS.Adapter`
934
- documentation.
1171
+ You can learn more about writing a custom adapter by reading the `DS.Adapter`
1172
+ documentation.
1173
+
1174
+ @class Store
1175
+ @namespace DS
1176
+ @extends Ember.Object
1177
+ @uses DS._Mappable
1178
+ @constructor
935
1179
  */
936
1180
  DS.Store = Ember.Object.extend(DS._Mappable, {
937
1181
 
@@ -939,40 +1183,23 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
939
1183
  Many methods can be invoked without specifying which store should be used.
940
1184
  In those cases, the first store created will be used as the default. If
941
1185
  an application has multiple stores, it should specify which store to use
942
- when performing actions, such as finding records by id.
1186
+ when performing actions, such as finding records by ID.
943
1187
 
944
1188
  The init method registers this store as the default if none is specified.
945
1189
  */
946
1190
  init: function() {
947
- // Enforce API revisioning. See BREAKING_CHANGES.md for more.
948
- var revision = get(this, 'revision');
949
-
950
- if (revision !== DS.CURRENT_API_REVISION && !Ember.ENV.TESTING) {
951
- throw new Error("Error: The Ember Data library has had breaking API changes since the last time you updated the library. Please review the list of breaking changes at https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md, then update your store's `revision` property to " + DS.CURRENT_API_REVISION);
952
- }
953
-
954
1191
  if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
955
1192
  set(DS, 'defaultStore', this);
956
1193
  }
957
1194
 
958
1195
  // internal bookkeeping; not observable
959
1196
  this.typeMaps = {};
960
- this.recordCache = [];
961
- this.clientIdToId = {};
962
- this.clientIdToType = {};
963
- this.clientIdToData = {};
964
- this.clientIdToPrematerializedData = {};
965
- this.recordArraysByClientId = {};
1197
+ this.recordArrayManager = DS.RecordArrayManager.create({
1198
+ store: this
1199
+ });
966
1200
  this.relationshipChanges = {};
967
- this.recordReferences = {};
968
-
969
- // Internally, we maintain a map of all unloaded IDs requested by
970
- // a ManyArray. As the adapter loads data into the store, the
971
- // store notifies any interested ManyArrays. When the ManyArray's
972
- // total number of loading records drops to zero, it becomes
973
- // `isLoaded` and fires a `didLoad` event.
974
- this.loadingRecordArrays = {};
975
1201
 
1202
+ set(this, 'currentTransaction', this.transaction());
976
1203
  set(this, 'defaultTransaction', this.transaction());
977
1204
  },
978
1205
 
@@ -997,31 +1224,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
997
1224
  return DS.Transaction.create({ store: this });
998
1225
  },
999
1226
 
1000
- ensureSameTransaction: function(records){
1001
- var transactions = Ember.A();
1002
- forEach( records, function(record){
1003
- if (record){ transactions.pushObject(get(record, 'transaction')); }
1004
- });
1005
-
1006
- var transaction = transactions.reduce(function(prev, t) {
1007
- if (!get(t, 'isDefault')) {
1008
- if (prev === null) { return t; }
1009
- Ember.assert("All records in a changed relationship must be in the same transaction. You tried to change the relationship between records when one is in " + t + " and the other is in " + prev, t === prev);
1010
- }
1011
-
1012
- return prev;
1013
- }, null);
1014
-
1015
- if (transaction) {
1016
- forEach( records, function(record){
1017
- if (record){ transaction.add(record); }
1018
- });
1019
- } else {
1020
- transaction = transactions.objectAt(0);
1021
- }
1022
- return transaction;
1023
-
1024
- },
1025
1227
  /**
1026
1228
  @private
1027
1229
 
@@ -1040,47 +1242,22 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1040
1242
  @param {DS.Model} record
1041
1243
  */
1042
1244
  materializeData: function(record) {
1043
- var clientId = get(record, 'clientId'),
1044
- cidToData = this.clientIdToData,
1045
- adapter = this.adapterForType(record.constructor),
1046
- data = cidToData[clientId];
1047
-
1048
- cidToData[clientId] = MATERIALIZED;
1245
+ var reference = get(record, '_reference'),
1246
+ data = reference.data,
1247
+ adapter = this.adapterForType(record.constructor);
1049
1248
 
1050
- var prematerialized = this.clientIdToPrematerializedData[clientId];
1249
+ reference.data = MATERIALIZED;
1051
1250
 
1052
- // Ensures the record's data structures are setup
1053
- // before being populated by the adapter.
1054
1251
  record.setupData();
1055
1252
 
1056
1253
  if (data !== CREATED) {
1057
1254
  // Instructs the adapter to extract information from the
1058
1255
  // opaque data and materialize the record's attributes and
1059
1256
  // relationships.
1060
- adapter.materialize(record, data, prematerialized);
1257
+ adapter.materialize(record, data, reference.prematerialized);
1061
1258
  }
1062
1259
  },
1063
1260
 
1064
- /**
1065
- @private
1066
-
1067
- Returns true if there is already a record for this clientId.
1068
-
1069
- This is used to determine whether cleanup is required, so that
1070
- "changes" to unmaterialized records do not trigger mass
1071
- materialization.
1072
-
1073
- For example, if a parent record in a relationship with a large
1074
- number of children is deleted, we want to avoid materializing
1075
- those children.
1076
-
1077
- @param {Object} reference
1078
- @return {Boolean}
1079
- */
1080
- recordIsMaterialized: function(reference) {
1081
- return !!this.recordCache[reference.clientId];
1082
- },
1083
-
1084
1261
  /**
1085
1262
  The adapter to use to communicate to a backend server or other persistence layer.
1086
1263
 
@@ -1089,7 +1266,14 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1089
1266
 
1090
1267
  @property {DS.Adapter|String}
1091
1268
  */
1092
- adapter: 'DS.RESTAdapter',
1269
+ adapter: Ember.computed(function(){
1270
+ if (!Ember.testing) {
1271
+ Ember.debug("A custom DS.Adapter was not provided as the 'Adapter' property of your application's Store. The default (DS.RESTAdapter) will be used.");
1272
+ }
1273
+
1274
+ return 'DS.RESTAdapter';
1275
+ }).property(),
1276
+
1093
1277
 
1094
1278
  /**
1095
1279
  @private
@@ -1163,6 +1347,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1163
1347
  If you want to create a record inside of a given transaction,
1164
1348
  use `transaction.createRecord()` instead of `store.createRecord()`.
1165
1349
 
1350
+ @method createRecord
1166
1351
  @param {subclass of DS.Model} type
1167
1352
  @param {Object} properties a hash of properties to set on the
1168
1353
  newly created record.
@@ -1195,15 +1380,17 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1195
1380
  // give the adapter an opportunity to generate one. Typically,
1196
1381
  // client-side ID generators will use something like uuid.js
1197
1382
  // to avoid conflicts.
1198
- var adapter;
1199
- if (Ember.isNone(id)) {
1200
- adapter = this.adapterForType(type);
1383
+
1384
+ if (isNone(id)) {
1385
+ var adapter = this.adapterForType(type);
1386
+
1201
1387
  if (adapter && adapter.generateIdForRecord) {
1202
1388
  id = coerceId(adapter.generateIdForRecord(this, record));
1203
1389
  properties.id = id;
1204
1390
  }
1205
1391
  }
1206
1392
 
1393
+ // Coerce ID to a string
1207
1394
  id = coerceId(id);
1208
1395
 
1209
1396
  // Create a new `clientId` and associate it with the
@@ -1212,24 +1399,20 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1212
1399
  // the sentinel value CREATED as the data for this
1213
1400
  // clientId. If we see this value later, we will skip
1214
1401
  // materialization.
1215
- var clientId = this.pushData(CREATED, id, type);
1402
+ var reference = this.createReference(type, id);
1403
+ reference.data = CREATED;
1216
1404
 
1217
- // Now that we have a clientId, attach it to the record we
1405
+ // Now that we have a reference, attach it to the record we
1218
1406
  // just created.
1219
- set(record, 'clientId', clientId);
1407
+ set(record, '_reference', reference);
1408
+ reference.record = record;
1220
1409
 
1221
1410
  // Move the record out of its initial `empty` state into
1222
1411
  // the `loaded` state.
1223
1412
  record.loadedData();
1224
1413
 
1225
- // Make sure the data is set up so the record doesn't
1226
- // try to materialize its nonexistent data.
1227
1414
  record.setupData();
1228
1415
 
1229
- // Store the record we just created in the record cache for
1230
- // this clientId.
1231
- this.recordCache[clientId] = record;
1232
-
1233
1416
  // Set the properties specified on the record.
1234
1417
  record.setProperties(properties);
1235
1418
 
@@ -1318,6 +1501,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1318
1501
 
1319
1502
  You can check whether a query results `RecordArray` has loaded by checking
1320
1503
  its `isLoaded` property.
1504
+
1505
+ @method find
1506
+ @param {DS.Model} type
1507
+ @param {Object|String|Integer|null} id
1321
1508
  */
1322
1509
  find: function(type, id) {
1323
1510
  if (id === undefined) {
@@ -1345,22 +1532,42 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1345
1532
  `getByReference`.
1346
1533
  */
1347
1534
  findById: function(type, id) {
1348
- var clientId = this.typeMapFor(type).idToCid[id];
1535
+ var reference;
1536
+
1537
+ if (this.hasReferenceForId(type, id)) {
1538
+ reference = this.referenceForId(type, id);
1539
+
1540
+ if (reference.data !== UNLOADED) {
1541
+ return this.recordForReference(reference);
1542
+ }
1543
+ }
1349
1544
 
1350
- if (clientId) {
1351
- return this.findByClientId(type, clientId);
1545
+ if (!reference) {
1546
+ reference = this.createReference(type, id);
1352
1547
  }
1353
1548
 
1354
- clientId = this.pushData(LOADING, id, type);
1549
+ reference.data = LOADING;
1355
1550
 
1356
1551
  // create a new instance of the model type in the
1357
1552
  // 'isLoading' state
1358
- var record = this.materializeRecord(type, clientId, id);
1553
+ var record = this.materializeRecord(reference);
1359
1554
 
1360
- // let the adapter set the data, possibly async
1361
- var adapter = this.adapterForType(type);
1362
- if (adapter && adapter.find) { adapter.find(this, type, id); }
1363
- else { throw "Adapter is either null or does not implement `find` method"; }
1555
+ if (reference.data === LOADING) {
1556
+ // let the adapter set the data, possibly async
1557
+ var adapter = this.adapterForType(type),
1558
+ store = this;
1559
+
1560
+ Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter);
1561
+ Ember.assert("You tried to find a record but your adapter does not implement `find`", adapter.find);
1562
+
1563
+ var thenable = adapter.find(this, type, id);
1564
+
1565
+ if (thenable && thenable.then) {
1566
+ thenable.then(null /* for future use */, function(error) {
1567
+ store.recordWasError(record);
1568
+ });
1569
+ }
1570
+ }
1364
1571
 
1365
1572
  return record;
1366
1573
  },
@@ -1368,56 +1575,45 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1368
1575
  reloadRecord: function(record) {
1369
1576
  var type = record.constructor,
1370
1577
  adapter = this.adapterForType(type),
1578
+ store = this,
1371
1579
  id = get(record, 'id');
1372
1580
 
1373
1581
  Ember.assert("You cannot update a record without an ID", id);
1374
1582
  Ember.assert("You tried to update a record but you have no adapter (for " + type + ")", adapter);
1375
1583
  Ember.assert("You tried to update a record but your adapter does not implement `find`", adapter.find);
1376
1584
 
1377
- adapter.find(this, type, id);
1585
+ var thenable = adapter.find(this, type, id);
1586
+
1587
+ if (thenable && thenable.then) {
1588
+ thenable.then(null /* for future use */, function(error) {
1589
+ store.recordWasError(record);
1590
+ });
1591
+ }
1378
1592
  },
1379
1593
 
1380
1594
  /**
1381
1595
  @private
1382
1596
 
1383
- This method returns a record for a given clientId.
1597
+ This method returns a record for a given record refeence.
1384
1598
 
1385
- If there is no record object yet for the clientId, this method materializes
1386
- a new record object. This allows adapters to eagerly load large amounts of
1387
- data into the store, and avoid incurring the cost to create the objects
1388
- until they are requested.
1389
-
1390
- Several parts of Ember Data call this method:
1391
-
1392
- * findById, if a clientId already exists for a given type and
1393
- id combination
1394
- * OneToManyChange, which is backed by clientIds, when getChild,
1395
- getOldParent or getNewParent are called
1396
- * RecordArray, which is backed by clientIds, when an object at
1397
- a particular index is looked up
1599
+ If no record for the reference has yet been materialized, this method will
1600
+ materialize a new `DS.Model` instance. This allows adapters to eagerly load
1601
+ large amounts of data into the store, and avoid incurring the cost of
1602
+ creating models until they are requested.
1398
1603
 
1399
1604
  In short, it's a convenient way to get a record for a known
1400
- clientId, materializing it if necessary.
1605
+ record reference, materializing it if necessary.
1401
1606
 
1402
- @param {Class} type
1403
- @param {Number|String} clientId
1607
+ @param {Object} reference
1608
+ @returns {DS.Model}
1404
1609
  */
1405
- findByClientId: function(type, clientId) {
1406
- var cidToData, record, id;
1407
-
1408
- record = this.recordCache[clientId];
1610
+ recordForReference: function(reference) {
1611
+ var record = reference.record;
1409
1612
 
1410
1613
  if (!record) {
1411
1614
  // create a new instance of the model type in the
1412
1615
  // 'isLoading' state
1413
- id = this.clientIdToId[clientId];
1414
- record = this.materializeRecord(type, clientId, id);
1415
-
1416
- cidToData = this.clientIdToData;
1417
-
1418
- if (typeof cidToData[clientId] === 'object') {
1419
- record.loadedData();
1420
- }
1616
+ record = this.materializeRecord(reference);
1421
1617
  }
1422
1618
 
1423
1619
  return record;
@@ -1426,27 +1622,25 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1426
1622
  /**
1427
1623
  @private
1428
1624
 
1429
- Given a type and array of `clientId`s, determines which of those
1625
+ Given an array of `reference`s, determines which of those
1430
1626
  `clientId`s has not yet been loaded.
1431
1627
 
1432
1628
  In preparation for loading, this method also marks any unloaded
1433
1629
  `clientId`s as loading.
1434
1630
  */
1435
- neededReferences: function(type, references) {
1436
- var neededReferences = [],
1437
- cidToData = this.clientIdToData,
1438
- reference;
1631
+ unloadedReferences: function(references) {
1632
+ var unloadedReferences = [];
1439
1633
 
1440
1634
  for (var i=0, l=references.length; i<l; i++) {
1441
- reference = references[i];
1635
+ var reference = references[i];
1442
1636
 
1443
- if (cidToData[reference.clientId] === UNLOADED) {
1444
- neededReferences.push(reference);
1445
- cidToData[reference.clientId] = LOADING;
1637
+ if (reference.data === UNLOADED) {
1638
+ unloadedReferences.push(reference);
1639
+ reference.data = LOADING;
1446
1640
  }
1447
1641
  }
1448
1642
 
1449
- return neededReferences;
1643
+ return unloadedReferences;
1450
1644
  },
1451
1645
 
1452
1646
  /**
@@ -1455,61 +1649,72 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1455
1649
  This method is the entry point that relationships use to update
1456
1650
  themselves when their underlying data changes.
1457
1651
 
1458
- First, it determines which of its `clientId`s are still unloaded,
1459
- then converts the needed `clientId`s to IDs and invokes `findMany`
1460
- on the adapter.
1652
+ First, it determines which of its `reference`s are still unloaded,
1653
+ then invokes `findMany` on the adapter.
1461
1654
  */
1462
- fetchUnloadedReferences: function(type, references, owner) {
1463
- var neededReferences = this.neededReferences(type, references);
1464
- this.fetchMany(type, neededReferences, owner);
1655
+ fetchUnloadedReferences: function(references, owner) {
1656
+ var unloadedReferences = this.unloadedReferences(references);
1657
+ this.fetchMany(unloadedReferences, owner);
1465
1658
  },
1466
1659
 
1467
1660
  /**
1468
1661
  @private
1469
1662
 
1470
- This method takes a type and list of `clientId`s, converts the
1471
- `clientId`s into IDs, and then invokes the adapter's `findMany`
1663
+ This method takes a list of `reference`s, groups the `reference`s by type,
1664
+ converts the `reference`s into IDs, and then invokes the adapter's `findMany`
1472
1665
  method.
1473
1666
 
1667
+ The `reference`s are grouped by type to invoke `findMany` on adapters
1668
+ for each unique type in `reference`s.
1669
+
1474
1670
  It is used both by a brand new relationship (via the `findMany`
1475
1671
  method) or when the data underlying an existing relationship
1476
1672
  changes (via the `fetchUnloadedReferences` method).
1477
1673
  */
1478
- fetchMany: function(type, references, owner) {
1674
+ fetchMany: function(references, owner) {
1479
1675
  if (!references.length) { return; }
1480
1676
 
1481
- var ids = map(references, function(reference) {
1482
- return reference.id;
1677
+ // Group By Type
1678
+ var referencesByTypeMap = Ember.MapWithDefault.create({
1679
+ defaultValue: function() { return Ember.A(); }
1680
+ });
1681
+ forEach(references, function(reference) {
1682
+ referencesByTypeMap.get(reference.type).push(reference);
1483
1683
  });
1484
1684
 
1485
- var adapter = this.adapterForType(type);
1486
- if (adapter && adapter.findMany) { adapter.findMany(this, type, ids, owner); }
1487
- else { throw "Adapter is either null or does not implement `findMany` method"; }
1488
- },
1685
+ forEach(referencesByTypeMap, function(type) {
1686
+ var references = referencesByTypeMap.get(type),
1687
+ ids = map(references, function(reference) { return reference.id; });
1489
1688
 
1490
- referenceForId: function(type, id) {
1491
- var clientId = this.clientIdForId(type, id);
1492
- return this.referenceForClientId(clientId);
1493
- },
1689
+ var adapter = this.adapterForType(type);
1494
1690
 
1495
- referenceForClientId: function(clientId) {
1496
- var references = this.recordReferences;
1691
+ Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
1692
+ Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany);
1497
1693
 
1498
- if (references[clientId]) {
1499
- return references[clientId];
1500
- }
1694
+ adapter.findMany(this, type, ids, owner);
1695
+ }, this);
1696
+ },
1501
1697
 
1502
- var type = this.clientIdToType[clientId];
1698
+ hasReferenceForId: function(type, id) {
1699
+ id = coerceId(id);
1503
1700
 
1504
- return references[clientId] = {
1505
- id: this.idForClientId(clientId),
1506
- clientId: clientId,
1507
- type: type
1508
- };
1701
+ return !!this.typeMapFor(type).idToReference[id];
1509
1702
  },
1510
1703
 
1511
- recordForReference: function(reference) {
1512
- return this.findByClientId(reference.type, reference.clientId);
1704
+ referenceForId: function(type, id) {
1705
+ id = coerceId(id);
1706
+
1707
+ // Check to see if we have seen this type/id pair before.
1708
+ var reference = this.typeMapFor(type).idToReference[id];
1709
+
1710
+ // If not, create a reference for it but don't populate it
1711
+ // with any data yet.
1712
+ if (!reference) {
1713
+ reference = this.createReference(type, id);
1714
+ reference.data = UNLOADED;
1715
+ }
1716
+
1717
+ return reference;
1513
1718
  },
1514
1719
 
1515
1720
  /**
@@ -1526,32 +1731,36 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1526
1731
  * create a new ManyArray whose content is *all* of the clientIds
1527
1732
  * notify the ManyArray of the number of its elements that are
1528
1733
  already loaded
1529
- * insert the unloaded clientIds into the `loadingRecordArrays`
1734
+ * insert the unloaded references into the `loadingRecordArrays`
1530
1735
  bookkeeping structure, which will allow the `ManyArray` to know
1531
1736
  when all of its loading elements are loaded from the server.
1532
1737
  * ask the adapter to load the unloaded elements, by invoking
1533
1738
  findMany with the still-unloaded IDs.
1534
1739
  */
1535
- findMany: function(type, ids, record, relationship) {
1536
- // 1. Convert ids to client ids
1537
- // 2. Determine which of the client ids need to be loaded
1538
- // 3. Create a new ManyArray whose content is ALL of the clientIds
1539
- // 4. Decrement the ManyArray's counter by the number of loaded clientIds
1540
- // 5. Put the ManyArray into our bookkeeping data structure, keyed on
1740
+ findMany: function(type, idsOrReferencesOrOpaque, record, relationship) {
1741
+ // 1. Determine which of the client ids need to be loaded
1742
+ // 2. Create a new ManyArray whose content is ALL of the clientIds
1743
+ // 3. Decrement the ManyArray's counter by the number of loaded clientIds
1744
+ // 4. Put the ManyArray into our bookkeeping data structure, keyed on
1541
1745
  // the needed clientIds
1542
- // 6. Ask the adapter to load the records for the unloaded clientIds (but
1746
+ // 5. Ask the adapter to load the records for the unloaded clientIds (but
1543
1747
  // convert them back to ids)
1544
1748
 
1545
- if (!Ember.isArray(ids)) {
1749
+ if (!Ember.isArray(idsOrReferencesOrOpaque)) {
1546
1750
  var adapter = this.adapterForType(type);
1547
- if (adapter && adapter.findHasMany) { adapter.findHasMany(this, record, relationship, ids); }
1548
- else { throw fmt("Adapter is either null or does not implement `findHasMany` method", this); }
1549
1751
 
1550
- return this.createManyArray(type, Ember.A());
1752
+ if (adapter && adapter.findHasMany) {
1753
+ adapter.findHasMany(this, record, relationship, idsOrReferencesOrOpaque);
1754
+ } else if (idsOrReferencesOrOpaque !== undefined) {
1755
+ Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
1756
+ Ember.assert("You tried to load many records but your adapter does not implement `findHasMany`", adapter.findHasMany);
1757
+ }
1758
+
1759
+ return this.recordArrayManager.createManyArray(type, Ember.A());
1551
1760
  }
1552
1761
 
1553
1762
  // Coerce server IDs into Record Reference
1554
- var references = map(ids, function(reference) {
1763
+ var references = map(idsOrReferencesOrOpaque, function(reference) {
1555
1764
  if (typeof reference !== 'object' && reference !== null) {
1556
1765
  return this.referenceForId(type, reference);
1557
1766
  }
@@ -1559,32 +1768,27 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1559
1768
  return reference;
1560
1769
  }, this);
1561
1770
 
1562
- var neededReferences = this.neededReferences(type, references),
1563
- manyArray = this.createManyArray(type, Ember.A(references)),
1771
+ var unloadedReferences = this.unloadedReferences(references),
1772
+ manyArray = this.recordArrayManager.createManyArray(type, Ember.A(references)),
1564
1773
  loadingRecordArrays = this.loadingRecordArrays,
1565
1774
  reference, clientId, i, l;
1566
1775
 
1567
1776
  // Start the decrementing counter on the ManyArray at the number of
1568
1777
  // records we need to load from the adapter
1569
- manyArray.loadingRecordsCount(neededReferences.length);
1778
+ manyArray.loadingRecordsCount(unloadedReferences.length);
1570
1779
 
1571
- if (neededReferences.length) {
1572
- for (i=0, l=neededReferences.length; i<l; i++) {
1573
- reference = neededReferences[i];
1574
- clientId = reference.clientId;
1780
+ if (unloadedReferences.length) {
1781
+ for (i=0, l=unloadedReferences.length; i<l; i++) {
1782
+ reference = unloadedReferences[i];
1575
1783
 
1576
1784
  // keep track of the record arrays that a given loading record
1577
1785
  // is part of. This way, if the same record is in multiple
1578
1786
  // ManyArrays, all of their loading records counters will be
1579
1787
  // decremented when the adapter provides the data.
1580
- if (loadingRecordArrays[clientId]) {
1581
- loadingRecordArrays[clientId].push(manyArray);
1582
- } else {
1583
- this.loadingRecordArrays[clientId] = [ manyArray ];
1584
- }
1788
+ this.recordArrayManager.registerWaitingRecordArray(manyArray, reference);
1585
1789
  }
1586
1790
 
1587
- this.fetchMany(type, neededReferences, record);
1791
+ this.fetchMany(unloadedReferences, record);
1588
1792
  } else {
1589
1793
  // all requested records are available
1590
1794
  manyArray.set('isLoaded', true);
@@ -1598,8 +1802,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1598
1802
  },
1599
1803
 
1600
1804
  /**
1601
- @private
1602
-
1603
1805
  This method delegates a query to the adapter. This is the one place where
1604
1806
  adapter-level semantics are exposed to the application.
1605
1807
 
@@ -1607,6 +1809,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1607
1809
  language for all server-side queries, and then require all adapters to
1608
1810
  implement them.
1609
1811
 
1812
+ @private
1813
+ @method findQuery
1610
1814
  @param {Class} type
1611
1815
  @param {Object} query an opaque query to be used by the adapter
1612
1816
  @return {DS.AdapterPopulatedRecordArray}
@@ -1614,8 +1818,12 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1614
1818
  findQuery: function(type, query) {
1615
1819
  var array = DS.AdapterPopulatedRecordArray.create({ type: type, query: query, content: Ember.A([]), store: this });
1616
1820
  var adapter = this.adapterForType(type);
1617
- if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
1618
- else { throw "Adapter is either null or does not implement `findQuery` method"; }
1821
+
1822
+ Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter);
1823
+ Ember.assert("You tried to load a query but your adapter does not implement `findQuery`", adapter.findQuery);
1824
+
1825
+ adapter.findQuery(this, type, query, array);
1826
+
1619
1827
  return array;
1620
1828
  },
1621
1829
 
@@ -1630,28 +1838,31 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1630
1838
  @return {DS.AdapterPopulatedRecordArray}
1631
1839
  */
1632
1840
  findAll: function(type) {
1633
- var array = this.all(type);
1634
- this.fetchAll(type, array);
1635
- return array;
1841
+ return this.fetchAll(type, this.all(type));
1636
1842
  },
1637
1843
 
1638
1844
  /**
1639
1845
  @private
1640
1846
  */
1641
1847
  fetchAll: function(type, array) {
1642
- var sinceToken = this.typeMapFor(type).sinceToken,
1643
- adapter = this.adapterForType(type);
1848
+ var adapter = this.adapterForType(type),
1849
+ sinceToken = this.typeMapFor(type).metadata.since;
1644
1850
 
1645
1851
  set(array, 'isUpdating', true);
1646
1852
 
1647
- if (adapter && adapter.findAll) { adapter.findAll(this, type, sinceToken); }
1648
- else { throw "Adapter is either null or does not implement `findAll` method"; }
1853
+ Ember.assert("You tried to load all records but you have no adapter (for " + type + ")", adapter);
1854
+ Ember.assert("You tried to load all records but your adapter does not implement `findAll`", adapter.findAll);
1855
+
1856
+ adapter.findAll(this, type, sinceToken);
1857
+
1858
+ return array;
1649
1859
  },
1650
1860
 
1651
1861
  /**
1652
1862
  */
1653
- sinceForType: function(type, sinceToken) {
1654
- this.typeMapFor(type).sinceToken = sinceToken;
1863
+ metaForType: function(type, property, data) {
1864
+ var target = this.typeMapFor(type).metadata;
1865
+ set(target, property, data);
1655
1866
  },
1656
1867
 
1657
1868
  /**
@@ -1671,6 +1882,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1671
1882
  Also note that multiple calls to `all` for a given type will always
1672
1883
  return the same RecordArray.
1673
1884
 
1885
+ @method all
1674
1886
  @param {Class} type
1675
1887
  @return {DS.RecordArray}
1676
1888
  */
@@ -1680,8 +1892,14 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1680
1892
 
1681
1893
  if (findAllCache) { return findAllCache; }
1682
1894
 
1683
- var array = DS.RecordArray.create({ type: type, content: Ember.A([]), store: this, isLoaded: true });
1684
- this.registerRecordArray(array, type);
1895
+ var array = DS.RecordArray.create({
1896
+ type: type,
1897
+ content: Ember.A([]),
1898
+ store: this,
1899
+ isLoaded: true
1900
+ });
1901
+
1902
+ this.recordArrayManager.registerFilteredRecordArray(array, type);
1685
1903
 
1686
1904
  typeMap.findAllCache = array;
1687
1905
  return array;
@@ -1711,9 +1929,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1711
1929
  In this scenario, you might want to consider filtering the raw
1712
1930
  data before loading it into the store.
1713
1931
 
1932
+ @method filter
1714
1933
  @param {Class} type
1715
1934
  @param {Function} filter
1716
-
1717
1935
  @return {DS.FilteredRecordArray}
1718
1936
  */
1719
1937
  filter: function(type, query, filter) {
@@ -1724,9 +1942,15 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1724
1942
  filter = query;
1725
1943
  }
1726
1944
 
1727
- var array = DS.FilteredRecordArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
1945
+ var array = DS.FilteredRecordArray.create({
1946
+ type: type,
1947
+ content: Ember.A([]),
1948
+ store: this,
1949
+ manager: this.recordArrayManager,
1950
+ filterFunction: filter
1951
+ });
1728
1952
 
1729
- this.registerRecordArray(array, type, filter);
1953
+ this.recordArrayManager.registerFilteredRecordArray(array, type, filter);
1730
1954
 
1731
1955
  return array;
1732
1956
  },
@@ -1741,7 +1965,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1741
1965
  @return {boolean}
1742
1966
  */
1743
1967
  recordIsLoaded: function(type, id) {
1744
- return !Ember.isNone(this.typeMapFor(type).idToCid[id]);
1968
+ if (!this.hasReferenceForId(type, id)) { return false; }
1969
+ return typeof this.referenceForId(type, id).data === 'object';
1745
1970
  },
1746
1971
 
1747
1972
  // ............
@@ -1774,12 +1999,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1774
1999
 
1775
2000
  if (get(record, 'isDeleted')) { return; }
1776
2001
 
1777
- var cidToData = this.clientIdToData,
1778
- clientId = reference.clientId,
1779
- data = cidToData[clientId];
1780
-
1781
- if (typeof data === "object") {
1782
- this.updateRecordArrays(type, clientId);
2002
+ if (typeof reference.data === "object") {
2003
+ this.recordArrayManager.referenceDidChange(reference);
1783
2004
  }
1784
2005
  },
1785
2006
 
@@ -1788,17 +2009,32 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1788
2009
  // ..............
1789
2010
 
1790
2011
  /**
1791
- This method delegates committing to the store's implicit
2012
+ This method delegates saving to the store's implicit
1792
2013
  transaction.
1793
2014
 
1794
2015
  Calling this method is essentially a request to persist
1795
2016
  any changes to records that were not explicitly added to
1796
2017
  a transaction.
1797
2018
  */
1798
- commit: function() {
2019
+ save: function() {
2020
+ once(this, 'commitDefaultTransaction');
2021
+ },
2022
+ commit: Ember.aliasMethod('save'),
2023
+
2024
+ commitDefaultTransaction: function() {
1799
2025
  get(this, 'defaultTransaction').commit();
1800
2026
  },
1801
2027
 
2028
+ scheduleSave: function(record) {
2029
+ get(this, 'currentTransaction').add(record);
2030
+ once(this, 'flushSavedRecords');
2031
+ },
2032
+
2033
+ flushSavedRecords: function() {
2034
+ get(this, 'currentTransaction').commit();
2035
+ set(this, 'currentTransaction', this.transaction());
2036
+ },
2037
+
1802
2038
  /**
1803
2039
  Adapters should call this method if they would like to acknowledge
1804
2040
  that all changes related to a record (other than relationship
@@ -1834,14 +2070,14 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
1834
2070
  @param {Object} data optional data (see above)
1835
2071
  */
1836
2072
  didSaveRecord: function(record, data) {
1837
- record.adapterDidCommit();
1838
-
1839
2073
  if (data) {
1840
2074
  this.updateId(record, data);
1841
2075
  this.updateRecordData(record, data);
1842
2076
  } else {
1843
2077
  this.didUpdateAttributes(record);
1844
2078
  }
2079
+
2080
+ record.adapterDidCommit();
1845
2081
  },
1846
2082
 
1847
2083
  /**
@@ -2011,7 +2247,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2011
2247
  @param {DS.Model} relationshipName
2012
2248
  */
2013
2249
  didUpdateRelationship: function(record, relationshipName) {
2014
- var relationship = this.relationshipChangeFor(get(record, 'clientId'), relationshipName);
2250
+ var clientId = get(record, '_reference').clientId;
2251
+
2252
+ var relationship = this.relationshipChangeFor(clientId, relationshipName);
2015
2253
  //TODO(Igor)
2016
2254
  if (relationship) { relationship.adapterDidUpdate(); }
2017
2255
  },
@@ -2076,11 +2314,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2076
2314
  @param {Object} data
2077
2315
  */
2078
2316
  updateRecordData: function(record, data) {
2079
- var clientId = get(record, 'clientId'),
2080
- cidToData = this.clientIdToData;
2081
-
2082
- cidToData[clientId] = data;
2083
-
2317
+ get(record, '_reference').data = data;
2084
2318
  record.didChangeData();
2085
2319
  },
2086
2320
 
@@ -2095,17 +2329,16 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2095
2329
  @param {Object} data
2096
2330
  */
2097
2331
  updateId: function(record, data) {
2098
- var typeMap = this.typeMapFor(record.constructor),
2099
- clientId = get(record, 'clientId'),
2332
+ var type = record.constructor,
2333
+ typeMap = this.typeMapFor(type),
2334
+ reference = get(record, '_reference'),
2100
2335
  oldId = get(record, 'id'),
2101
- type = record.constructor,
2102
2336
  id = this.preprocessData(type, data);
2103
2337
 
2104
2338
  Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === null || id === oldId);
2105
2339
 
2106
- typeMap.idToCid[id] = clientId;
2107
- this.clientIdToId[clientId] = id;
2108
- this.referenceForClientId(clientId).id = id;
2340
+ typeMap.idToReference[id] = reference;
2341
+ reference.id = id;
2109
2342
  },
2110
2343
 
2111
2344
  /**
@@ -2125,428 +2358,180 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2125
2358
  return this.adapterForType(type).extractId(type, data);
2126
2359
  },
2127
2360
 
2128
- // .................
2129
- // . RECORD ARRAYS .
2130
- // .................
2361
+ /** @private
2362
+ Returns a map of IDs to client IDs for a given type.
2363
+ */
2364
+ typeMapFor: function(type) {
2365
+ var typeMaps = get(this, 'typeMaps'),
2366
+ guid = Ember.guidFor(type),
2367
+ typeMap;
2131
2368
 
2132
- /**
2133
- @private
2369
+ typeMap = typeMaps[guid];
2134
2370
 
2135
- Register a RecordArray for a given type to be backed by
2136
- a filter function. This will cause the array to update
2137
- automatically when records of that type change attribute
2138
- values or states.
2371
+ if (typeMap) { return typeMap; }
2139
2372
 
2140
- @param {DS.RecordArray} array
2141
- @param {Class} type
2142
- @param {Function} filter
2143
- */
2144
- registerRecordArray: function(array, type, filter) {
2145
- var recordArrays = this.typeMapFor(type).recordArrays;
2373
+ typeMap = {
2374
+ idToReference: {},
2375
+ references: [],
2376
+ metadata: {}
2377
+ };
2146
2378
 
2147
- recordArrays.push(array);
2379
+ typeMaps[guid] = typeMap;
2148
2380
 
2149
- this.updateRecordArrayFilter(array, type, filter);
2381
+ return typeMap;
2150
2382
  },
2151
2383
 
2152
- /**
2153
- @private
2384
+ // ................
2385
+ // . LOADING DATA .
2386
+ // ................
2154
2387
 
2155
- Create a `DS.ManyArray` for a type and list of clientIds
2156
- and index the `ManyArray` under each clientId. This allows
2157
- us to efficiently remove records from `ManyArray`s when
2158
- they are deleted.
2388
+ /**
2389
+ Load new data into the store for a given id and type combination.
2390
+ If data for that record had been loaded previously, the new information
2391
+ overwrites the old.
2159
2392
 
2160
- @param {Class} type
2161
- @param {Array} clientIds
2393
+ If the record you are loading data for has outstanding changes that have not
2394
+ yet been saved, an exception will be thrown.
2162
2395
 
2163
- @return {DS.ManyArray}
2396
+ @param {DS.Model} type
2397
+ @param {String|Number} id
2398
+ @param {Object} data the data to load
2164
2399
  */
2165
- createManyArray: function(type, clientIds) {
2166
- var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
2400
+ load: function(type, data, prematerialized) {
2401
+ var id;
2167
2402
 
2168
- clientIds.forEach(function(clientId) {
2169
- var recordArrays = this.recordArraysForClientId(clientId);
2170
- recordArrays.add(array);
2171
- }, this);
2403
+ if (typeof data === 'number' || typeof data === 'string') {
2404
+ id = data;
2405
+ data = prematerialized;
2406
+ prematerialized = null;
2407
+ }
2172
2408
 
2173
- return array;
2174
- },
2409
+ if (prematerialized && prematerialized.id) {
2410
+ id = prematerialized.id;
2411
+ } else if (id === undefined) {
2412
+ id = this.preprocessData(type, data);
2413
+ }
2175
2414
 
2176
- /**
2177
- @private
2415
+ id = coerceId(id);
2178
2416
 
2179
- This method is invoked if the `filterFunction` property is
2180
- changed on a `DS.FilteredRecordArray`.
2417
+ var reference = this.referenceForId(type, id);
2181
2418
 
2182
- It essentially re-runs the filter from scratch. This same
2183
- method is invoked when the filter is created in th first place.
2184
- */
2185
- updateRecordArrayFilter: function(array, type, filter) {
2186
- var typeMap = this.typeMapFor(type),
2187
- cidToData = this.clientIdToData,
2188
- clientIds = typeMap.clientIds,
2189
- clientId, data, shouldFilter, record;
2419
+ if (reference.record) {
2420
+ once(reference.record, 'loadedData');
2421
+ }
2190
2422
 
2191
- for (var i=0, l=clientIds.length; i<l; i++) {
2192
- clientId = clientIds[i];
2193
- shouldFilter = false;
2423
+ reference.data = data;
2424
+ reference.prematerialized = prematerialized;
2194
2425
 
2195
- data = cidToData[clientId];
2426
+ this.recordArrayManager.referenceDidChange(reference);
2196
2427
 
2197
- if (typeof data === 'object') {
2198
- if (record = this.recordCache[clientId]) {
2199
- if (!get(record, 'isDeleted')) { shouldFilter = true; }
2200
- } else {
2201
- shouldFilter = true;
2202
- }
2428
+ return reference;
2429
+ },
2203
2430
 
2204
- if (shouldFilter) {
2205
- this.updateRecordArray(array, filter, type, clientId);
2206
- }
2207
- }
2431
+ loadMany: function(type, ids, dataList) {
2432
+ if (dataList === undefined) {
2433
+ dataList = ids;
2434
+ ids = map(dataList, function(data) {
2435
+ return this.preprocessData(type, data);
2436
+ }, this);
2208
2437
  }
2209
- },
2210
2438
 
2211
- updateRecordArraysLater: function(type, clientId) {
2212
- Ember.run.once(this, function() {
2213
- this.updateRecordArrays(type, clientId);
2214
- });
2439
+ return map(ids, function(id, i) {
2440
+ return this.load(type, id, dataList[i]);
2441
+ }, this);
2215
2442
  },
2216
2443
 
2217
- /**
2218
- @private
2219
-
2220
- This method is invoked whenever data is loaded into the store
2221
- by the adapter or updated by the adapter, or when an attribute
2222
- changes on a record.
2444
+ loadHasMany: function(record, key, ids) {
2445
+ //It looks sad to have to do the conversion in the store
2446
+ var type = record.get(key + '.type'),
2447
+ tuples = map(ids, function(id) {
2448
+ return {id: id, type: type};
2449
+ });
2450
+ record.materializeHasMany(key, tuples);
2223
2451
 
2224
- It updates all filters that a record belongs to.
2452
+ // Update any existing many arrays that use the previous IDs,
2453
+ // if necessary.
2454
+ record.hasManyDidChange(key);
2225
2455
 
2226
- To avoid thrashing, it only runs once per run loop per record.
2456
+ var relationship = record.cacheFor(key);
2227
2457
 
2228
- @param {Class} type
2229
- @param {Number|String} clientId
2230
- */
2231
- updateRecordArrays: function(type, clientId) {
2232
- var recordArrays = this.typeMapFor(type).recordArrays,
2233
- filter;
2234
-
2235
- recordArrays.forEach(function(array) {
2236
- filter = get(array, 'filterFunction');
2237
- this.updateRecordArray(array, filter, type, clientId);
2238
- }, this);
2239
-
2240
- // loop through all manyArrays containing an unloaded copy of this
2241
- // clientId and notify them that the record was loaded.
2242
- var manyArrays = this.loadingRecordArrays[clientId];
2243
-
2244
- if (manyArrays) {
2245
- for (var i=0, l=manyArrays.length; i<l; i++) {
2246
- manyArrays[i].loadedRecord();
2247
- }
2248
-
2249
- this.loadingRecordArrays[clientId] = null;
2250
- }
2251
- },
2252
-
2253
- /**
2254
- @private
2255
-
2256
- Update an individual filter.
2257
-
2258
- @param {DS.FilteredRecordArray} array
2259
- @param {Function} filter
2260
- @param {Class} type
2261
- @param {Number|String} clientId
2262
- */
2263
- updateRecordArray: function(array, filter, type, clientId) {
2264
- var shouldBeInArray, record;
2265
-
2266
- if (!filter) {
2267
- shouldBeInArray = true;
2268
- } else {
2269
- record = this.findByClientId(type, clientId);
2270
- shouldBeInArray = filter(record);
2271
- }
2272
-
2273
- var content = get(array, 'content');
2274
- var alreadyInArray = content.indexOf(clientId) !== -1;
2275
-
2276
- var recordArrays = this.recordArraysForClientId(clientId);
2277
- var reference = this.referenceForClientId(clientId);
2278
-
2279
- if (shouldBeInArray) {
2280
- recordArrays.add(array);
2281
- array.addReference(reference);
2282
- } else if (!shouldBeInArray) {
2283
- recordArrays.remove(array);
2284
- array.removeReference(reference);
2285
- }
2286
- },
2287
-
2288
- /**
2289
- @private
2290
-
2291
- When a record is deleted, it is removed from all its
2292
- record arrays.
2293
-
2294
- @param {DS.Model} record
2295
- */
2296
- removeFromRecordArrays: function(record) {
2297
- var reference = get(record, '_reference');
2298
- var recordArrays = this.recordArraysForClientId(reference.clientId);
2299
-
2300
- recordArrays.forEach(function(array) {
2301
- array.removeReference(reference);
2302
- });
2303
- },
2304
-
2305
- // ............
2306
- // . INDEXING .
2307
- // ............
2308
-
2309
- /**
2310
- @private
2311
-
2312
- Return a list of all `DS.RecordArray`s a clientId is
2313
- part of.
2314
-
2315
- @return {Object(clientId: Ember.OrderedSet)}
2316
- */
2317
- recordArraysForClientId: function(clientId) {
2318
- var recordArrays = get(this, 'recordArraysByClientId');
2319
- var ret = recordArrays[clientId];
2320
-
2321
- if (!ret) {
2322
- ret = recordArrays[clientId] = Ember.OrderedSet.create();
2323
- }
2324
-
2325
- return ret;
2326
- },
2327
-
2328
- typeMapFor: function(type) {
2329
- var typeMaps = get(this, 'typeMaps');
2330
- var guidForType = Ember.guidFor(type);
2331
-
2332
- var typeMap = typeMaps[guidForType];
2333
-
2334
- if (typeMap) {
2335
- return typeMap;
2336
- } else {
2337
- return (typeMaps[guidForType] =
2338
- {
2339
- idToCid: {},
2340
- clientIds: [],
2341
- recordArrays: []
2342
- });
2458
+ // TODO (tomdale) this assumes that loadHasMany *always* means
2459
+ // that the records for the provided IDs are loaded.
2460
+ if (relationship) {
2461
+ set(relationship, 'isLoaded', true);
2462
+ relationship.trigger('didLoad');
2343
2463
  }
2344
2464
  },
2345
2465
 
2346
2466
  /** @private
2347
2467
 
2348
- For a given type and id combination, returns the client id used by the store.
2349
- If no client id has been assigned yet, one will be created and returned.
2350
-
2351
- @param {DS.Model} type
2352
- @param {String|Number} id
2353
- */
2354
- clientIdForId: function(type, id) {
2355
- id = coerceId(id);
2356
-
2357
- var clientId = this.typeMapFor(type).idToCid[id];
2358
- if (clientId !== undefined) { return clientId; }
2359
-
2360
- return this.pushData(UNLOADED, id, type);
2361
- },
2362
-
2363
- /**
2364
- @private
2365
-
2366
- This method works exactly like `clientIdForId`, but does not
2367
- require looking up the `typeMap` for every `clientId` and
2368
- invoking a method per `clientId`.
2369
- */
2370
- clientIdsForIds: function(type, ids) {
2371
- var typeMap = this.typeMapFor(type),
2372
- idToClientIdMap = typeMap.idToCid;
2373
-
2374
- return map(ids, function(id) {
2375
- id = coerceId(id);
2376
-
2377
- var clientId = idToClientIdMap[id];
2378
- if (clientId) { return clientId; }
2379
- return this.pushData(UNLOADED, id, type);
2380
- }, this);
2381
- },
2382
-
2383
- typeForClientId: function(clientId) {
2384
- return this.clientIdToType[clientId];
2385
- },
2386
-
2387
- idForClientId: function(clientId) {
2388
- return this.clientIdToId[clientId];
2389
- },
2390
-
2391
- // ................
2392
- // . LOADING DATA .
2393
- // ................
2394
-
2395
- /**
2396
- Load new data into the store for a given id and type combination.
2397
- If data for that record had been loaded previously, the new information
2398
- overwrites the old.
2399
-
2400
- If the record you are loading data for has outstanding changes that have not
2401
- yet been saved, an exception will be thrown.
2468
+ Creates a new reference for a given type & ID pair. Metadata about the
2469
+ record can be stored in the reference without having to create a full-blown
2470
+ DS.Model instance.
2402
2471
 
2403
2472
  @param {DS.Model} type
2404
2473
  @param {String|Number} id
2405
- @param {Object} data the data to load
2474
+ @returns {Reference}
2406
2475
  */
2407
- load: function(type, data, prematerialized) {
2408
- var id;
2409
-
2410
- if (typeof data === 'number' || typeof data === 'string') {
2411
- id = data;
2412
- data = prematerialized;
2413
- prematerialized = null;
2414
- }
2415
-
2416
- if (prematerialized && prematerialized.id) {
2417
- id = prematerialized.id;
2418
- } else if (id === undefined) {
2419
- var adapter = this.adapterForType(type);
2420
- id = this.preprocessData(type, data);
2421
- }
2422
-
2423
- id = coerceId(id);
2424
-
2476
+ createReference: function(type, id) {
2425
2477
  var typeMap = this.typeMapFor(type),
2426
- cidToData = this.clientIdToData,
2427
- clientId = typeMap.idToCid[id],
2428
- cidToPrematerialized = this.clientIdToPrematerializedData;
2429
-
2430
- if (clientId !== undefined) {
2431
- cidToData[clientId] = data;
2432
- cidToPrematerialized[clientId] = prematerialized;
2433
-
2434
- var record = this.recordCache[clientId];
2435
- if (record) {
2436
- once(record, 'loadedData');
2437
- }
2438
- } else {
2439
- clientId = this.pushData(data, id, type);
2440
- cidToPrematerialized[clientId] = prematerialized;
2441
- }
2442
-
2443
- this.updateRecordArraysLater(type, clientId);
2444
-
2445
- return this.referenceForClientId(clientId);
2446
- },
2447
-
2448
- prematerialize: function(reference, prematerialized) {
2449
- this.clientIdToPrematerializedData[reference.clientId] = prematerialized;
2450
- },
2451
-
2452
- loadMany: function(type, ids, dataList) {
2453
- if (dataList === undefined) {
2454
- dataList = ids;
2455
- ids = map(dataList, function(data) {
2456
- return this.preprocessData(type, data);
2457
- }, this);
2458
- }
2459
-
2460
- return map(ids, function(id, i) {
2461
- return this.load(type, id, dataList[i]);
2462
- }, this);
2463
- },
2464
-
2465
- loadHasMany: function(record, key, ids) {
2466
- record.materializeHasMany(key, ids);
2467
-
2468
- // Update any existing many arrays that use the previous IDs,
2469
- // if necessary.
2470
- record.hasManyDidChange(key);
2471
-
2472
- var relationship = record.cacheFor(key);
2478
+ idToReference = typeMap.idToReference;
2473
2479
 
2474
- // TODO (tomdale) this assumes that loadHasMany *always* means
2475
- // that the records for the provided IDs are loaded.
2476
- if (relationship) { set(relationship, 'isLoaded', true); }
2477
- },
2478
-
2479
- /** @private
2480
-
2481
- Stores data for the specified type and id combination and returns
2482
- the client id.
2483
-
2484
- @param {Object} data
2485
- @param {String|Number} id
2486
- @param {DS.Model} type
2487
- @returns {Number}
2488
- */
2489
- pushData: function(data, id, type) {
2490
- var typeMap = this.typeMapFor(type);
2491
-
2492
- var idToClientIdMap = typeMap.idToCid,
2493
- clientIdToIdMap = this.clientIdToId,
2494
- clientIdToTypeMap = this.clientIdToType,
2495
- clientIds = typeMap.clientIds,
2496
- cidToData = this.clientIdToData;
2480
+ Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToReference[id]);
2497
2481
 
2498
- var clientId = ++this.clientIdCounter;
2499
-
2500
- cidToData[clientId] = data;
2501
- clientIdToTypeMap[clientId] = type;
2482
+ var reference = {
2483
+ id: id,
2484
+ clientId: this.clientIdCounter++,
2485
+ type: type
2486
+ };
2502
2487
 
2503
2488
  // if we're creating an item, this process will be done
2504
2489
  // later, once the object has been persisted.
2505
2490
  if (id) {
2506
- idToClientIdMap[id] = clientId;
2507
- clientIdToIdMap[clientId] = id;
2491
+ idToReference[id] = reference;
2508
2492
  }
2509
2493
 
2510
- clientIds.push(clientId);
2494
+ typeMap.references.push(reference);
2511
2495
 
2512
- return clientId;
2496
+ return reference;
2513
2497
  },
2514
2498
 
2515
2499
  // ..........................
2516
2500
  // . RECORD MATERIALIZATION .
2517
2501
  // ..........................
2518
2502
 
2519
- materializeRecord: function(type, clientId, id) {
2520
- var record;
2521
-
2522
- this.recordCache[clientId] = record = type._create({
2503
+ materializeRecord: function(reference) {
2504
+ var record = reference.type._create({
2505
+ id: reference.id,
2523
2506
  store: this,
2524
- clientId: clientId
2507
+ _reference: reference
2525
2508
  });
2526
2509
 
2527
- set(record, 'id', id);
2510
+ reference.record = record;
2528
2511
 
2529
2512
  get(this, 'defaultTransaction').adoptRecord(record);
2530
2513
 
2531
2514
  record.loadingData();
2515
+
2516
+ if (typeof reference.data === 'object') {
2517
+ record.loadedData();
2518
+ }
2519
+
2532
2520
  return record;
2533
2521
  },
2534
2522
 
2535
2523
  dematerializeRecord: function(record) {
2536
- var id = get(record, 'id'),
2537
- clientId = get(record, 'clientId'),
2538
- type = this.typeForClientId(clientId),
2524
+ var reference = get(record, '_reference'),
2525
+ type = reference.type,
2526
+ id = reference.id,
2539
2527
  typeMap = this.typeMapFor(type);
2540
2528
 
2541
2529
  record.updateRecordArrays();
2542
2530
 
2543
- delete this.recordCache[clientId];
2544
- delete this.clientIdToId[clientId];
2545
- delete this.clientIdToType[clientId];
2546
- delete this.clientIdToData[clientId];
2547
- delete this.recordArraysByClientId[clientId];
2531
+ if (id) { delete typeMap.idToReference[id]; }
2548
2532
 
2549
- if (id) { delete typeMap.idToCid[id]; }
2533
+ var loc = typeMap.references.indexOf(reference);
2534
+ typeMap.references.splice(loc, 1);
2550
2535
  },
2551
2536
 
2552
2537
  willDestroy: function() {
@@ -2587,7 +2572,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2587
2572
  delete changes[clientId][parentClientId][key][type];
2588
2573
  },
2589
2574
 
2590
- relationshipChangeFor: function(clientId, childKey, parentClientId, parentKey, type) {
2575
+ relationshipChangeFor: function(clientReference, childKey, parentReference, parentKey, type) {
2576
+ var clientId = clientReference.clientId,
2577
+ parentClientId = parentReference ? parentReference.clientId : parentReference;
2591
2578
  var changes = this.relationshipChanges;
2592
2579
  var key = childKey + parentKey;
2593
2580
  if (!(clientId in changes) || !(parentClientId in changes[clientId])){
@@ -2657,7 +2644,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
2657
2644
  // ..............................
2658
2645
 
2659
2646
  recordAttributeDidChange: function(reference, attributeName, newValue, oldValue) {
2660
- var record = this.recordForReference(reference),
2647
+ var record = reference.record,
2661
2648
  dirtySet = new Ember.OrderedSet(),
2662
2649
  adapter = this.adapterForType(record.constructor);
2663
2650
 
@@ -2723,7 +2710,12 @@ DS.Store.reopenClass({
2723
2710
 
2724
2711
 
2725
2712
  (function() {
2726
- var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor,
2713
+ /**
2714
+ @module data
2715
+ @submodule data-model
2716
+ */
2717
+
2718
+ var get = Ember.get, set = Ember.set,
2727
2719
  once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
2728
2720
 
2729
2721
  /**
@@ -2872,9 +2864,13 @@ var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor,
2872
2864
  }
2873
2865
  })
2874
2866
 
2875
- Note that enter and exit events are called once per transition. If the
2876
- current state changes, but changes to another child state of the parent,
2877
- the transition event on the parent will not be triggered.
2867
+ Note that enter and exit events are called once per transition. If the
2868
+ current state changes, but changes to another child state of the parent,
2869
+ the transition event on the parent will not be triggered.
2870
+
2871
+ @class States
2872
+ @namespace DS
2873
+ @extends Ember.State
2878
2874
  */
2879
2875
 
2880
2876
  var stateProperty = Ember.computed(function(key) {
@@ -2884,14 +2880,6 @@ var stateProperty = Ember.computed(function(key) {
2884
2880
  }
2885
2881
  }).property();
2886
2882
 
2887
- var isEmptyObject = function(object) {
2888
- for (var name in object) {
2889
- if (object.hasOwnProperty(name)) { return false; }
2890
- }
2891
-
2892
- return true;
2893
- };
2894
-
2895
2883
  var hasDefinedProperties = function(object) {
2896
2884
  for (var name in object) {
2897
2885
  if (object.hasOwnProperty(name) && object[name]) { return true; }
@@ -2909,22 +2897,17 @@ var willSetProperty = function(manager, context) {
2909
2897
  context.oldValue = get(get(manager, 'record'), context.name);
2910
2898
 
2911
2899
  var change = DS.AttributeChange.createChange(context);
2912
- get(manager, 'record')._changesToSync[context.attributeName] = change;
2900
+ get(manager, 'record')._changesToSync[context.name] = change;
2913
2901
  };
2914
2902
 
2915
2903
  var didSetProperty = function(manager, context) {
2916
- var change = get(manager, 'record')._changesToSync[context.attributeName];
2904
+ var change = get(manager, 'record')._changesToSync[context.name];
2917
2905
  change.value = get(get(manager, 'record'), context.name);
2918
2906
  change.sync();
2919
2907
  };
2920
2908
 
2921
- // Whenever a property is set, recompute all dependent filters
2922
- var updateRecordArrays = function(manager) {
2923
- var record = manager.get('record');
2924
- record.updateRecordArraysLater();
2925
- };
2926
-
2927
2909
  DS.State = Ember.State.extend({
2910
+ isLoading: stateProperty,
2928
2911
  isLoaded: stateProperty,
2929
2912
  isReloading: stateProperty,
2930
2913
  isDirty: stateProperty,
@@ -2996,15 +2979,6 @@ var DirtyState = DS.State.extend({
2996
2979
  // This means that there are local pending changes, but they
2997
2980
  // have not yet begun to be saved, and are not invalid.
2998
2981
  uncommitted: DS.State.extend({
2999
- // TRANSITIONS
3000
- enter: function(manager) {
3001
- var dirtyType = get(this, 'dirtyType'),
3002
- record = get(manager, 'record');
3003
-
3004
- record.withTransaction(function (t) {
3005
- t.recordBecameDirty(dirtyType, record);
3006
- });
3007
- },
3008
2982
 
3009
2983
  // EVENTS
3010
2984
  willSetProperty: willSetProperty,
@@ -3017,24 +2991,15 @@ var DirtyState = DS.State.extend({
3017
2991
  },
3018
2992
 
3019
2993
  becameClean: function(manager) {
3020
- var record = get(manager, 'record'),
3021
- dirtyType = get(this, 'dirtyType');
2994
+ var record = get(manager, 'record');
3022
2995
 
3023
2996
  record.withTransaction(function(t) {
3024
- t.recordBecameClean(dirtyType, record);
2997
+ t.remove(record);
3025
2998
  });
3026
-
3027
2999
  manager.transitionTo('loaded.materializing');
3028
3000
  },
3029
3001
 
3030
3002
  becameInvalid: function(manager) {
3031
- var dirtyType = get(this, 'dirtyType'),
3032
- record = get(manager, 'record');
3033
-
3034
- record.withTransaction(function (t) {
3035
- t.recordBecameInFlight(dirtyType, record);
3036
- });
3037
-
3038
3003
  manager.transitionTo('invalid');
3039
3004
  },
3040
3005
 
@@ -3052,29 +3017,32 @@ var DirtyState = DS.State.extend({
3052
3017
 
3053
3018