rasputin 0.11.3 → 0.12.1

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.
@@ -0,0 +1,1400 @@
1
+
2
+ (function(exports) {
3
+ window.DS = SC.Namespace.create();
4
+
5
+ })({});
6
+
7
+
8
+ (function(exports) {
9
+ DS.Adapter = SC.Object.extend({
10
+ commit: function(store, commitDetails) {
11
+ commitDetails.updated.eachType(function(type, array) {
12
+ this.updateMany(store, type, array.slice());
13
+ }, this);
14
+
15
+ commitDetails.created.eachType(function(type, array) {
16
+ this.createMany(store, type, array.slice());
17
+ }, this);
18
+
19
+ commitDetails.deleted.eachType(function(type, array) {
20
+ this.deleteMany(store, type, array.slice());
21
+ }, this);
22
+ },
23
+
24
+ createMany: function(store, type, models) {
25
+ models.forEach(function(model) {
26
+ this.create(store, type, model);
27
+ }, this);
28
+ },
29
+
30
+ updateMany: function(store, type, models) {
31
+ models.forEach(function(model) {
32
+ this.update(store, type, model);
33
+ }, this);
34
+ },
35
+
36
+ deleteMany: function(store, type, models) {
37
+ models.forEach(function(model) {
38
+ this.deleteModel(store, type, model);
39
+ }, this);
40
+ },
41
+
42
+ findMany: function(store, type, ids) {
43
+ ids.forEach(function(id) {
44
+ this.find(store, type, id);
45
+ }, this);
46
+ }
47
+ });
48
+ })({});
49
+
50
+
51
+ (function(exports) {
52
+ DS.fixtureAdapter = DS.Adapter.create({
53
+ find: function(store, type, id) {
54
+ var fixtures = type.FIXTURES;
55
+
56
+ ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
57
+ if (fixtures.hasLoaded) { return; }
58
+
59
+ setTimeout(function() {
60
+ store.loadMany(type, fixtures);
61
+ fixtures.hasLoaded = true;
62
+ }, 300);
63
+ },
64
+
65
+ findMany: function() {
66
+ this.find.apply(this, arguments);
67
+ }
68
+ });
69
+
70
+ })({});
71
+
72
+
73
+ (function(exports) {
74
+ var get = SC.get, set = SC.set;
75
+
76
+ DS.ModelArray = SC.ArrayProxy.extend({
77
+ type: null,
78
+ content: null,
79
+ store: null,
80
+
81
+ init: function() {
82
+ set(this, 'modelCache', Ember.A([]));
83
+ this._super();
84
+ },
85
+
86
+ arrayDidChange: function(array, index, removed, added) {
87
+ this._super(array, index, removed, added);
88
+
89
+ var modelCache = get(this, 'modelCache');
90
+ modelCache.replace(index, 0, Array(added));
91
+ },
92
+
93
+ arrayWillChange: function(array, index, removed, added) {
94
+ this._super(array, index, removed, added);
95
+
96
+ var modelCache = get(this, 'modelCache');
97
+ modelCache.replace(index, removed);
98
+ },
99
+
100
+ objectAtContent: function(index) {
101
+ var modelCache = get(this, 'modelCache');
102
+ var model = modelCache.objectAt(index);
103
+
104
+ if (!model) {
105
+ var store = get(this, 'store');
106
+ var content = get(this, 'content');
107
+
108
+ var contentObject = content.objectAt(index);
109
+
110
+ if (contentObject !== undefined) {
111
+ model = store.findByClientId(get(this, 'type'), contentObject);
112
+ modelCache.replace(index, 1, [model]);
113
+ }
114
+ }
115
+
116
+ return model;
117
+ }
118
+ });
119
+
120
+ DS.FilteredModelArray = DS.ModelArray.extend({
121
+ filterFunction: null,
122
+
123
+ updateFilter: SC.observer(function() {
124
+ var store = get(this, 'store');
125
+ store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
126
+ }, 'filterFunction')
127
+ });
128
+
129
+ DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
130
+ query: null,
131
+ isLoaded: false,
132
+
133
+ load: function(array) {
134
+ var store = get(this, 'store'), type = get(this, 'type');
135
+
136
+ var clientIds = store.loadMany(type, array).clientIds;
137
+
138
+ this.beginPropertyChanges();
139
+ set(this, 'content', Ember.A(clientIds));
140
+ set(this, 'isLoaded', true);
141
+ this.endPropertyChanges();
142
+ }
143
+ });
144
+
145
+ })({});
146
+
147
+
148
+ (function(exports) {
149
+ var get = SC.get, set = SC.set, getPath = SC.getPath, fmt = SC.String.fmt;
150
+
151
+ var OrderedSet = SC.Object.extend({
152
+ init: function() {
153
+ this.clear();
154
+ },
155
+
156
+ clear: function() {
157
+ this.set('presenceSet', {});
158
+ this.set('list', SC.NativeArray.apply([]));
159
+ },
160
+
161
+ add: function(obj) {
162
+ var guid = SC.guidFor(obj),
163
+ presenceSet = get(this, 'presenceSet'),
164
+ list = get(this, 'list');
165
+
166
+ if (guid in presenceSet) { return; }
167
+
168
+ presenceSet[guid] = true;
169
+ list.pushObject(obj);
170
+ },
171
+
172
+ remove: function(obj) {
173
+ var guid = SC.guidFor(obj),
174
+ presenceSet = get(this, 'presenceSet'),
175
+ list = get(this, 'list');
176
+
177
+ delete presenceSet[guid];
178
+ list.removeObject(obj);
179
+ },
180
+
181
+ isEmpty: function() {
182
+ return getPath(this, 'list.length') === 0;
183
+ },
184
+
185
+ forEach: function(fn, self) {
186
+ get(this, 'list').forEach(function(item) {
187
+ fn.call(self, item);
188
+ });
189
+ }
190
+ });
191
+
192
+ // Implementors Note:
193
+ //
194
+ // The variables in this file are consistently named according to the following
195
+ // scheme:
196
+ //
197
+ // * +id+ means an identifier managed by an external source, provided inside the
198
+ // data hash provided by that source.
199
+ // * +clientId+ means a transient numerical identifier generated at runtime by
200
+ // the data store. It is important primarily because newly created objects may
201
+ // not yet have an externally generated id.
202
+ // * +type+ means a subclass of DS.Model.
203
+
204
+ /**
205
+ The store contains all of the hashes for data models loaded from the server.
206
+ It is also responsible for creating instances of DS.Model when you request one
207
+ of these data hashes, so that they can be bound to in your Handlebars templates.
208
+
209
+ Create a new store like this:
210
+
211
+ MyApp.store = DS.Store.create();
212
+
213
+ You can retrieve DS.Model instances from the store in several ways. To retrieve
214
+ a model for a specific id, use the `find()` method:
215
+
216
+ var model = MyApp.store.find(MyApp.Contact, 123);
217
+
218
+ By default, the store will talk to your backend using a standard REST mechanism.
219
+ You can customize how the store talks to your backend by specifying a custom adapter:
220
+
221
+ MyApp.store = DS.Store.create({
222
+ adapter: 'MyApp.CustomAdapter'
223
+ });
224
+
225
+ You can learn more about writing a custom adapter by reading the `DS.Adapter`
226
+ documentation.
227
+ */
228
+ DS.Store = SC.Object.extend({
229
+
230
+ /**
231
+ Many methods can be invoked without specifying which store should be used.
232
+ In those cases, the first store created will be used as the default. If
233
+ an application has multiple stores, it should specify which store to use
234
+ when performing actions, such as finding records by id.
235
+
236
+ The init method registers this store as the default if none is specified.
237
+ */
238
+ init: function() {
239
+ if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
240
+ set(DS, 'defaultStore', this);
241
+ }
242
+
243
+ set(this, 'data', []);
244
+ set(this, 'ids', {});
245
+ set(this, 'models', []);
246
+ set(this, 'modelArrays', []);
247
+ set(this, 'modelArraysByClientId', {});
248
+ set(this, 'updatedTypes', OrderedSet.create());
249
+ set(this, 'createdTypes', OrderedSet.create());
250
+ set(this, 'deletedTypes', OrderedSet.create());
251
+
252
+ return this._super();
253
+ },
254
+
255
+ modelArraysForClientId: function(clientId) {
256
+ var modelArrays = get(this, 'modelArraysByClientId');
257
+ var ret = modelArrays[clientId];
258
+
259
+ if (!ret) {
260
+ ret = modelArrays[clientId] = OrderedSet.create();
261
+ }
262
+
263
+ return ret;
264
+ },
265
+
266
+ /**
267
+ The adapter to use to communicate to a backend server or other persistence layer.
268
+
269
+ This can be specified as an instance, a class, or a property path that specifies
270
+ where the adapter can be located.
271
+
272
+ @property {DS.Adapter|String}
273
+ */
274
+ adapter: null,
275
+
276
+ _adapter: SC.computed(function() {
277
+ var adapter = get(this, 'adapter');
278
+ if (typeof adapter === 'string') {
279
+ return getPath(this, adapter);
280
+ }
281
+ return adapter;
282
+ }).property('adapter').cacheable(),
283
+
284
+ clientIdCounter: -1,
285
+
286
+ // ....................
287
+ // . CREATE NEW MODEL .
288
+ // ....................
289
+
290
+ create: function(type, hash) {
291
+ hash = hash || {};
292
+
293
+ var id = hash[getPath(type, 'proto.primaryKey')] || null;
294
+
295
+ var model = type.create({ data: hash || {}, store: this });
296
+ model.adapterDidCreate();
297
+
298
+ var data = this.clientIdToHashMap(type);
299
+ var models = get(this, 'models');
300
+
301
+ var clientId = this.pushHash(hash, id, type);
302
+ this.updateModelArrays(type, clientId, hash);
303
+
304
+ set(model, 'clientId', clientId);
305
+
306
+ get(this, 'models')[clientId] = model;
307
+
308
+ return model;
309
+ },
310
+
311
+ // ................
312
+ // . DELETE MODEL .
313
+ // ................
314
+
315
+ deleteModel: function(model) {
316
+ model.deleteModel();
317
+ },
318
+
319
+ // ...............
320
+ // . FIND MODELS .
321
+ // ...............
322
+
323
+ /**
324
+ Finds a model by its id. If the data for that model has already been
325
+ loaded, an instance of DS.Model with that data will be returned
326
+ immediately. Otherwise, an empty DS.Model instance will be returned in
327
+ the loading state. As soon as the requested data is available, the model
328
+ will be moved into the loaded state and all of the information will be
329
+ available.
330
+
331
+ Note that only one DS.Model instance is ever created per unique id for a
332
+ given type.
333
+
334
+ Example:
335
+
336
+ var record = MyApp.store.find(MyApp.Person, 1234);
337
+
338
+ @param {DS.Model} type
339
+ @param {String|Number} id
340
+ */
341
+ find: function(type, id, query) {
342
+ if (id === undefined) {
343
+ return this.findMany(type, null, null);
344
+ }
345
+
346
+ if (query !== undefined) {
347
+ return this.findMany(type, id, query);
348
+ } else if (SC.typeOf(id) === 'object') {
349
+ return this.findQuery(type, id);
350
+ }
351
+
352
+ if (SC.isArray(id)) {
353
+ return this.findMany(type, id);
354
+ }
355
+
356
+ var clientId = this.clientIdForId(type, id);
357
+
358
+ return this.findByClientId(type, clientId, id);
359
+ },
360
+
361
+ findByClientId: function(type, clientId, id) {
362
+ var model;
363
+
364
+ var models = get(this, 'models');
365
+ var data = this.clientIdToHashMap(type);
366
+
367
+ // If there is already a clientId assigned for this
368
+ // type/id combination, try to find an existing
369
+ // model for that id and return. Otherwise,
370
+ // materialize a new model and set its data to the
371
+ // value we already have.
372
+ if (clientId !== undefined) {
373
+ model = models[clientId];
374
+
375
+ if (!model) {
376
+ // create a new instance of the model in the
377
+ // 'isLoading' state
378
+ model = this.createModel(type, clientId);
379
+
380
+ // immediately set its data
381
+ model.setData(data[clientId] || null);
382
+ }
383
+ } else {
384
+ clientId = this.pushHash(null, id, type);
385
+
386
+ // create a new instance of the model in the
387
+ // 'isLoading' state
388
+ model = this.createModel(type, clientId);
389
+
390
+ // let the adapter set the data, possibly async
391
+ var adapter = get(this, '_adapter');
392
+ if (adapter && adapter.find) { adapter.find(this, type, id); }
393
+ else { throw fmt("Adapter is either null or do not implement `find` method", this); }
394
+ }
395
+
396
+ return model;
397
+ },
398
+
399
+ /** @private
400
+ */
401
+ findMany: function(type, ids, query) {
402
+ var idToClientIdMap = this.idToClientIdMap(type);
403
+ var data = this.clientIdToHashMap(type), needed;
404
+
405
+ var clientIds = Ember.A([]);
406
+
407
+ if (ids) {
408
+ needed = [];
409
+
410
+ ids.forEach(function(id) {
411
+ var clientId = idToClientIdMap[id];
412
+ if (clientId === undefined || data[clientId] === undefined) {
413
+ clientId = this.pushHash(null, id, type);
414
+ needed.push(id);
415
+ }
416
+
417
+ clientIds.push(clientId);
418
+ }, this);
419
+ } else {
420
+ needed = null;
421
+ }
422
+
423
+ if ((needed && get(needed, 'length') > 0) || query) {
424
+ var adapter = get(this, '_adapter');
425
+ if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
426
+ else { throw fmt("Adapter is either null or do not implement `findMany` method", this); }
427
+ }
428
+
429
+ return this.createModelArray(type, clientIds);
430
+ },
431
+
432
+ findQuery: function(type, query) {
433
+ var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
434
+ var adapter = get(this, '_adapter');
435
+ if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
436
+ else { throw fmt("Adapter is either null or do not implement `findQuery` method", this); }
437
+ return array;
438
+ },
439
+
440
+ findAll: function(type) {
441
+ var array = DS.ModelArray.create({ type: type, content: Ember.A([]), store: this });
442
+ this.registerModelArray(array, type);
443
+
444
+ var adapter = get(this, '_adapter');
445
+ if (adapter && adapter.findAll) { adapter.findAll(this, type); }
446
+
447
+ return array;
448
+ },
449
+
450
+ filter: function(type, filter) {
451
+ var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
452
+
453
+ this.registerModelArray(array, type, filter);
454
+
455
+ return array;
456
+ },
457
+
458
+ // ............
459
+ // . UPDATING .
460
+ // ............
461
+
462
+ hashWasUpdated: function(type, clientId) {
463
+ var clientIdToHashMap = this.clientIdToHashMap(type);
464
+ var hash = clientIdToHashMap[clientId];
465
+
466
+ this.updateModelArrays(type, clientId, hash);
467
+ },
468
+
469
+
470
+ // Internally, the store keeps two data structures representing
471
+ // the dirty models.
472
+ //
473
+ // It holds an OrderedSet of all of the dirty types and a Hash
474
+ // keyed off of the guid of each type.
475
+ //
476
+ // Assuming that Ember.guidFor(Person) is 'sc1', guidFor(Place)
477
+ // is 'sc2', and guidFor(Thing) is 'sc3', the structure will look
478
+ // like:
479
+ //
480
+ // store: {
481
+ // updatedTypes: [ Person, Place, Thing ],
482
+ // updatedModels: {
483
+ // sc1: [ person1, person2, person3 ],
484
+ // sc2: [ place1 ],
485
+ // sc3: [ thing1, thing2 ]
486
+ // }
487
+ // }
488
+ //
489
+ // Adapters receive an iterator that they can use to retrieve the
490
+ // type and array at the same time:
491
+ //
492
+ // adapter: {
493
+ // commit: function(store, commitDetails) {
494
+ // commitDetails.updated.eachType(function(type, array) {
495
+ // // this callback will be invoked three times:
496
+ // //
497
+ // // 1. Person, [ person1, person2, person3 ]
498
+ // // 2. Place, [ place1 ]
499
+ // // 3. Thing, [ thing1, thing2 ]
500
+ // }
501
+ // }
502
+ // }
503
+ //
504
+ // This encapsulates the internal structure and presents it to the
505
+ // adapter as if it was a regular Hash with types as keys and dirty
506
+ // models as values.
507
+ //
508
+ // Note that there is a pair of *Types and *Models for each of
509
+ // `created`, `updated` and `deleted`. These correspond with the
510
+ // commitDetails passed into the adapter's commit method.
511
+
512
+ modelBecameDirty: function(kind, model) {
513
+ var dirtyTypes = get(this, kind + 'Types'), type = model.constructor;
514
+ dirtyTypes.add(type);
515
+
516
+ var dirtyModels = this.typeMap(type)[kind + 'Models'];
517
+ dirtyModels.add(model);
518
+ },
519
+
520
+ modelBecameClean: function(kind, model) {
521
+ var dirtyTypes = get(this, kind + 'Types'), type = model.constructor;
522
+
523
+ var dirtyModels = this.typeMap(type)[kind + 'Models'];
524
+ dirtyModels.remove(model);
525
+
526
+ if (dirtyModels.isEmpty()) {
527
+ dirtyTypes.remove(type);
528
+ }
529
+ },
530
+
531
+ eachDirtyType: function(kind, fn, self) {
532
+ var types = get(this, kind + 'Types'), dirtyModels;
533
+
534
+ types.forEach(function(type) {
535
+ dirtyModels = this.typeMap(type)[kind + 'Models'];
536
+ fn.call(self, type, get(dirtyModels, 'list'));
537
+ }, this);
538
+ },
539
+
540
+ // ..............
541
+ // . PERSISTING .
542
+ // ..............
543
+
544
+ commit: function() {
545
+ var self = this;
546
+
547
+ var iterate = function(kind, fn, binding) {
548
+ self.eachDirtyType(kind, function(type, models) {
549
+ models.forEach(function(model) {
550
+ model.willCommit();
551
+ });
552
+
553
+ fn.call(binding, type, models);
554
+ });
555
+ };
556
+
557
+ var commitDetails = {
558
+ updated: {
559
+ eachType: function(fn, binding) { iterate('updated', fn, binding); }
560
+ },
561
+
562
+ created: {
563
+ eachType: function(fn, binding) { iterate('created', fn, binding); }
564
+ },
565
+
566
+ deleted: {
567
+ eachType: function(fn, binding) { iterate('deleted', fn, binding); }
568
+ }
569
+ };
570
+
571
+ var adapter = get(this, '_adapter');
572
+ if (adapter && adapter.commit) { adapter.commit(this, commitDetails); }
573
+ else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
574
+ },
575
+
576
+ didUpdateModels: function(array, hashes) {
577
+ if (arguments.length === 2) {
578
+ array.forEach(function(model, idx) {
579
+ this.didUpdateModel(model, hashes[idx]);
580
+ }, this);
581
+ } else {
582
+ array.forEach(function(model) {
583
+ this.didUpdateModel(model);
584
+ }, this);
585
+ }
586
+ },
587
+
588
+ didUpdateModel: function(model, hash) {
589
+ if (arguments.length === 2) {
590
+ var clientId = get(model, 'clientId');
591
+ var data = this.clientIdToHashMap(model.constructor);
592
+
593
+ data[clientId] = hash;
594
+ model.set('data', hash);
595
+ }
596
+
597
+ model.adapterDidUpdate();
598
+ },
599
+
600
+ didDeleteModels: function(array) {
601
+ array.forEach(function(model) {
602
+ model.adapterDidDelete();
603
+ });
604
+ },
605
+
606
+ didDeleteModel: function(model) {
607
+ model.adapterDidDelete();
608
+ },
609
+
610
+ didCreateModels: function(type, array, hashes) {
611
+ var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
612
+
613
+ var idToClientIdMap = this.idToClientIdMap(type);
614
+ var data = this.clientIdToHashMap(type);
615
+ var idList = this.idList(type);
616
+
617
+ for (var i=0, l=get(array, 'length'); i<l; i++) {
618
+ var model = array[i], hash = hashes[i];
619
+ id = hash[primaryKey];
620
+ clientId = get(model, 'clientId');
621
+
622
+ data[clientId] = hash;
623
+ set(model, 'data', hash);
624
+
625
+ idToClientIdMap[id] = clientId;
626
+ idList.push(id);
627
+
628
+ model.adapterDidUpdate();
629
+ }
630
+ },
631
+
632
+ didCreateModel: function(model, hash) {
633
+ var type = model.constructor;
634
+
635
+ var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
636
+
637
+ var idToClientIdMap = this.idToClientIdMap(type);
638
+ var data = this.clientIdToHashMap(type);
639
+ var idList = this.idList(type);
640
+
641
+ id = hash[primaryKey];
642
+
643
+ clientId = get(model, 'clientId');
644
+ data[clientId] = hash;
645
+ set(model, 'data', hash);
646
+
647
+ idToClientIdMap[id] = clientId;
648
+ idList.push(id);
649
+
650
+ model.adapterDidUpdate();
651
+ },
652
+
653
+ // ................
654
+ // . MODEL ARRAYS .
655
+ // ................
656
+
657
+ registerModelArray: function(array, type, filter) {
658
+ var modelArrays = get(this, 'modelArrays');
659
+ var idToClientIdMap = this.idToClientIdMap(type);
660
+
661
+ modelArrays.push(array);
662
+
663
+ this.updateModelArrayFilter(array, type, filter);
664
+ },
665
+
666
+ createModelArray: function(type, clientIds) {
667
+ var array = DS.ModelArray.create({ type: type, content: clientIds, store: this });
668
+
669
+ clientIds.forEach(function(clientId) {
670
+ var modelArrays = this.modelArraysForClientId(clientId);
671
+ modelArrays.add(array);
672
+ }, this);
673
+
674
+ return array;
675
+ },
676
+
677
+ updateModelArrayFilter: function(array, type, filter) {
678
+ var data = this.clientIdToHashMap(type);
679
+ var allClientIds = this.clientIdList(type);
680
+
681
+ for (var i=0, l=allClientIds.length; i<l; i++) {
682
+ clientId = allClientIds[i];
683
+
684
+ hash = data[clientId];
685
+
686
+ if (hash) {
687
+ this.updateModelArray(array, filter, type, clientId, hash);
688
+ }
689
+ }
690
+ },
691
+
692
+ updateModelArrays: function(type, clientId, hash) {
693
+ var modelArrays = get(this, 'modelArrays');
694
+
695
+ modelArrays.forEach(function(array) {
696
+ modelArrayType = get(array, 'type');
697
+ filter = get(array, 'filterFunction');
698
+
699
+ if (type !== modelArrayType) { return; }
700
+
701
+ this.updateModelArray(array, filter, type, clientId, hash);
702
+ }, this);
703
+ },
704
+
705
+ updateModelArray: function(array, filter, type, clientId, hash) {
706
+ var shouldBeInArray;
707
+
708
+ if (!filter) {
709
+ shouldBeInArray = true;
710
+ } else {
711
+ shouldBeInArray = filter(hash);
712
+ }
713
+
714
+ var content = get(array, 'content');
715
+ var alreadyInArray = content.indexOf(clientId) !== -1;
716
+
717
+ var modelArrays = this.modelArraysForClientId(clientId);
718
+
719
+ if (shouldBeInArray && !alreadyInArray) {
720
+ modelArrays.add(array);
721
+ content.pushObject(clientId);
722
+ } else if (!shouldBeInArray && alreadyInArray) {
723
+ modelArrays.remove(array);
724
+ content.removeObject(clientId);
725
+ }
726
+ },
727
+
728
+ removeFromModelArrays: function(model) {
729
+ var clientId = get(model, 'clientId');
730
+ var modelArrays = this.modelArraysForClientId(clientId);
731
+
732
+ modelArrays.forEach(function(array) {
733
+ var content = get(array, 'content');
734
+ content.removeObject(clientId);
735
+ });
736
+ },
737
+
738
+ // ............
739
+ // . TYPE MAP .
740
+ // ............
741
+
742
+ typeMap: function(type) {
743
+ var ids = get(this, 'ids');
744
+ var guidForType = SC.guidFor(type);
745
+
746
+ var idToClientIdMap = ids[guidForType];
747
+
748
+ if (idToClientIdMap) {
749
+ return idToClientIdMap;
750
+ } else {
751
+ return (ids[guidForType] =
752
+ {
753
+ idToCid: {},
754
+ idList: [],
755
+ cidList: [],
756
+ cidToHash: {},
757
+ updatedModels: OrderedSet.create(),
758
+ createdModels: OrderedSet.create(),
759
+ deletedModels: OrderedSet.create()
760
+ });
761
+ }
762
+ },
763
+
764
+ idToClientIdMap: function(type) {
765
+ return this.typeMap(type).idToCid;
766
+ },
767
+
768
+ idList: function(type) {
769
+ return this.typeMap(type).idList;
770
+ },
771
+
772
+ clientIdList: function(type) {
773
+ return this.typeMap(type).cidList;
774
+ },
775
+
776
+ clientIdToHashMap: function(type) {
777
+ return this.typeMap(type).cidToHash;
778
+ },
779
+
780
+ /** @private
781
+
782
+ For a given type and id combination, returns the client id used by the store.
783
+ If no client id has been assigned yet, `undefined` is returned.
784
+
785
+ @param {DS.Model} type
786
+ @param {String|Number} id
787
+ */
788
+ clientIdForId: function(type, id) {
789
+ return this.typeMap(type).idToCid[id];
790
+ },
791
+
792
+ idForHash: function(type, hash) {
793
+ var primaryKey = getPath(type, 'proto.primaryKey');
794
+
795
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
796
+ return hash[primaryKey];
797
+ },
798
+
799
+ // ................
800
+ // . LOADING DATA .
801
+ // ................
802
+
803
+ /**
804
+ Load a new data hash into the store for a given id and type combination.
805
+ If data for that model had been loaded previously, the new information
806
+ overwrites the old.
807
+
808
+ If the model you are loading data for has outstanding changes that have not
809
+ yet been saved, an exception will be thrown.
810
+
811
+ @param {DS.Model} type
812
+ @param {String|Number} id
813
+ @param {Object} hash the data hash to load
814
+ */
815
+ load: function(type, id, hash) {
816
+ if (hash === undefined) {
817
+ hash = id;
818
+ var primaryKey = getPath(type, 'proto.primaryKey');
819
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
820
+ id = hash[primaryKey];
821
+ }
822
+
823
+ var ids = get(this, 'ids');
824
+ var data = this.clientIdToHashMap(type);
825
+ var models = get(this, 'models');
826
+
827
+ var clientId = this.clientIdForId(type, id);
828
+
829
+ if (clientId !== undefined) {
830
+ data[clientId] = hash;
831
+
832
+ var model = models[clientId];
833
+ if (model) {
834
+ model.willLoadData();
835
+ model.setData(hash);
836
+ }
837
+ } else {
838
+ clientId = this.pushHash(hash, id, type);
839
+ }
840
+
841
+ this.updateModelArrays(type, clientId, hash);
842
+
843
+ return { id: id, clientId: clientId };
844
+ },
845
+
846
+ loadMany: function(type, ids, hashes) {
847
+ var clientIds = Ember.A([]);
848
+
849
+ if (hashes === undefined) {
850
+ hashes = ids;
851
+ ids = [];
852
+ var primaryKey = getPath(type, 'proto.primaryKey');
853
+
854
+ ids = hashes.map(function(hash) {
855
+ ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
856
+ return hash[primaryKey];
857
+ });
858
+ }
859
+
860
+ for (var i=0, l=get(ids, 'length'); i<l; i++) {
861
+ var loaded = this.load(type, ids[i], hashes[i]);
862
+ clientIds.pushObject(loaded.clientId);
863
+ }
864
+
865
+ return { clientIds: clientIds, ids: ids };
866
+ },
867
+
868
+ /** @private
869
+
870
+ Stores a data hash for the specified type and id combination and returns
871
+ the client id.
872
+
873
+ @param {Object} hash
874
+ @param {String|Number} id
875
+ @param {DS.Model} type
876
+ @returns {Number}
877
+ */
878
+ pushHash: function(hash, id, type) {
879
+ var idToClientIdMap = this.idToClientIdMap(type);
880
+ var clientIdList = this.clientIdList(type);
881
+ var idList = this.idList(type);
882
+ var data = this.clientIdToHashMap(type);
883
+
884
+ var clientId = this.incrementProperty('clientIdCounter');
885
+
886
+ data[clientId] = hash;
887
+
888
+ // if we're creating an item, this process will be done
889
+ // later, once the object has been persisted.
890
+ if (id) {
891
+ idToClientIdMap[id] = clientId;
892
+ idList.push(id);
893
+ }
894
+
895
+ clientIdList.push(clientId);
896
+
897
+ return clientId;
898
+ },
899
+
900
+ // .........................
901
+ // . MODEL MATERIALIZATION .
902
+ // .........................
903
+
904
+ createModel: function(type, clientId) {
905
+ var model;
906
+
907
+ get(this, 'models')[clientId] = model = type.create({ store: this, clientId: clientId });
908
+ set(model, 'clientId', clientId);
909
+ model.loadingData();
910
+ return model;
911
+ }
912
+ });
913
+
914
+
915
+ })({});
916
+
917
+
918
+ (function(exports) {
919
+ var get = SC.get, set = SC.set, getPath = SC.getPath;
920
+
921
+ var stateProperty = SC.computed(function(key) {
922
+ var parent = get(this, 'parentState');
923
+ if (parent) {
924
+ return get(parent, key);
925
+ }
926
+ }).property();
927
+
928
+ DS.State = SC.State.extend({
929
+ isLoaded: stateProperty,
930
+ isDirty: stateProperty,
931
+ isSaving: stateProperty,
932
+ isDeleted: stateProperty,
933
+ isError: stateProperty,
934
+ isNew: stateProperty
935
+ });
936
+
937
+ var cantLoadData = function() {
938
+ // TODO: get the current state name
939
+ throw "You cannot load data into the store when its associated model is in its current state";
940
+ };
941
+
942
+ var states = {
943
+ rootState: SC.State.create({
944
+ isLoaded: false,
945
+ isDirty: false,
946
+ isSaving: false,
947
+ isDeleted: false,
948
+ isError: false,
949
+ isNew: false,
950
+
951
+ willLoadData: cantLoadData,
952
+
953
+ didCreate: function(manager) {
954
+ manager.goToState('loaded.created');
955
+ },
956
+
957
+ empty: DS.State.create({
958
+ loadingData: function(manager) {
959
+ manager.goToState('loading');
960
+ }
961
+ }),
962
+
963
+ loading: DS.State.create({
964
+ willLoadData: SC.K,
965
+
966
+ exit: function(manager) {
967
+ var model = get(manager, 'model');
968
+ model.didLoad();
969
+ },
970
+
971
+ setData: function(manager, data) {
972
+ var model = get(manager, 'model');
973
+
974
+ model.beginPropertyChanges();
975
+ model.set('data', data);
976
+
977
+ if (data !== null) {
978
+ manager.goToState('loaded');
979
+ }
980
+
981
+ model.endPropertyChanges();
982
+ }
983
+ }),
984
+
985
+ loaded: DS.State.create({
986
+ isLoaded: true,
987
+
988
+ willLoadData: SC.K,
989
+
990
+ setProperty: function(manager, context) {
991
+ var key = context.key, value = context.value;
992
+
993
+ var model = get(manager, 'model'), type = model.constructor;
994
+ var store = get(model, 'store');
995
+ var data = get(model, 'data');
996
+
997
+ data[key] = value;
998
+
999
+ if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
1000
+
1001
+ manager.goToState('updated');
1002
+ },
1003
+
1004
+ 'delete': function(manager) {
1005
+ manager.goToState('deleted');
1006
+ },
1007
+
1008
+ created: DS.State.create({
1009
+ isNew: true,
1010
+ isDirty: true,
1011
+
1012
+ enter: function(manager) {
1013
+ var model = get(manager, 'model');
1014
+ var store = get(model, 'store');
1015
+
1016
+ if (store) { store.modelBecameDirty('created', model); }
1017
+ },
1018
+
1019
+ exit: function(manager) {
1020
+ var model = get(manager, 'model');
1021
+ var store = get(model, 'store');
1022
+
1023
+ model.didCreate();
1024
+
1025
+ if (store) { store.modelBecameClean('created', model); }
1026
+ },
1027
+
1028
+ setProperty: function(manager, context) {
1029
+ var key = context.key, value = context.value;
1030
+
1031
+ var model = get(manager, 'model'), type = model.constructor;
1032
+ var store = get(model, 'store');
1033
+ var data = get(model, 'data');
1034
+
1035
+ data[key] = value;
1036
+
1037
+ if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
1038
+ },
1039
+
1040
+ willCommit: function(manager) {
1041
+ manager.goToState('saving');
1042
+ },
1043
+
1044
+ saving: DS.State.create({
1045
+ isSaving: true,
1046
+
1047
+ didUpdate: function(manager) {
1048
+ manager.goToState('loaded');
1049
+ }
1050
+ })
1051
+ }),
1052
+
1053
+ updated: DS.State.create({
1054
+ isDirty: true,
1055
+
1056
+ willLoadData: cantLoadData,
1057
+
1058
+ enter: function(manager) {
1059
+ var model = get(manager, 'model');
1060
+ var store = get(model, 'store');
1061
+
1062
+ if (store) { store.modelBecameDirty('updated', model); }
1063
+ },
1064
+
1065
+ willCommit: function(manager) {
1066
+ manager.goToState('saving');
1067
+ },
1068
+
1069
+ exit: function(manager) {
1070
+ var model = get(manager, 'model');
1071
+ var store = get(model, 'store');
1072
+
1073
+ model.didUpdate();
1074
+
1075
+ if (store) { store.modelBecameClean('updated', model); }
1076
+ },
1077
+
1078
+ saving: DS.State.create({
1079
+ isSaving: true,
1080
+
1081
+ didUpdate: function(manager) {
1082
+ manager.goToState('loaded');
1083
+ }
1084
+ })
1085
+ })
1086
+ }),
1087
+
1088
+ deleted: DS.State.create({
1089
+ isDeleted: true,
1090
+ isLoaded: true,
1091
+ isDirty: true,
1092
+
1093
+ willLoadData: cantLoadData,
1094
+
1095
+ enter: function(manager) {
1096
+ var model = get(manager, 'model');
1097
+ var store = get(model, 'store');
1098
+
1099
+ if (store) {
1100
+ store.removeFromModelArrays(model);
1101
+ store.modelBecameDirty('deleted', model);
1102
+ }
1103
+ },
1104
+
1105
+ willCommit: function(manager) {
1106
+ manager.goToState('saving');
1107
+ },
1108
+
1109
+ saving: DS.State.create({
1110
+ isSaving: true,
1111
+
1112
+ didDelete: function(manager) {
1113
+ manager.goToState('saved');
1114
+ },
1115
+
1116
+ exit: function(stateManager) {
1117
+ var model = get(stateManager, 'model');
1118
+ var store = get(model, 'store');
1119
+
1120
+ store.modelBecameClean('deleted', model);
1121
+ }
1122
+ }),
1123
+
1124
+ saved: DS.State.create({
1125
+ isDirty: false
1126
+ })
1127
+ }),
1128
+
1129
+ error: DS.State.create({
1130
+ isError: true
1131
+ })
1132
+ })
1133
+ };
1134
+
1135
+ DS.StateManager = Ember.StateManager.extend({
1136
+ model: null,
1137
+ initialState: 'rootState',
1138
+ states: states
1139
+ });
1140
+
1141
+ var retrieveFromCurrentState = SC.computed(function(key) {
1142
+ return get(getPath(this, 'stateManager.currentState'), key);
1143
+ }).property('stateManager.currentState').cacheable();
1144
+
1145
+ DS.Model = SC.Object.extend({
1146
+ isLoaded: retrieveFromCurrentState,
1147
+ isDirty: retrieveFromCurrentState,
1148
+ isSaving: retrieveFromCurrentState,
1149
+ isDeleted: retrieveFromCurrentState,
1150
+ isError: retrieveFromCurrentState,
1151
+ isNew: retrieveFromCurrentState,
1152
+
1153
+ clientId: null,
1154
+
1155
+ primaryKey: 'id',
1156
+ data: null,
1157
+
1158
+ didLoad: Ember.K,
1159
+ didUpdate: Ember.K,
1160
+ didCreate: Ember.K,
1161
+
1162
+ init: function() {
1163
+ var stateManager = DS.StateManager.create({
1164
+ model: this
1165
+ });
1166
+
1167
+ set(this, 'stateManager', stateManager);
1168
+ stateManager.goToState('empty');
1169
+ },
1170
+
1171
+ setData: function(data) {
1172
+ var stateManager = get(this, 'stateManager');
1173
+ stateManager.send('setData', data);
1174
+ },
1175
+
1176
+ setProperty: function(key, value) {
1177
+ var stateManager = get(this, 'stateManager');
1178
+ stateManager.send('setProperty', { key: key, value: value });
1179
+ },
1180
+
1181
+ "deleteModel": function() {
1182
+ var stateManager = get(this, 'stateManager');
1183
+ stateManager.send('delete');
1184
+ },
1185
+
1186
+ loadingData: function() {
1187
+ var stateManager = get(this, 'stateManager');
1188
+ stateManager.send('loadingData');
1189
+ },
1190
+
1191
+ willLoadData: function() {
1192
+ var stateManager = get(this, 'stateManager');
1193
+ stateManager.send('willLoadData');
1194
+ },
1195
+
1196
+ willCommit: function() {
1197
+ var stateManager = get(this, 'stateManager');
1198
+ stateManager.send('willCommit');
1199
+ },
1200
+
1201
+ adapterDidUpdate: function() {
1202
+ var stateManager = get(this, 'stateManager');
1203
+ stateManager.send('didUpdate');
1204
+ },
1205
+
1206
+ adapterDidCreate: function() {
1207
+ var stateManager = get(this, 'stateManager');
1208
+ stateManager.send('didCreate');
1209
+ },
1210
+
1211
+ adapterDidDelete: function() {
1212
+ var stateManager = get(this, 'stateManager');
1213
+ stateManager.send('didDelete');
1214
+ },
1215
+
1216
+ unknownProperty: function(key) {
1217
+ var data = get(this, 'data');
1218
+
1219
+ if (data) {
1220
+ return get(data, key);
1221
+ }
1222
+ },
1223
+
1224
+ setUnknownProperty: function(key, value) {
1225
+ var data = get(this, 'data');
1226
+ ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
1227
+
1228
+ this.setProperty(key, value);
1229
+ return value;
1230
+ }
1231
+ });
1232
+
1233
+ DS.attr = function(type, options) {
1234
+ var transform = DS.attr.transforms[type];
1235
+ var transformFrom = transform.from;
1236
+ var transformTo = transform.to;
1237
+
1238
+ return SC.computed(function(key, value) {
1239
+ var data = get(this, 'data');
1240
+
1241
+ key = (options && options.key) ? options.key : key;
1242
+
1243
+ if (value === undefined) {
1244
+ if (!data) { return; }
1245
+
1246
+ return transformFrom(data[key]);
1247
+ } else {
1248
+ ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
1249
+
1250
+ value = transformTo(value);
1251
+ this.setProperty(key, value);
1252
+ return value;
1253
+ }
1254
+ }).property('data');
1255
+ };
1256
+
1257
+ var embeddedFindMany = function(store, type, data, key) {
1258
+ var association = data ? get(data, key) : [];
1259
+ return store.loadMany(type, association).ids;
1260
+ };
1261
+
1262
+ var referencedFindMany = function(store, type, data, key) {
1263
+ return data ? get(data, key) : [];
1264
+ };
1265
+
1266
+ DS.hasMany = function(type, options) {
1267
+ var embedded = options && options.embedded, load;
1268
+
1269
+ findMany = embedded ? embeddedFindMany : referencedFindMany;
1270
+
1271
+ return SC.computed(function(key) {
1272
+ var data = get(this, 'data'), ids;
1273
+ var store = get(this, 'store');
1274
+
1275
+ key = (options && options.key) ? options.key : key;
1276
+ ids = findMany(store, type, data, key);
1277
+ var hasMany = store.findMany(type, ids);
1278
+
1279
+ SC.addObserver(this, 'data', function() {
1280
+ var data = get(this, 'data');
1281
+
1282
+ var ids = findMany(store, type, data, key);
1283
+ store.findMany(type, ids);
1284
+
1285
+ var idToClientIdMap = store.idToClientIdMap(type);
1286
+
1287
+ var clientIds = ids.map(function(id) {
1288
+ return idToClientIdMap[id];
1289
+ });
1290
+
1291
+ set(hasMany, 'content', Ember.A(clientIds));
1292
+ });
1293
+
1294
+ return hasMany;
1295
+ }).property().cacheable();
1296
+ };
1297
+
1298
+ DS.attr.transforms = {
1299
+ string: {
1300
+ from: function(serialized) {
1301
+ return String(serialized);
1302
+ },
1303
+
1304
+ to: function(deserialized) {
1305
+ return String(deserialized);
1306
+ }
1307
+ },
1308
+
1309
+ integer: {
1310
+ from: function(serialized) {
1311
+ return Number(serialized);
1312
+ },
1313
+
1314
+ to: function(deserialized) {
1315
+ return Number(deserialized);
1316
+ }
1317
+ },
1318
+
1319
+ boolean: {
1320
+ from: function(serialized) {
1321
+ return Boolean(serialized);
1322
+ },
1323
+
1324
+ to: function(deserialized) {
1325
+ return Boolean(deserialized);
1326
+ }
1327
+ },
1328
+
1329
+ date: {
1330
+ from: function(serialized) {
1331
+ var type = typeof serialized;
1332
+
1333
+ if (type === "string" || type === "number") {
1334
+ return new Date(serialized);
1335
+ } else if (serialized == null) {
1336
+ // if the value is not present in the data,
1337
+ // return undefined, not null.
1338
+ return serialized;
1339
+ } else {
1340
+ return null;
1341
+ }
1342
+ },
1343
+
1344
+ to: function(date) {
1345
+ if (date instanceof Date) {
1346
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1347
+ var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1348
+
1349
+ var pad = function(num) {
1350
+ return num < 10 ? "0"+num : ""+num;
1351
+ };
1352
+
1353
+ var utcYear = date.getUTCFullYear(),
1354
+ utcMonth = date.getUTCMonth(),
1355
+ utcDayOfMonth = date.getUTCDate(),
1356
+ utcDay = date.getUTCDay(),
1357
+ utcHours = date.getUTCHours(),
1358
+ utcMinutes = date.getUTCMinutes(),
1359
+ utcSeconds = date.getUTCSeconds();
1360
+
1361
+
1362
+ var dayOfWeek = days[utcDay];
1363
+ var dayOfMonth = pad(utcDayOfMonth);
1364
+ var month = months[utcMonth];
1365
+
1366
+ return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
1367
+ pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
1368
+ } else if (date === undefined) {
1369
+ return undefined;
1370
+ } else {
1371
+ return null;
1372
+ }
1373
+ }
1374
+ }
1375
+ };
1376
+
1377
+ })({});
1378
+
1379
+
1380
+ (function(exports) {
1381
+ //Copyright (C) 2011 by Living Social, Inc.
1382
+
1383
+ //Permission is hereby granted, free of charge, to any person obtaining a copy of
1384
+ //this software and associated documentation files (the "Software"), to deal in
1385
+ //the Software without restriction, including without limitation the rights to
1386
+ //use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1387
+ //of the Software, and to permit persons to whom the Software is furnished to do
1388
+ //so, subject to the following conditions:
1389
+
1390
+ //The above copyright notice and this permission notice shall be included in all
1391
+ //copies or substantial portions of the Software.
1392
+
1393
+ //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1394
+ //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1395
+ //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1396
+ //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1397
+ //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1398
+ //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1399
+ //SOFTWARE.
1400
+ })({});