rasputin 0.11.3 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ })({});