embient 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,38 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- embient (0.0.8)
4
+ embient (0.0.9)
5
5
  emberjs-rails
6
6
  rails (>= 3.1.0)
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- actionmailer (3.2.1)
12
- actionpack (= 3.2.1)
11
+ actionmailer (3.2.0)
12
+ actionpack (= 3.2.0)
13
13
  mail (~> 2.4.0)
14
- actionpack (3.2.1)
15
- activemodel (= 3.2.1)
16
- activesupport (= 3.2.1)
14
+ actionpack (3.2.0)
15
+ activemodel (= 3.2.0)
16
+ activesupport (= 3.2.0)
17
17
  builder (~> 3.0.0)
18
18
  erubis (~> 2.7.0)
19
- journey (~> 1.0.1)
19
+ journey (~> 1.0.0)
20
20
  rack (~> 1.4.0)
21
21
  rack-cache (~> 1.1)
22
22
  rack-test (~> 0.6.1)
23
23
  sprockets (~> 2.1.2)
24
- activemodel (3.2.1)
25
- activesupport (= 3.2.1)
24
+ activemodel (3.2.0)
25
+ activesupport (= 3.2.0)
26
26
  builder (~> 3.0.0)
27
- activerecord (3.2.1)
28
- activemodel (= 3.2.1)
29
- activesupport (= 3.2.1)
27
+ activerecord (3.2.0)
28
+ activemodel (= 3.2.0)
29
+ activesupport (= 3.2.0)
30
30
  arel (~> 3.0.0)
31
31
  tzinfo (~> 0.3.29)
32
- activeresource (3.2.1)
33
- activemodel (= 3.2.1)
34
- activesupport (= 3.2.1)
35
- activesupport (3.2.1)
32
+ activeresource (3.2.0)
33
+ activemodel (= 3.2.0)
34
+ activesupport (= 3.2.0)
35
+ activesupport (3.2.0)
36
36
  i18n (~> 0.6)
37
37
  multi_json (~> 1.0)
38
38
  arel (3.0.2)
@@ -52,13 +52,13 @@ GEM
52
52
  hike (1.2.1)
53
53
  i18n (0.6.0)
54
54
  journey (1.0.3)
55
- json (1.6.5)
55
+ json (1.6.6)
56
56
  mail (2.4.4)
57
57
  i18n (>= 0.4.0)
58
58
  mime-types (~> 1.16)
59
59
  treetop (~> 1.4.8)
60
60
  mime-types (1.18)
61
- multi_json (1.1.0)
61
+ multi_json (1.2.0)
62
62
  polyglot (0.3.3)
63
63
  rack (1.4.1)
64
64
  rack-cache (1.2)
@@ -67,17 +67,17 @@ GEM
67
67
  rack
68
68
  rack-test (0.6.1)
69
69
  rack (>= 1.0)
70
- rails (3.2.1)
71
- actionmailer (= 3.2.1)
72
- actionpack (= 3.2.1)
73
- activerecord (= 3.2.1)
74
- activeresource (= 3.2.1)
75
- activesupport (= 3.2.1)
70
+ rails (3.2.0)
71
+ actionmailer (= 3.2.0)
72
+ actionpack (= 3.2.0)
73
+ activerecord (= 3.2.0)
74
+ activeresource (= 3.2.0)
75
+ activesupport (= 3.2.0)
76
76
  bundler (~> 1.0)
77
- railties (= 3.2.1)
78
- railties (3.2.1)
79
- actionpack (= 3.2.1)
80
- activesupport (= 3.2.1)
77
+ railties (= 3.2.0)
78
+ railties (3.2.0)
79
+ actionpack (= 3.2.0)
80
+ activesupport (= 3.2.0)
81
81
  rack-ssl (~> 1.3.2)
82
82
  rake (>= 0.8.7)
83
83
  rdoc (~> 3.4)
@@ -94,7 +94,7 @@ GEM
94
94
  treetop (1.4.10)
95
95
  polyglot
96
96
  polyglot (>= 0.3.1)
97
- tzinfo (0.3.32)
97
+ tzinfo (0.3.33)
98
98
 
99
99
  PLATFORMS
100
100
  ruby
@@ -1,3 +1,3 @@
1
1
  module Embient
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -1,510 +1,426 @@
1
-
2
- (function(exports) {
1
+ (function() {
3
2
  window.DS = Ember.Namespace.create({
4
- CURRENT_API_REVISION: 3
3
+ CURRENT_API_REVISION: 4
5
4
  });
6
5
 
7
- })({});
6
+ })();
8
7
 
9
8
 
10
- (function(exports) {
11
- DS.Adapter = Ember.Object.extend({
12
- commit: function(store, commitDetails) {
13
- commitDetails.updated.eachType(function(type, array) {
14
- this.updateRecords(store, type, array.slice());
15
- }, this);
16
9
 
17
- commitDetails.created.eachType(function(type, array) {
18
- this.createRecords(store, type, array.slice());
19
- }, this);
10
+ (function() {
11
+ var get = Ember.get, set = Ember.set;
20
12
 
21
- commitDetails.deleted.eachType(function(type, array) {
22
- this.deleteRecords(store, type, array.slice());
23
- }, this);
24
- },
13
+ /**
14
+ A record array is an array that contains records of a certain type. The record
15
+ array materializes records as needed when they are retrieved for the first
16
+ time. You should not create record arrays yourself. Instead, an instance of
17
+ DS.RecordArray or its subclasses will be returned by your application's store
18
+ in response to queries.
19
+ */
25
20
 
26
- createRecords: function(store, type, models) {
27
- models.forEach(function(model) {
28
- this.createRecord(store, type, model);
29
- }, this);
30
- },
21
+ DS.RecordArray = Ember.ArrayProxy.extend({
31
22
 
32
- updateRecords: function(store, type, models) {
33
- models.forEach(function(model) {
34
- this.updateRecord(store, type, model);
35
- }, this);
36
- },
23
+ /**
24
+ The model type contained by this record array.
37
25
 
38
- deleteRecords: function(store, type, models) {
39
- models.forEach(function(model) {
40
- this.deleteRecord(store, type, model);
41
- }, this);
42
- },
26
+ @type DS.Model
27
+ */
28
+ type: null,
43
29
 
44
- findMany: function(store, type, ids) {
45
- ids.forEach(function(id) {
46
- this.find(store, type, id);
47
- }, this);
48
- }
49
- });
50
- })({});
30
+ // The array of client ids backing the record array. When a
31
+ // record is requested from the record array, the record
32
+ // for the client id at the same index is materialized, if
33
+ // necessary, by the store.
34
+ content: null,
51
35
 
36
+ // The store that created this record array.
37
+ store: null,
52
38
 
53
- (function(exports) {
54
- DS.fixtureAdapter = DS.Adapter.create({
55
- find: function(store, type, id) {
56
- var fixtures = type.FIXTURES;
39
+ init: function() {
40
+ set(this, 'recordCache', Ember.A([]));
41
+ this._super();
42
+ },
57
43
 
58
- ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
59
- if (fixtures.hasLoaded) { return; }
44
+ arrayDidChange: function(array, index, removed, added) {
45
+ var recordCache = get(this, 'recordCache');
46
+ recordCache.replace(index, 0, new Array(added));
60
47
 
61
- setTimeout(function() {
62
- store.loadMany(type, fixtures);
63
- fixtures.hasLoaded = true;
64
- }, 300);
48
+ this._super(array, index, removed, added);
65
49
  },
66
50
 
67
- findMany: function() {
68
- this.find.apply(this, arguments);
51
+ arrayWillChange: function(array, index, removed, added) {
52
+ this._super(array, index, removed, added);
53
+
54
+ var recordCache = get(this, 'recordCache');
55
+ recordCache.replace(index, removed);
69
56
  },
70
57
 
71
- findAll: function(store, type) {
72
- var fixtures = type.FIXTURES;
58
+ objectAtContent: function(index) {
59
+ var recordCache = get(this, 'recordCache');
60
+ var record = recordCache.objectAt(index);
73
61
 
74
- ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
62
+ if (!record) {
63
+ var store = get(this, 'store');
64
+ var content = get(this, 'content');
75
65
 
76
- var ids = fixtures.map(function(item, index, self){ return item.id; });
77
- store.loadMany(type, ids, fixtures);
78
- }
66
+ var contentObject = content.objectAt(index);
67
+
68
+ if (contentObject !== undefined) {
69
+ record = store.findByClientId(get(this, 'type'), contentObject);
70
+ recordCache.replace(index, 1, [record]);
71
+ }
72
+ }
79
73
 
74
+ return record;
75
+ }
80
76
  });
81
77
 
82
- })({});
78
+ })();
83
79
 
84
80
 
85
- (function(exports) {
86
- /*global jQuery*/
87
- var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
88
81
 
89
- DS.RESTAdapter = DS.Adapter.extend({
90
- createRecord: function(store, type, model) {
91
- var root = this.rootForType(type);
82
+ (function() {
83
+ var get = Ember.get;
92
84
 
93
- var data = {};
94
- data[root] = model.toJSON();
85
+ DS.FilteredRecordArray = DS.RecordArray.extend({
86
+ filterFunction: null,
95
87
 
96
- this.ajax(this.buildURL(root), "POST", {
97
- data: data,
98
- success: function(json) {
99
- this.sideload(store, type, json, root);
100
- store.didCreateRecord(model, json[root]);
101
- }
102
- });
88
+ replace: function() {
89
+ var type = get(this, 'type').toString();
90
+ throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
103
91
  },
104
92
 
105
- createRecords: function(store, type, models) {
106
- if (get(this, 'bulkCommit') === false) {
107
- return this._super(store, type, models);
108
- }
109
-
110
- var root = this.rootForType(type),
111
- plural = this.pluralize(root);
93
+ updateFilter: Ember.observer(function() {
94
+ var store = get(this, 'store');
95
+ store.updateRecordArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
96
+ }, 'filterFunction')
97
+ });
112
98
 
113
- var data = {};
114
- data[plural] = models.map(function(model) {
115
- return model.toJSON();
116
- });
99
+ })();
117
100
 
118
- this.ajax(this.buildURL(root), "POST", {
119
- data: data,
120
101
 
121
- success: function(json) {
122
- this.sideload(store, type, json, plural);
123
- store.didCreateRecords(type, models, json[plural]);
124
- }
125
- });
126
- },
127
102
 
128
- updateRecord: function(store, type, model) {
129
- var id = get(model, 'id');
130
- var root = this.rootForType(type);
103
+ (function() {
104
+ var get = Ember.get, set = Ember.set;
131
105
 
132
- var data = {};
133
- data[root] = model.toJSON();
106
+ DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
107
+ query: null,
108
+ isLoaded: false,
134
109
 
135
- this.ajax(this.buildURL(root, id), "PUT", {
136
- data: data,
137
- success: function(json) {
138
- this.sideload(store, type, json, root);
139
- store.didUpdateRecord(model, json && json[root]);
140
- }
141
- });
110
+ replace: function() {
111
+ var type = get(this, 'type').toString();
112
+ throw new Error("The result of a server query (on " + type + ") is immutable.");
142
113
  },
143
114
 
144
- updateRecords: function(store, type, models) {
145
- if (get(this, 'bulkCommit') === false) {
146
- return this._super(store, type, models);
147
- }
115
+ load: function(array) {
116
+ var store = get(this, 'store'), type = get(this, 'type');
148
117
 
149
- var root = this.rootForType(type),
150
- plural = this.pluralize(root);
118
+ var clientIds = store.loadMany(type, array).clientIds;
151
119
 
152
- var data = {};
153
- data[plural] = models.map(function(model) {
154
- return model.toJSON();
155
- });
120
+ this.beginPropertyChanges();
121
+ set(this, 'content', Ember.A(clientIds));
122
+ set(this, 'isLoaded', true);
123
+ this.endPropertyChanges();
124
+ }
125
+ });
156
126
 
157
- this.ajax(this.buildURL(root, "bulk"), "PUT", {
158
- data: data,
159
- success: function(json) {
160
- this.sideload(store, type, json, plural);
161
- store.didUpdateRecords(models, json[plural]);
162
- }
163
- });
164
- },
165
127
 
166
- deleteRecord: function(store, type, model) {
167
- var id = get(model, 'id');
168
- var root = this.rootForType(type);
128
+ })();
169
129
 
170
- this.ajax(this.buildURL(root, id), "DELETE", {
171
- success: function(json) {
172
- if (json) { this.sideload(store, type, json); }
173
- store.didDeleteRecord(model);
174
- }
175
- });
176
- },
177
130
 
178
- deleteRecords: function(store, type, models) {
179
- if (get(this, 'bulkCommit') === false) {
180
- return this._super(store, type, models);
181
- }
182
131
 
183
- var root = this.rootForType(type),
184
- plural = this.pluralize(root);
132
+ (function() {
133
+ var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor;
185
134
 
186
- var data = {};
187
- data[plural] = models.map(function(model) {
188
- return get(model, 'id');
189
- });
135
+ var Set = function() {
136
+ this.hash = {};
137
+ this.list = [];
138
+ };
190
139
 
191
- this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
192
- data: data,
193
- success: function(json) {
194
- if (json) { this.sideload(store, type, json); }
195
- store.didDeleteRecords(models);
196
- }
197
- });
198
- },
140
+ Set.prototype = {
141
+ add: function(item) {
142
+ var hash = this.hash,
143
+ guid = guidFor(item);
199
144
 
200
- find: function(store, type, id) {
201
- var root = this.rootForType(type);
145
+ if (hash.hasOwnProperty(guid)) { return; }
202
146
 
203
- this.ajax(this.buildURL(root, id), "GET", {
204
- success: function(json) {
205
- store.load(type, json[root]);
206
- this.sideload(store, type, json, root);
207
- }
208
- });
147
+ hash[guid] = true;
148
+ this.list.push(item);
209
149
  },
210
150
 
211
- findMany: function(store, type, ids) {
212
- var root = this.rootForType(type), plural = this.pluralize(root);
151
+ remove: function(item) {
152
+ var hash = this.hash,
153
+ guid = guidFor(item);
213
154
 
214
- this.ajax(this.buildURL(root), "GET", {
215
- data: { ids: ids },
216
- success: function(json) {
217
- store.loadMany(type, ids, json[plural]);
218
- this.sideload(store, type, json, plural);
219
- }
220
- });
221
- },
155
+ if (!hash.hasOwnProperty(guid)) { return; }
222
156
 
223
- findAll: function(store, type) {
224
- var root = this.rootForType(type), plural = this.pluralize(root);
157
+ delete hash[guid];
158
+ var list = this.list,
159
+ index = Ember.ArrayUtils.indexOf(this, item);
225
160
 
226
- this.ajax(this.buildURL(root), "GET", {
227
- success: function(json) {
228
- store.loadMany(type, json[plural]);
229
- this.sideload(store, type, json, plural);
230
- }
231
- });
161
+ list.splice(index, 1);
232
162
  },
233
163
 
234
- findQuery: function(store, type, query, modelArray) {
235
- var root = this.rootForType(type), plural = this.pluralize(root);
164
+ isEmpty: function() {
165
+ return this.list.length === 0;
166
+ }
167
+ };
236
168
 
237
- this.ajax(this.buildURL(root), "GET", {
238
- data: query,
239
- success: function(json) {
240
- modelArray.load(json[plural]);
241
- this.sideload(store, type, json, plural);
169
+ var ManyArrayState = Ember.State.extend({
170
+ recordWasAdded: function(manager, record) {
171
+ var dirty = manager.dirty, observer;
172
+ dirty.add(record);
173
+
174
+ observer = function() {
175
+ if (!get(record, 'isDirty')) {
176
+ record.removeObserver('isDirty', observer);
177
+ manager.send('childWasSaved', record);
242
178
  }
243
- });
244
- },
179
+ };
245
180
 
246
- // HELPERS
181
+ record.addObserver('isDirty', observer);
182
+ },
247
183
 
248
- plurals: {},
184
+ recordWasRemoved: function(manager, record) {
185
+ var dirty = manager.dirty, observer;
186
+ dirty.add(record);
249
187
 
250
- // define a plurals hash in your subclass to define
251
- // special-case pluralization
252
- pluralize: function(name) {
253
- return this.plurals[name] || name + "s";
254
- },
188
+ observer = function() {
189
+ record.removeObserver('isDirty', observer);
190
+ if (!get(record, 'isDirty')) { manager.send('childWasSaved', record); }
191
+ };
255
192
 
256
- rootForType: function(type) {
257
- if (type.url) { return type.url; }
193
+ record.addObserver('isDirty', observer);
194
+ }
195
+ });
258
196
 
259
- // use the last part of the name as the URL
260
- var parts = type.toString().split(".");
261
- var name = parts[parts.length - 1];
262
- return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
263
- },
197
+ var states = {
198
+ clean: ManyArrayState.create({
199
+ isDirty: false,
264
200
 
265
- ajax: function(url, type, hash) {
266
- hash.url = url;
267
- hash.type = type;
268
- hash.dataType = 'json';
269
- hash.contentType = 'application/json';
270
- hash.context = this;
201
+ recordWasAdded: function(manager, record) {
202
+ this._super(manager, record);
203
+ manager.goToState('dirty');
204
+ },
271
205
 
272
- if (hash.data && type !== 'GET') {
273
- hash.data = JSON.stringify(hash.data);
206
+ update: function(manager, clientIds) {
207
+ var manyArray = manager.manyArray;
208
+ set(manyArray, 'content', clientIds);
274
209
  }
210
+ }),
275
211
 
276
- jQuery.ajax(hash);
277
- },
212
+ dirty: ManyArrayState.create({
213
+ isDirty: true,
278
214
 
279
- sideload: function(store, type, json, root) {
280
- var sideloadedType, mappings;
215
+ childWasSaved: function(manager, child) {
216
+ var dirty = manager.dirty;
217
+ dirty.remove(child);
281
218
 
282
- for (var prop in json) {
283
- if (!json.hasOwnProperty(prop)) { continue; }
284
- if (prop === root) { continue; }
219
+ if (dirty.isEmpty()) { manager.send('arrayBecameSaved'); }
220
+ },
285
221
 
286
- sideloadedType = type.typeForAssociation(prop);
222
+ arrayBecameSaved: function(manager) {
223
+ manager.goToState('clean');
224
+ }
225
+ })
226
+ };
287
227
 
288
- if (!sideloadedType) {
289
- mappings = get(this, 'mappings');
228
+ DS.ManyArrayStateManager = Ember.StateManager.extend({
229
+ manyArray: null,
230
+ initialState: 'clean',
231
+ states: states,
290
232
 
291
- ember_assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
233
+ init: function() {
234
+ this._super();
235
+ this.dirty = new Set();
236
+ }
237
+ });
292
238
 
293
- sideloadedType = get(get(this, 'mappings'), prop);
239
+ })();
294
240
 
295
- ember_assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
296
- }
297
241
 
298
- this.loadValue(store, sideloadedType, json[prop]);
299
- }
300
- },
301
-
302
- loadValue: function(store, type, value) {
303
- if (value instanceof Array) {
304
- store.loadMany(type, value);
305
- } else {
306
- store.load(type, value);
307
- }
308
- },
309
-
310
- buildURL: function(model, suffix) {
311
- var url = [""];
312
-
313
- if (this.namespace !== undefined) {
314
- url.push(this.namespace);
315
- }
316
242
 
317
- url.push(this.pluralize(model));
318
- if (suffix !== undefined) {
319
- url.push(suffix);
320
- }
243
+ (function() {
244
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
321
245
 
322
- return url.join("/");
323
- }
324
- });
246
+ DS.ManyArray = DS.RecordArray.extend({
247
+ init: function() {
248
+ set(this, 'stateManager', DS.ManyArrayStateManager.create({ manyArray: this }));
325
249
 
250
+ return this._super();
251
+ },
326
252
 
327
- })({});
253
+ parentRecord: null,
328
254
 
255
+ isDirty: Ember.computed(function() {
256
+ return getPath(this, 'stateManager.currentState.isDirty');
257
+ }).property('stateManager.currentState').cacheable(),
329
258
 
330
- (function(exports) {
331
- var get = Ember.get, set = Ember.set;
259
+ fetch: function() {
260
+ var clientIds = get(this, 'content'),
261
+ store = get(this, 'store'),
262
+ type = get(this, 'type');
332
263
 
333
- /**
334
- A model array is an array that contains records of a certain type. The model
335
- array materializes records as needed when they are retrieved for the first
336
- time. You should not create model arrays yourself. Instead, an instance of
337
- DS.ModelArray or its subclasses will be returned by your application's store
338
- in response to queries.
339
- */
264
+ var ids = clientIds.map(function(clientId) {
265
+ return store.clientIdToId[clientId];
266
+ });
340
267
 
341
- DS.ModelArray = Ember.ArrayProxy.extend({
268
+ store.fetchMany(type, ids);
269
+ },
342
270
 
343
- /**
344
- The model type contained by this model array.
271
+ // Overrides Ember.Array's replace method to implement
272
+ replace: function(index, removed, added) {
273
+ var parentRecord = get(this, 'parentRecord');
274
+ var pendingParent = parentRecord && !get(parentRecord, 'id');
275
+ var stateManager = get(this, 'stateManager');
345
276
 
346
- @type DS.Model
347
- */
348
- type: null,
277
+ added = added.map(function(record) {
278
+ ember_assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this association.", !get(this, 'type') || (get(this, 'type') === record.constructor));
349
279
 
350
- // The array of client ids backing the model array. When a
351
- // record is requested from the model array, the record
352
- // for the client id at the same index is materialized, if
353
- // necessary, by the store.
354
- content: null,
280
+ if (pendingParent) {
281
+ record.send('waitingOn', parentRecord);
282
+ }
355
283
 
356
- // The store that created this model array.
357
- store: null,
284
+ this.assignInverse(record, parentRecord);
358
285
 
359
- init: function() {
360
- set(this, 'modelCache', Ember.A([]));
361
- this._super();
362
- },
286
+ stateManager.send('recordWasAdded', record);
363
287
 
364
- arrayDidChange: function(array, index, removed, added) {
365
- var modelCache = get(this, 'modelCache');
366
- modelCache.replace(index, 0, new Array(added));
288
+ return record.get('clientId');
289
+ }, this);
367
290
 
368
- this._super(array, index, removed, added);
369
- },
291
+ var store = this.store;
370
292
 
371
- arrayWillChange: function(array, index, removed, added) {
372
- this._super(array, index, removed, added);
293
+ var len = index+removed, record;
294
+ for (var i = index; i < len; i++) {
295
+ // TODO: null out inverse FK
296
+ record = this.objectAt(i);
297
+ this.assignInverse(record, parentRecord, true);
298
+ stateManager.send('recordWasAdded', record);
299
+ }
373
300
 
374
- var modelCache = get(this, 'modelCache');
375
- modelCache.replace(index, removed);
301
+ this._super(index, removed, added);
376
302
  },
377
303
 
378
- objectAtContent: function(index) {
379
- var modelCache = get(this, 'modelCache');
380
- var model = modelCache.objectAt(index);
304
+ assignInverse: function(record, parentRecord, remove) {
305
+ var associationMap = get(record.constructor, 'associations'),
306
+ possibleAssociations = associationMap.get(parentRecord.constructor),
307
+ possible, actual;
381
308
 
382
- if (!model) {
383
- var store = get(this, 'store');
384
- var content = get(this, 'content');
309
+ if (!possibleAssociations) { return; }
385
310
 
386
- var contentObject = content.objectAt(index);
311
+ for (var i = 0, l = possibleAssociations.length; i < l; i++) {
312
+ possible = possibleAssociations[i];
387
313
 
388
- if (contentObject !== undefined) {
389
- model = store.findByClientId(get(this, 'type'), contentObject);
390
- modelCache.replace(index, 1, [model]);
314
+ if (possible.kind === 'belongsTo') {
315
+ actual = possible;
316
+ break;
391
317
  }
392
318
  }
393
319
 
394
- return model;
320
+ if (actual) {
321
+ set(record, actual.name, remove ? null : parentRecord);
322
+ }
395
323
  }
396
324
  });
397
325
 
398
- })({});
326
+ })();
399
327
 
400
328
 
401
- (function(exports) {
402
- var get = Ember.get;
403
-
404
- DS.FilteredModelArray = DS.ModelArray.extend({
405
- filterFunction: null,
406
-
407
- replace: function() {
408
- var type = get(this, 'type').toString();
409
- throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
410
- },
411
329
 
412
- updateFilter: Ember.observer(function() {
413
- var store = get(this, 'store');
414
- store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
415
- }, 'filterFunction')
416
- });
330
+ (function() {
417
331
 
418
- })({});
332
+ })();
419
333
 
420
334
 
421
- (function(exports) {
422
- var get = Ember.get, set = Ember.set;
423
-
424
- DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
425
- query: null,
426
- isLoaded: false,
427
-
428
- replace: function() {
429
- var type = get(this, 'type').toString();
430
- throw new Error("The result of a server query (on " + type + ") is immutable.");
431
- },
432
335
 
433
- load: function(array) {
434
- var store = get(this, 'store'), type = get(this, 'type');
336
+ (function() {
337
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
435
338
 
436
- var clientIds = store.loadMany(type, array).clientIds;
339
+ /**
340
+ A transaction allows you to collect multiple records into a unit of work
341
+ that can be committed or rolled back as a group.
437
342
 
438
- this.beginPropertyChanges();
439
- set(this, 'content', Ember.A(clientIds));
440
- set(this, 'isLoaded', true);
441
- this.endPropertyChanges();
442
- }
443
- });
343
+ For example, if a record has local modifications that have not yet
344
+ been saved, calling `commit()` on its transaction will cause those
345
+ modifications to be sent to the adapter to be saved. Calling
346
+ `rollback()` on its transaction would cause all of the modifications to
347
+ be discarded and the record to return to the last known state before
348
+ changes were made.
444
349
 
350
+ If a newly created record's transaction is rolled back, it will
351
+ immediately transition to the deleted state.
445
352
 
446
- })({});
353
+ If you do not explicitly create a transaction, a record is assigned to
354
+ an implicit transaction called the default transaction. In these cases,
355
+ you can treat your application's instance of `DS.Store` as a transaction
356
+ and call the `commit()` and `rollback()` methods on the store itself.
447
357
 
358
+ Once a record has been successfully committed or rolled back, it will
359
+ be moved back to the implicit transaction. Because it will now be in
360
+ a clean state, it can be moved to a new transaction if you wish.
448
361
 
449
- (function(exports) {
450
- var get = Ember.get, set = Ember.set;
362
+ ### Creating a Transaction
451
363
 
452
- DS.ManyArray = DS.ModelArray.extend({
453
- parentRecord: null,
364
+ To create a new transaction, call the `transaction()` method of your
365
+ application's `DS.Store` instance:
454
366
 
455
- // Overrides Ember.Array's replace method to implement
456
- replace: function(index, removed, added) {
457
- var parentRecord = get(this, 'parentRecord');
458
- var pendingParent = parentRecord && !get(parentRecord, 'id');
367
+ var transaction = App.store.transaction();
459
368
 
460
- added = added.map(function(record) {
461
- ember_assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this association.", !get(this, 'type') || (get(this, 'type') === record.constructor));
369
+ This will return a new instance of `DS.Transaction` with no records
370
+ yet assigned to it.
462
371
 
463
- if (pendingParent) {
464
- record.send('waitingOn', parentRecord);
465
- }
372
+ ### Adding Existing Records
466
373
 
467
- this.assignInverse(record, parentRecord);
374
+ Add records to a transaction using the `add()` method:
468
375
 
469
- return record.get('clientId');
470
- }, this);
376
+ record = App.store.find(Person, 1);
377
+ transaction.add(record);
471
378
 
472
- this._super(index, removed, added);
473
- },
379
+ Note that only records whose `isDirty` flag is `false` may be added
380
+ to a transaction. Once modifications to a record have been made
381
+ (its `isDirty` flag is `true`), it is not longer able to be added to
382
+ a transaction.
474
383
 
475
- assignInverse: function(record, parentRecord) {
476
- var associationMap = get(record.constructor, 'associations'),
477
- possibleAssociations = associationMap.get(record.constructor),
478
- possible, actual;
384
+ ### Creating New Records
479
385
 
480
- if (!possibleAssociations) { return; }
386
+ Because newly created records are dirty from the time they are created,
387
+ and because dirty records can not be added to a transaction, you must
388
+ use the `createRecord()` method to assign new records to a transaction.
481
389
 
482
- for (var i = 0, l = possibleAssociations.length; i < l; i++) {
483
- possible = possibleAssociations[i];
390
+ For example, instead of this:
484
391
 
485
- if (possible.kind === 'belongsTo') {
486
- actual = possible;
487
- break;
488
- }
489
- }
392
+ var transaction = store.transaction();
393
+ var person = Person.createRecord({ name: "Steve" });
490
394
 
491
- if (actual) {
492
- set(record, actual.name, parentRecord);
493
- }
494
- }
495
- });
395
+ // won't work because person is dirty
396
+ transaction.add(person);
496
397
 
497
- })({});
398
+ Call `createRecord()` on the transaction directly:
498
399
 
400
+ var transaction = store.transaction();
401
+ transaction.createRecord(Person, { name: "Steve" });
499
402
 
500
- (function(exports) {
501
- })({});
403
+ ### Asynchronous Commits
502
404
 
405
+ Typically, all of the records in a transaction will be committed
406
+ together. However, new records that have a dependency on other new
407
+ records need to wait for their parent record to be saved and assigned an
408
+ ID. In that case, the child record will continue to live in the
409
+ transaction until its parent is saved, at which time the transaction will
410
+ attempt to commit again.
503
411
 
504
- (function(exports) {
505
- var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
412
+ For this reason, you should not re-use transactions once you have committed
413
+ them. Always make a new transaction and move the desired records to it before
414
+ calling commit.
415
+ */
506
416
 
507
417
  DS.Transaction = Ember.Object.extend({
418
+ /**
419
+ @private
420
+
421
+ Creates the bucket data structure used to segregate records by
422
+ type.
423
+ */
508
424
  init: function() {
509
425
  set(this, 'buckets', {
510
426
  clean: Ember.Map.create(),
@@ -514,30 +430,182 @@ DS.Transaction = Ember.Object.extend({
514
430
  });
515
431
  },
516
432
 
433
+ /**
434
+ Creates a new record of the given type and assigns it to the transaction
435
+ on which the method was called.
436
+
437
+ This is useful as only clean records can be added to a transaction and
438
+ new records created using other methods immediately become dirty.
439
+
440
+ @param {DS.Model} type the model type to create
441
+ @param {Object} hash the data hash to assign the new record
442
+ */
517
443
  createRecord: function(type, hash) {
518
444
  var store = get(this, 'store');
519
445
 
520
446
  return store.createRecord(type, hash, this);
521
447
  },
522
448
 
449
+ /**
450
+ Adds an existing record to this transaction. Only records without
451
+ modficiations (i.e., records whose `isDirty` property is `false`)
452
+ can be added to a transaction.
453
+
454
+ @param {DS.Model} record the record to add to the transaction
455
+ */
523
456
  add: function(record) {
524
457
  // we could probably make this work if someone has a valid use case. Do you?
525
458
  ember_assert("Once a record has changed, you cannot move it into a different transaction", !get(record, 'isDirty'));
526
459
 
527
- var modelTransaction = get(record, 'transaction'),
460
+ var recordTransaction = get(record, 'transaction'),
528
461
  defaultTransaction = getPath(this, 'store.defaultTransaction');
529
462
 
530
- ember_assert("Models cannot belong to more than one transaction at a time.", modelTransaction === defaultTransaction);
463
+ ember_assert("Models cannot belong to more than one transaction at a time.", recordTransaction === defaultTransaction);
531
464
 
532
465
  this.adoptRecord(record);
533
466
  },
534
467
 
468
+ /**
469
+ Commits the transaction, which causes all of the modified records that
470
+ belong to the transaction to be sent to the adapter to be saved.
471
+
472
+ Once you call `commit()` on a transaction, you should not re-use it.
473
+
474
+ When a record is saved, it will be removed from this transaction and
475
+ moved back to the store's default transaction.
476
+ */
477
+ commit: function() {
478
+ var self = this,
479
+ iterate;
480
+
481
+ iterate = function(bucketType, fn, binding) {
482
+ var dirty = self.bucketForType(bucketType);
483
+
484
+ dirty.forEach(function(type, records) {
485
+ if (records.isEmpty()) { return; }
486
+
487
+ var array = [];
488
+
489
+ records.forEach(function(record) {
490
+ record.send('willCommit');
491
+
492
+ if (get(record, 'isPending') === false) {
493
+ array.push(record);
494
+ }
495
+ });
496
+
497
+ fn.call(binding, type, array);
498
+ });
499
+ };
500
+
501
+ var commitDetails = {
502
+ updated: {
503
+ eachType: function(fn, binding) { iterate('updated', fn, binding); }
504
+ },
505
+
506
+ created: {
507
+ eachType: function(fn, binding) { iterate('created', fn, binding); }
508
+ },
509
+
510
+ deleted: {
511
+ eachType: function(fn, binding) { iterate('deleted', fn, binding); }
512
+ }
513
+ };
514
+
515
+ var store = get(this, 'store');
516
+ var adapter = get(store, '_adapter');
517
+
518
+ this.removeCleanRecords();
519
+
520
+ if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
521
+ else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
522
+ },
523
+
524
+ /**
525
+ Rolling back a transaction resets the records that belong to
526
+ that transaction.
527
+
528
+ Updated records have their properties reset to the last known
529
+ value from the persistence layer. Deleted records are reverted
530
+ to a clean, non-deleted state. Newly created records immediately
531
+ become deleted, and are not sent to the adapter to be persisted.
532
+
533
+ After the transaction is rolled back, any records that belong
534
+ to it will return to the store's default transaction, and the
535
+ current transaction should not be used again.
536
+ */
537
+ rollback: function() {
538
+ var store = get(this, 'store'),
539
+ dirty;
540
+
541
+ // Loop through all of the records in each of the dirty states
542
+ // and initiate a rollback on them. As a side effect of telling
543
+ // the record to roll back, it should also move itself out of
544
+ // the dirty bucket and into the clean bucket.
545
+ ['created', 'updated', 'deleted'].forEach(function(bucketType) {
546
+ dirty = this.bucketForType(bucketType);
547
+
548
+ dirty.forEach(function(type, records) {
549
+ records.forEach(function(record) {
550
+ record.send('rollback');
551
+ });
552
+ });
553
+ }, this);
554
+
555
+ // Now that all records in the transaction are guaranteed to be
556
+ // clean, migrate them all to the store's default transaction.
557
+ this.removeCleanRecords();
558
+ },
559
+
560
+ /**
561
+ @private
562
+
563
+ Removes a record from this transaction and back to the store's
564
+ default transaction.
565
+
566
+ Note: This method is private for now, but should probably be exposed
567
+ in the future once we have stricter error checking (for example, in the
568
+ case of the record being dirty).
569
+
570
+ @param {DS.Model} record
571
+ */
535
572
  remove: function(record) {
536
573
  var defaultTransaction = getPath(this, 'store.defaultTransaction');
537
-
538
574
  defaultTransaction.adoptRecord(record);
539
575
  },
540
576
 
577
+ /**
578
+ @private
579
+
580
+ Removes all of the records in the transaction's clean bucket.
581
+ */
582
+ removeCleanRecords: function() {
583
+ var clean = this.bucketForType('clean'),
584
+ self = this;
585
+
586
+ clean.forEach(function(type, records) {
587
+ records.forEach(function(record) {
588
+ self.remove(record);
589
+ });
590
+ });
591
+ },
592
+
593
+ /**
594
+ @private
595
+
596
+ Returns the bucket for the given bucket type. For example, you might call
597
+ `this.bucketForType('updated')` to get the `Ember.Map` that contains all
598
+ of the records that have changes pending.
599
+
600
+ @param {String} bucketType the type of bucket
601
+ @returns Ember.Map
602
+ */
603
+ bucketForType: function(bucketType) {
604
+ var buckets = get(this, 'buckets');
605
+
606
+ return get(buckets, bucketType);
607
+ },
608
+
541
609
  /**
542
610
  @private
543
611
 
@@ -549,6 +617,8 @@ DS.Transaction = Ember.Object.extend({
549
617
  into a new transaction when the transaction is committed.
550
618
 
551
619
  This method must not be called unless the record is clean.
620
+
621
+ @param {DS.Model} record
552
622
  */
553
623
  adoptRecord: function(record) {
554
624
  var oldTransaction = get(record, 'transaction');
@@ -561,14 +631,15 @@ DS.Transaction = Ember.Object.extend({
561
631
  set(record, 'transaction', this);
562
632
  },
563
633
 
564
- modelBecameDirty: function(kind, record) {
565
- this.removeFromBucket('clean', record);
566
- this.addToBucket(kind, record);
567
- },
634
+ /**
635
+ @private
568
636
 
569
- /** @private */
570
- addToBucket: function(kind, record) {
571
- var bucket = get(get(this, 'buckets'), kind),
637
+ Adds a record to the named bucket.
638
+
639
+ @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
640
+ */
641
+ addToBucket: function(bucketType, record) {
642
+ var bucket = this.bucketForType(bucketType),
572
643
  type = record.constructor;
573
644
 
574
645
  var records = bucket.get(type);
@@ -581,80 +652,58 @@ DS.Transaction = Ember.Object.extend({
581
652
  records.add(record);
582
653
  },
583
654
 
584
- /** @private */
585
- removeFromBucket: function(kind, record) {
586
- var bucket = get(get(this, 'buckets'), kind),
655
+ /**
656
+ @private
657
+
658
+ Removes a record from the named bucket.
659
+
660
+ @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
661
+ */
662
+ removeFromBucket: function(bucketType, record) {
663
+ var bucket = this.bucketForType(bucketType),
587
664
  type = record.constructor;
588
665
 
589
666
  var records = bucket.get(type);
590
667
  records.remove(record);
591
668
  },
592
669
 
593
- modelBecameClean: function(kind, record) {
594
- this.removeFromBucket(kind, record);
670
+ /**
671
+ @private
595
672
 
596
- var defaultTransaction = getPath(this, 'store.defaultTransaction');
597
- defaultTransaction.adoptRecord(record);
598
- },
673
+ Called by a record's state manager to indicate that the record has entered
674
+ a dirty state. The record will be moved from the `clean` bucket and into
675
+ the appropriate dirty bucket.
599
676
 
600
- commit: function() {
601
- var buckets = get(this, 'buckets');
602
-
603
- var iterate = function(kind, fn, binding) {
604
- var dirty = get(buckets, kind);
605
-
606
- dirty.forEach(function(type, models) {
607
- if (models.isEmpty()) { return; }
608
-
609
- var array = [];
610
-
611
- models.forEach(function(model) {
612
- model.send('willCommit');
613
-
614
- if (get(model, 'isPending') === false) {
615
- array.push(model);
616
- }
617
- });
618
-
619
- fn.call(binding, type, array);
620
- });
621
- };
622
-
623
- var commitDetails = {
624
- updated: {
625
- eachType: function(fn, binding) { iterate('updated', fn, binding); }
626
- },
627
-
628
- created: {
629
- eachType: function(fn, binding) { iterate('created', fn, binding); }
630
- },
631
-
632
- deleted: {
633
- eachType: function(fn, binding) { iterate('deleted', fn, binding); }
634
- }
635
- };
677
+ @param {String} bucketType one of `created`, `updated`, or `deleted`
678
+ */
679
+ recordBecameDirty: function(bucketType, record) {
680
+ this.removeFromBucket('clean', record);
681
+ this.addToBucket(bucketType, record);
682
+ },
636
683
 
637
- var store = get(this, 'store');
638
- var adapter = get(store, '_adapter');
684
+ /**
685
+ @private
639
686
 
640
- var clean = get(buckets, 'clean');
641
- var defaultTransaction = get(store, 'defaultTransaction');
687
+ Called by a record's state manager to indicate that the record has entered
688
+ a clean state. The record will be moved from its current dirty bucket and into
689
+ the `clean` bucket.
642
690
 
643
- clean.forEach(function(type, records) {
644
- records.forEach(function(record) {
645
- this.remove(record);
646
- }, this);
647
- }, this);
691
+ @param {String} bucketType one of `created`, `updated`, or `deleted`
692
+ */
693
+ recordBecameClean: function(kind, record) {
694
+ this.removeFromBucket(kind, record);
648
695
 
649
- if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
650
- else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
696
+ var defaultTransaction = getPath(this, 'store.defaultTransaction');
697
+ defaultTransaction.adoptRecord(record);
651
698
  }
652
699
  });
653
700
 
654
- })({});
701
+ })();
655
702
 
656
703
 
657
- (function(exports) {
704
+
705
+ (function() {
706
+ /*globals Ember*/
658
707
  var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
659
708
 
660
709
  var DATA_PROXY = {
@@ -663,6 +712,11 @@ var DATA_PROXY = {
663
712
  }
664
713
  };
665
714
 
715
+ // These values are used in the data cache when clientIds are
716
+ // needed but the underlying data has not yet been loaded by
717
+ // the server.
718
+ var UNLOADED = 'unloaded';
719
+ var LOADING = 'loading';
666
720
 
667
721
  // Implementors Note:
668
722
  //
@@ -677,7 +731,7 @@ var DATA_PROXY = {
677
731
  // * +type+ means a subclass of DS.Model.
678
732
 
679
733
  /**
680
- The store contains all of the hashes for data models loaded from the server.
734
+ The store contains all of the hashes for records loaded from the server.
681
735
  It is also responsible for creating instances of DS.Model when you request one
682
736
  of these data hashes, so that they can be bound to in your Handlebars templates.
683
737
 
@@ -686,9 +740,9 @@ var DATA_PROXY = {
686
740
  MyApp.store = DS.Store.create();
687
741
 
688
742
  You can retrieve DS.Model instances from the store in several ways. To retrieve
689
- a model for a specific id, use the `find()` method:
743
+ a record for a specific id, use the `find()` method:
690
744
 
691
- var model = MyApp.store.find(MyApp.Contact, 123);
745
+ var record = MyApp.store.find(MyApp.Contact, 123);
692
746
 
693
747
  By default, the store will talk to your backend using a standard REST mechanism.
694
748
  You can customize how the store talks to your backend by specifying a custom adapter:
@@ -726,7 +780,7 @@ DS.Store = Ember.Object.extend({
726
780
  this.typeMaps = {};
727
781
  this.recordCache = [];
728
782
  this.clientIdToId = {};
729
- this.modelArraysByClientId = {};
783
+ this.recordArraysByClientId = {};
730
784
 
731
785
  set(this, 'defaultTransaction', this.transaction());
732
786
 
@@ -746,7 +800,7 @@ DS.Store = Ember.Object.extend({
746
800
  /**
747
801
  @private
748
802
 
749
- This is used only by the model's DataProxy. Do not use this directly.
803
+ This is used only by the record's DataProxy. Do not use this directly.
750
804
  */
751
805
  dataForRecord: function(record) {
752
806
  var type = record.constructor,
@@ -783,11 +837,11 @@ DS.Store = Ember.Object.extend({
783
837
 
784
838
  // A monotonically increasing number to be used to uniquely identify
785
839
  // data hashes and records.
786
- clientIdCounter: -1,
840
+ clientIdCounter: 1,
787
841
 
788
- // ....................
789
- // . CREATE NEW MODEL .
790
- // ....................
842
+ // .....................
843
+ // . CREATE NEW RECORD .
844
+ // .....................
791
845
 
792
846
  /**
793
847
  Create a new record in the current store. The properties passed
@@ -839,14 +893,14 @@ DS.Store = Ember.Object.extend({
839
893
  // Set the properties specified on the record.
840
894
  record.setProperties(properties);
841
895
 
842
- this.updateModelArrays(type, clientId, get(record, 'data'));
896
+ this.updateRecordArrays(type, clientId, get(record, 'data'));
843
897
 
844
898
  return record;
845
899
  },
846
900
 
847
- // ................
848
- // . DELETE MODEL .
849
- // ................
901
+ // .................
902
+ // . DELETE RECORD .
903
+ // .................
850
904
 
851
905
  /**
852
906
  For symmetry, a record can be deleted via the store.
@@ -857,9 +911,9 @@ DS.Store = Ember.Object.extend({
857
911
  record.send('deleteRecord');
858
912
  },
859
913
 
860
- // ...............
861
- // . FIND MODELS .
862
- // ...............
914
+ // ................
915
+ // . FIND RECORDS .
916
+ // ................
863
917
 
864
918
  /**
865
919
  This is the main entry point into finding records. The first
@@ -879,7 +933,8 @@ DS.Store = Ember.Object.extend({
879
933
 
880
934
  If the record with that `id` had not previously been loaded,
881
935
  the store will return an empty record immediately and ask
882
- the adapter to find the data by calling its `find` method.
936
+ the adapter to find the data by calling the adapter's `find`
937
+ method.
883
938
 
884
939
  The `find` method will always return the same object for a
885
940
  given type and `id`. To check whether the adapter has populated
@@ -893,11 +948,11 @@ DS.Store = Ember.Object.extend({
893
948
  store.find(App.Person);
894
949
  App.Person.find();
895
950
 
896
- This will return a `ModelArray` representing all known records
951
+ This will return a `RecordArray` representing all known records
897
952
  for the given type and kick off a request to the adapter's
898
953
  `findAll` method to load any additional records for the type.
899
954
 
900
- The `ModelArray` returned by `find()` is live. If any more
955
+ The `RecordArray` returned by `find()` is live. If any more
901
956
  records for the type are added at a later time through any
902
957
  mechanism, it will automatically update to reflect the change.
903
958
 
@@ -909,12 +964,12 @@ DS.Store = Ember.Object.extend({
909
964
  store.find(App.Person, { page: 1 });
910
965
  App.Person.find({ page: 1 });
911
966
 
912
- This will return a `ModelArray` immediately, but it will always
913
- be an empty `ModelArray` at first. It will call the adapter's
914
- `findQuery` method, which will populate the `ModelArray` once
967
+ This will return a `RecordArray` immediately, but it will always
968
+ be an empty `RecordArray` at first. It will call the adapter's
969
+ `findQuery` method, which will populate the `RecordArray` once
915
970
  the server has returned results.
916
971
 
917
- You can check whether a query results `ModelArray` has loaded
972
+ You can check whether a query results `RecordArray` has loaded
918
973
  by checking its `isLoaded` property.
919
974
  */
920
975
  find: function(type, id, query) {
@@ -932,7 +987,7 @@ DS.Store = Ember.Object.extend({
932
987
  return this.findMany(type, id);
933
988
  }
934
989
 
935
- var clientId = this.clientIdForId(type, id);
990
+ var clientId = this.typeMapFor(type).idToCid[id];
936
991
 
937
992
  return this.findByClientId(type, clientId, id);
938
993
  },
@@ -940,31 +995,31 @@ DS.Store = Ember.Object.extend({
940
995
  findByClientId: function(type, clientId, id) {
941
996
  var recordCache = get(this, 'recordCache'),
942
997
  dataCache = this.typeMapFor(type).cidToHash,
943
- model;
998
+ record;
944
999
 
945
1000
  // If there is already a clientId assigned for this
946
1001
  // type/id combination, try to find an existing
947
- // model for that id and return. Otherwise,
948
- // materialize a new model and set its data to the
1002
+ // record for that id and return. Otherwise,
1003
+ // materialize a new record and set its data to the
949
1004
  // value we already have.
950
1005
  if (clientId !== undefined) {
951
- model = recordCache[clientId];
1006
+ record = recordCache[clientId];
952
1007
 
953
- if (!model) {
954
- // create a new instance of the model in the
1008
+ if (!record) {
1009
+ // create a new instance of the model type in the
955
1010
  // 'isLoading' state
956
- model = this.materializeRecord(type, clientId);
1011
+ record = this.materializeRecord(type, clientId);
957
1012
 
958
- if (dataCache[clientId]) {
959
- model.send('didChangeData');
1013
+ if (typeof dataCache[clientId] === 'object') {
1014
+ record.send('didChangeData');
960
1015
  }
961
1016
  }
962
1017
  } else {
963
- clientId = this.pushHash(null, id, type);
1018
+ clientId = this.pushHash(LOADING, id, type);
964
1019
 
965
- // create a new instance of the model in the
1020
+ // create a new instance of the model type in the
966
1021
  // 'isLoading' state
967
- model = this.materializeRecord(type, clientId);
1022
+ record = this.materializeRecord(type, clientId);
968
1023
 
969
1024
  // let the adapter set the data, possibly async
970
1025
  var adapter = get(this, '_adapter');
@@ -972,14 +1027,29 @@ DS.Store = Ember.Object.extend({
972
1027
  else { throw fmt("Adapter is either null or does not implement `find` method", this); }
973
1028
  }
974
1029
 
975
- return model;
1030
+ return record;
976
1031
  },
977
1032
 
978
- /** @private
1033
+ /**
1034
+ @private
1035
+
1036
+ Ask the adapter to fetch IDs that are not already loaded.
1037
+
1038
+ This method will convert `id`s to `clientId`s, filter out
1039
+ `clientId`s that already have a data hash present, and pass
1040
+ the remaining `id`s to the adapter.
1041
+
1042
+ @param {Class} type A model class
1043
+ @param {Array} ids An array of ids
1044
+ @param {Object} query
1045
+
1046
+ @returns {Array} An Array of all clientIds for the
1047
+ specified ids.
979
1048
  */
980
- findMany: function(type, ids, query) {
1049
+ fetchMany: function(type, ids, query) {
981
1050
  var typeMap = this.typeMapFor(type),
982
1051
  idToClientIdMap = typeMap.idToCid,
1052
+ dataCache = typeMap.cidToHash,
983
1053
  data = typeMap.cidToHash,
984
1054
  needed;
985
1055
 
@@ -989,29 +1059,56 @@ DS.Store = Ember.Object.extend({
989
1059
  needed = [];
990
1060
 
991
1061
  ids.forEach(function(id) {
1062
+ // Get the clientId for the given id
992
1063
  var clientId = idToClientIdMap[id];
993
- if (clientId === undefined || data[clientId] === undefined) {
994
- clientId = this.pushHash(null, id, type);
1064
+
1065
+ // If there is no `clientId` yet
1066
+ if (clientId === undefined) {
1067
+ // Create a new `clientId`, marking its data hash
1068
+ // as loading. Once the adapter returns the data
1069
+ // hash, it will be updated
1070
+ clientId = this.pushHash(LOADING, id, type);
1071
+ needed.push(id);
1072
+
1073
+ // If there is a clientId, but its data hash is
1074
+ // marked as unloaded (this happens when a
1075
+ // hasMany association creates clientIds for its
1076
+ // referenced ids before they were loaded)
1077
+ } else if (clientId && data[clientId] === UNLOADED) {
1078
+ // change the data hash marker to loading
1079
+ dataCache[clientId] = LOADING;
995
1080
  needed.push(id);
996
1081
  }
997
1082
 
1083
+ // this method is expected to return a list of
1084
+ // all of the clientIds for the specified ids,
1085
+ // unconditionally add it.
998
1086
  clientIds.push(clientId);
999
1087
  }, this);
1000
1088
  } else {
1001
1089
  needed = null;
1002
1090
  }
1003
1091
 
1092
+ // If there are any needed ids, ask the adapter to load them
1004
1093
  if ((needed && get(needed, 'length') > 0) || query) {
1005
1094
  var adapter = get(this, '_adapter');
1006
1095
  if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
1007
1096
  else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
1008
1097
  }
1009
1098
 
1099
+ return clientIds;
1100
+ },
1101
+
1102
+ /** @private
1103
+ */
1104
+ findMany: function(type, ids, query) {
1105
+ var clientIds = this.fetchMany(type, ids, query);
1106
+
1010
1107
  return this.createManyArray(type, clientIds);
1011
1108
  },
1012
1109
 
1013
1110
  findQuery: function(type, query) {
1014
- var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
1111
+ var array = DS.AdapterPopulatedRecordArray.create({ type: type, content: Ember.A([]), store: this });
1015
1112
  var adapter = get(this, '_adapter');
1016
1113
  if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
1017
1114
  else { throw fmt("Adapter is either null or does not implement `findQuery` method", this); }
@@ -1025,8 +1122,8 @@ DS.Store = Ember.Object.extend({
1025
1122
 
1026
1123
  if (findAllCache) { return findAllCache; }
1027
1124
 
1028
- var array = DS.ModelArray.create({ type: type, content: Ember.A([]), store: this });
1029
- this.registerModelArray(array, type);
1125
+ var array = DS.RecordArray.create({ type: type, content: Ember.A([]), store: this });
1126
+ this.registerRecordArray(array, type);
1030
1127
 
1031
1128
  var adapter = get(this, '_adapter');
1032
1129
  if (adapter && adapter.findAll) { adapter.findAll(this, type); }
@@ -1043,9 +1140,9 @@ DS.Store = Ember.Object.extend({
1043
1140
  filter = query;
1044
1141
  }
1045
1142
 
1046
- var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
1143
+ var array = DS.FilteredRecordArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
1047
1144
 
1048
- this.registerModelArray(array, type, filter);
1145
+ this.registerRecordArray(array, type, filter);
1049
1146
 
1050
1147
  return array;
1051
1148
  },
@@ -1055,7 +1152,17 @@ DS.Store = Ember.Object.extend({
1055
1152
  // ............
1056
1153
 
1057
1154
  hashWasUpdated: function(type, clientId, record) {
1058
- this.updateModelArrays(type, clientId, get(record, 'data'));
1155
+ // Because hash updates are invoked at the end of the run loop,
1156
+ // it is possible that a record might be deleted after its hash
1157
+ // has been modified and this method was scheduled to be called.
1158
+ //
1159
+ // If that's the case, the record would have already been removed
1160
+ // from all record arrays; calling updateRecordArrays would just
1161
+ // add it back. If the record is deleted, just bail. It shouldn't
1162
+ // give us any more trouble after this.
1163
+
1164
+ if (get(record, 'isDeleted')) { return; }
1165
+ this.updateRecordArrays(type, clientId, get(record, 'data'));
1059
1166
  },
1060
1167
 
1061
1168
  // ..............
@@ -1071,37 +1178,37 @@ DS.Store = Ember.Object.extend({
1071
1178
 
1072
1179
  didUpdateRecords: function(array, hashes) {
1073
1180
  if (hashes) {
1074
- array.forEach(function(model, idx) {
1075
- this.didUpdateRecord(model, hashes[idx]);
1181
+ array.forEach(function(record, idx) {
1182
+ this.didUpdateRecord(record, hashes[idx]);
1076
1183
  }, this);
1077
1184
  } else {
1078
- array.forEach(function(model) {
1079
- this.didUpdateRecord(model);
1185
+ array.forEach(function(record) {
1186
+ this.didUpdateRecord(record);
1080
1187
  }, this);
1081
1188
  }
1082
1189
  },
1083
1190
 
1084
- didUpdateRecord: function(model, hash) {
1191
+ didUpdateRecord: function(record, hash) {
1085
1192
  if (hash) {
1086
- var clientId = get(model, 'clientId'),
1087
- dataCache = this.typeMapFor(model.constructor).cidToHash;
1193
+ var clientId = get(record, 'clientId'),
1194
+ dataCache = this.typeMapFor(record.constructor).cidToHash;
1088
1195
 
1089
1196
  dataCache[clientId] = hash;
1090
- model.send('didChangeData');
1091
- model.hashWasUpdated();
1197
+ record.send('didChangeData');
1198
+ record.hashWasUpdated();
1092
1199
  }
1093
1200
 
1094
- model.send('didCommit');
1201
+ record.send('didCommit');
1095
1202
  },
1096
1203
 
1097
1204
  didDeleteRecords: function(array) {
1098
- array.forEach(function(model) {
1099
- model.send('didCommit');
1205
+ array.forEach(function(record) {
1206
+ record.send('didCommit');
1100
1207
  });
1101
1208
  },
1102
1209
 
1103
- didDeleteRecord: function(model) {
1104
- model.send('didCommit');
1210
+ didDeleteRecord: function(record) {
1211
+ record.send('didCommit');
1105
1212
  },
1106
1213
 
1107
1214
  _didCreateRecord: function(record, hash, typeMap, clientId, primaryKey) {
@@ -1133,20 +1240,20 @@ DS.Store = Ember.Object.extend({
1133
1240
  didCreateRecords: function(type, array, hashes) {
1134
1241
  var primaryKey = type.proto().primaryKey,
1135
1242
  typeMap = this.typeMapFor(type),
1136
- id, clientId;
1243
+ clientId;
1137
1244
 
1138
1245
  for (var i=0, l=get(array, 'length'); i<l; i++) {
1139
- var model = array[i], hash = hashes[i];
1140
- clientId = get(model, 'clientId');
1246
+ var record = array[i], hash = hashes[i];
1247
+ clientId = get(record, 'clientId');
1141
1248
 
1142
- this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
1249
+ this._didCreateRecord(record, hash, typeMap, clientId, primaryKey);
1143
1250
  }
1144
1251
  },
1145
1252
 
1146
- didCreateRecord: function(model, hash) {
1147
- var type = model.constructor,
1253
+ didCreateRecord: function(record, hash) {
1254
+ var type = record.constructor,
1148
1255
  typeMap = this.typeMapFor(type),
1149
- id, clientId, primaryKey;
1256
+ clientId, primaryKey;
1150
1257
 
1151
1258
  // The hash is optional, but if it is not provided, the client must have
1152
1259
  // provided a primary key.
@@ -1157,42 +1264,42 @@ DS.Store = Ember.Object.extend({
1157
1264
  if (hash) {
1158
1265
  ember_assert("The server must provide a primary key: " + primaryKey, get(hash, primaryKey));
1159
1266
  } else {
1160
- ember_assert("The server did not return data, and you did not create a primary key (" + primaryKey + ") on the client", get(get(model, 'data'), primaryKey));
1267
+ ember_assert("The server did not return data, and you did not create a primary key (" + primaryKey + ") on the client", get(get(record, 'data'), primaryKey));
1161
1268
  }
1162
1269
 
1163
- clientId = get(model, 'clientId');
1270
+ clientId = get(record, 'clientId');
1164
1271
 
1165
- this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
1272
+ this._didCreateRecord(record, hash, typeMap, clientId, primaryKey);
1166
1273
  },
1167
1274
 
1168
1275
  recordWasInvalid: function(record, errors) {
1169
1276
  record.send('becameInvalid', errors);
1170
1277
  },
1171
1278
 
1172
- // ................
1173
- // . MODEL ARRAYS .
1174
- // ................
1279
+ // .................
1280
+ // . RECORD ARRAYS .
1281
+ // .................
1175
1282
 
1176
- registerModelArray: function(array, type, filter) {
1177
- var modelArrays = this.typeMapFor(type).modelArrays;
1283
+ registerRecordArray: function(array, type, filter) {
1284
+ var recordArrays = this.typeMapFor(type).recordArrays;
1178
1285
 
1179
- modelArrays.push(array);
1286
+ recordArrays.push(array);
1180
1287
 
1181
- this.updateModelArrayFilter(array, type, filter);
1288
+ this.updateRecordArrayFilter(array, type, filter);
1182
1289
  },
1183
1290
 
1184
1291
  createManyArray: function(type, clientIds) {
1185
1292
  var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
1186
1293
 
1187
1294
  clientIds.forEach(function(clientId) {
1188
- var modelArrays = this.modelArraysForClientId(clientId);
1189
- modelArrays.add(array);
1295
+ var recordArrays = this.recordArraysForClientId(clientId);
1296
+ recordArrays.add(array);
1190
1297
  }, this);
1191
1298
 
1192
1299
  return array;
1193
1300
  },
1194
1301
 
1195
- updateModelArrayFilter: function(array, type, filter) {
1302
+ updateRecordArrayFilter: function(array, type, filter) {
1196
1303
  var typeMap = this.typeMapFor(type),
1197
1304
  dataCache = typeMap.cidToHash,
1198
1305
  clientIds = typeMap.clientIds,
@@ -1203,7 +1310,8 @@ DS.Store = Ember.Object.extend({
1203
1310
  for (var i=0, l=clientIds.length; i<l; i++) {
1204
1311
  clientId = clientIds[i];
1205
1312
 
1206
- if (hash = dataCache[clientId]) {
1313
+ hash = dataCache[clientId];
1314
+ if (typeof hash === 'object') {
1207
1315
  if (record = recordCache[clientId]) {
1208
1316
  proxy = get(record, 'data');
1209
1317
  } else {
@@ -1211,22 +1319,22 @@ DS.Store = Ember.Object.extend({
1211
1319
  proxy = DATA_PROXY;
1212
1320
  }
1213
1321
 
1214
- this.updateModelArray(array, filter, type, clientId, proxy);
1322
+ this.updateRecordArray(array, filter, type, clientId, proxy);
1215
1323
  }
1216
1324
  }
1217
1325
  },
1218
1326
 
1219
- updateModelArrays: function(type, clientId, dataProxy) {
1220
- var modelArrays = this.typeMapFor(type).modelArrays,
1221
- modelArrayType, filter;
1327
+ updateRecordArrays: function(type, clientId, dataProxy) {
1328
+ var recordArrays = this.typeMapFor(type).recordArrays,
1329
+ filter;
1222
1330
 
1223
- modelArrays.forEach(function(array) {
1331
+ recordArrays.forEach(function(array) {
1224
1332
  filter = get(array, 'filterFunction');
1225
- this.updateModelArray(array, filter, type, clientId, dataProxy);
1333
+ this.updateRecordArray(array, filter, type, clientId, dataProxy);
1226
1334
  }, this);
1227
1335
  },
1228
1336
 
1229
- updateModelArray: function(array, filter, type, clientId, dataProxy) {
1337
+ updateRecordArray: function(array, filter, type, clientId, dataProxy) {
1230
1338
  var shouldBeInArray;
1231
1339
 
1232
1340
  if (!filter) {
@@ -1238,22 +1346,22 @@ DS.Store = Ember.Object.extend({
1238
1346
  var content = get(array, 'content');
1239
1347
  var alreadyInArray = content.indexOf(clientId) !== -1;
1240
1348
 
1241
- var modelArrays = this.modelArraysForClientId(clientId);
1349
+ var recordArrays = this.recordArraysForClientId(clientId);
1242
1350
 
1243
1351
  if (shouldBeInArray && !alreadyInArray) {
1244
- modelArrays.add(array);
1352
+ recordArrays.add(array);
1245
1353
  content.pushObject(clientId);
1246
1354
  } else if (!shouldBeInArray && alreadyInArray) {
1247
- modelArrays.remove(array);
1355
+ recordArrays.remove(array);
1248
1356
  content.removeObject(clientId);
1249
1357
  }
1250
1358
  },
1251
1359
 
1252
- removeFromModelArrays: function(model) {
1253
- var clientId = get(model, 'clientId');
1254
- var modelArrays = this.modelArraysForClientId(clientId);
1360
+ removeFromRecordArrays: function(record) {
1361
+ var clientId = get(record, 'clientId');
1362
+ var recordArrays = this.recordArraysForClientId(clientId);
1255
1363
 
1256
- modelArrays.forEach(function(array) {
1364
+ recordArrays.forEach(function(array) {
1257
1365
  var content = get(array, 'content');
1258
1366
  content.removeObject(clientId);
1259
1367
  });
@@ -1263,12 +1371,12 @@ DS.Store = Ember.Object.extend({
1263
1371
  // . INDEXING .
1264
1372
  // ............
1265
1373
 
1266
- modelArraysForClientId: function(clientId) {
1267
- var modelArrays = get(this, 'modelArraysByClientId');
1268
- var ret = modelArrays[clientId];
1374
+ recordArraysForClientId: function(clientId) {
1375
+ var recordArrays = get(this, 'recordArraysByClientId');
1376
+ var ret = recordArrays[clientId];
1269
1377
 
1270
1378
  if (!ret) {
1271
- ret = modelArrays[clientId] = Ember.OrderedSet.create();
1379
+ ret = recordArrays[clientId] = Ember.OrderedSet.create();
1272
1380
  }
1273
1381
 
1274
1382
  return ret;
@@ -1288,7 +1396,7 @@ DS.Store = Ember.Object.extend({
1288
1396
  idToCid: {},
1289
1397
  clientIds: [],
1290
1398
  cidToHash: {},
1291
- modelArrays: []
1399
+ recordArrays: []
1292
1400
  });
1293
1401
  }
1294
1402
  },
@@ -1296,13 +1404,17 @@ DS.Store = Ember.Object.extend({
1296
1404
  /** @private
1297
1405
 
1298
1406
  For a given type and id combination, returns the client id used by the store.
1299
- If no client id has been assigned yet, `undefined` is returned.
1407
+ If no client id has been assigned yet, one will be created and returned.
1300
1408
 
1301
1409
  @param {DS.Model} type
1302
1410
  @param {String|Number} id
1303
1411
  */
1304
1412
  clientIdForId: function(type, id) {
1305
- return this.typeMapFor(type).idToCid[id];
1413
+ var clientId = this.typeMapFor(type).idToCid[id];
1414
+
1415
+ if (clientId !== undefined) { return clientId; }
1416
+
1417
+ return this.pushHash(UNLOADED, id, type);
1306
1418
  },
1307
1419
 
1308
1420
  // ................
@@ -1311,10 +1423,10 @@ DS.Store = Ember.Object.extend({
1311
1423
 
1312
1424
  /**
1313
1425
  Load a new data hash into the store for a given id and type combination.
1314
- If data for that model had been loaded previously, the new information
1426
+ If data for that record had been loaded previously, the new information
1315
1427
  overwrites the old.
1316
1428
 
1317
- If the model you are loading data for has outstanding changes that have not
1429
+ If the record you are loading data for has outstanding changes that have not
1318
1430
  yet been saved, an exception will be thrown.
1319
1431
 
1320
1432
  @param {DS.Model} type
@@ -1325,7 +1437,7 @@ DS.Store = Ember.Object.extend({
1325
1437
  if (hash === undefined) {
1326
1438
  hash = id;
1327
1439
  var primaryKey = type.proto().primaryKey;
1328
- ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", primaryKey in hash);
1440
+ ember_assert("A data hash was loaded for a record of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", primaryKey in hash);
1329
1441
  id = hash[primaryKey];
1330
1442
  }
1331
1443
 
@@ -1337,16 +1449,16 @@ DS.Store = Ember.Object.extend({
1337
1449
  if (clientId !== undefined) {
1338
1450
  dataCache[clientId] = hash;
1339
1451
 
1340
- var model = recordCache[clientId];
1341
- if (model) {
1342
- model.send('didChangeData');
1452
+ var record = recordCache[clientId];
1453
+ if (record) {
1454
+ record.send('didChangeData');
1343
1455
  }
1344
1456
  } else {
1345
1457
  clientId = this.pushHash(hash, id, type);
1346
1458
  }
1347
1459
 
1348
1460
  DATA_PROXY.savedData = hash;
1349
- this.updateModelArrays(type, clientId, DATA_PROXY);
1461
+ this.updateRecordArrays(type, clientId, DATA_PROXY);
1350
1462
 
1351
1463
  return { id: id, clientId: clientId };
1352
1464
  },
@@ -1406,22 +1518,22 @@ DS.Store = Ember.Object.extend({
1406
1518
  return clientId;
1407
1519
  },
1408
1520
 
1409
- // .........................
1410
- // . MODEL MATERIALIZATION .
1411
- // .........................
1521
+ // ..........................
1522
+ // . RECORD MATERIALIZATION .
1523
+ // ..........................
1412
1524
 
1413
1525
  materializeRecord: function(type, clientId) {
1414
- var model;
1526
+ var record;
1415
1527
 
1416
- get(this, 'recordCache')[clientId] = model = type._create({
1528
+ get(this, 'recordCache')[clientId] = record = type._create({
1417
1529
  store: this,
1418
1530
  clientId: clientId
1419
1531
  });
1420
1532
 
1421
- get(this, 'defaultTransaction').adoptRecord(model);
1533
+ get(this, 'defaultTransaction').adoptRecord(record);
1422
1534
 
1423
- model.send('loadingData');
1424
- return model;
1535
+ record.send('loadingData');
1536
+ return record;
1425
1537
  },
1426
1538
 
1427
1539
  destroy: function() {
@@ -1433,12 +1545,164 @@ DS.Store = Ember.Object.extend({
1433
1545
  }
1434
1546
  });
1435
1547
 
1436
- })({});
1548
+ })();
1437
1549
 
1438
1550
 
1439
- (function(exports) {
1551
+
1552
+ (function() {
1440
1553
  var get = Ember.get, set = Ember.set, getPath = Ember.getPath, guidFor = Ember.guidFor;
1441
1554
 
1555
+ /**
1556
+ This file encapsulates the various states that a record can transition
1557
+ through during its lifecycle.
1558
+
1559
+ ### State Manager
1560
+
1561
+ A record's state manager explicitly tracks what state a record is in
1562
+ at any given time. For instance, if a record is newly created and has
1563
+ not yet been sent to the adapter to be saved, it would be in the
1564
+ `created.uncommitted` state. If a record has had local modifications
1565
+ made to it that are in the process of being saved, the record would be
1566
+ in the `updated.inFlight` state. (These state paths will be explained
1567
+ in more detail below.)
1568
+
1569
+ Events are sent by the record or its store to the record's state manager.
1570
+ How the state manager reacts to these events is dependent on which state
1571
+ it is in. In some states, certain events will be invalid and will cause
1572
+ an exception to be raised.
1573
+
1574
+ States are hierarchical. For example, a record can be in the
1575
+ `deleted.start` state, then transition into the `deleted.inFlight` state.
1576
+ If a child state does not implement an event handler, the state manager
1577
+ will attempt to invoke the event on all parent states until the root state is
1578
+ reached. The state hierarchy of a record is described in terms of a path
1579
+ string. You can determine a record's current state by getting its manager's
1580
+ current state path:
1581
+
1582
+ record.getPath('manager.currentState.path');
1583
+ //=> "created.uncommitted"
1584
+
1585
+ The `DS.Model` states are themselves stateless. What we mean is that,
1586
+ though each instance of a record also has a unique instance of a
1587
+ `DS.StateManager`, the hierarchical states that each of *those* points
1588
+ to is a shared data structure. For performance reasons, instead of each
1589
+ record getting its own copy of the hierarchy of states, each state
1590
+ manager points to this global, immutable shared instance. How does a
1591
+ state know which record it should be acting on? We pass a reference to
1592
+ the current state manager as the first parameter to every method invoked
1593
+ on a state.
1594
+
1595
+ The state manager passed as the first parameter is where you should stash
1596
+ state about the record if needed; you should never store data on the state
1597
+ object itself. If you need access to the record being acted on, you can
1598
+ retrieve the state manager's `record` property. For example, if you had
1599
+ an event handler `myEvent`:
1600
+
1601
+ myEvent: function(manager) {
1602
+ var record = manager.get('record');
1603
+ record.doSomething();
1604
+ }
1605
+
1606
+ For more information about state managers in general, see the Ember.js
1607
+ documentation on `Ember.StateManager`.
1608
+
1609
+ ### Events, Flags, and Transitions
1610
+
1611
+ A state may implement zero or more events, flags, or transitions.
1612
+
1613
+ #### Events
1614
+
1615
+ Events are named functions that are invoked when sent to a record. The
1616
+ state manager will first look for a method with the given name on the
1617
+ current state. If no method is found, it will search the current state's
1618
+ parent, and then its grandparent, and so on until reaching the top of
1619
+ the hierarchy. If the root is reached without an event handler being found,
1620
+ an exception will be raised. This can be very helpful when debugging new
1621
+ features.
1622
+
1623
+ Here's an example implementation of a state with a `myEvent` event handler:
1624
+
1625
+ aState: DS.State.create({
1626
+ myEvent: function(manager, param) {
1627
+ console.log("Received myEvent with "+param);
1628
+ }
1629
+ })
1630
+
1631
+ To trigger this event:
1632
+
1633
+ record.send('myEvent', 'foo');
1634
+ //=> "Received myEvent with foo"
1635
+
1636
+ Note that an optional parameter can be sent to a record's `send()` method,
1637
+ which will be passed as the second parameter to the event handler.
1638
+
1639
+ Events should transition to a different state if appropriate. This can be
1640
+ done by calling the state manager's `goToState()` method with a path to the
1641
+ desired state. The state manager will attempt to resolve the state path
1642
+ relative to the current state. If no state is found at that path, it will
1643
+ attempt to resolve it relative to the current state's parent, and then its
1644
+ parent, and so on until the root is reached. For example, imagine a hierarchy
1645
+ like this:
1646
+
1647
+ * created
1648
+ * start <-- currentState
1649
+ * inFlight
1650
+ * updated
1651
+ * inFlight
1652
+
1653
+ If we are currently in the `start` state, calling
1654
+ `goToState('inFlight')` would transition to the `created.inFlight` state,
1655
+ while calling `goToState('updated.inFlight')` would transition to
1656
+ the `updated.inFlight` state.
1657
+
1658
+ Remember that *only events* should ever cause a state transition. You should
1659
+ never call `goToState()` from outside a state's event handler. If you are
1660
+ tempted to do so, create a new event and send that to the state manager.
1661
+
1662
+ #### Flags
1663
+
1664
+ Flags are Boolean values that can be used to introspect a record's current
1665
+ state in a more user-friendly way than examining its state path. For example,
1666
+ instead of doing this:
1667
+
1668
+ var statePath = record.getPath('stateManager.currentState.path');
1669
+ if (statePath === 'created.inFlight') {
1670
+ doSomething();
1671
+ }
1672
+
1673
+ You can say:
1674
+
1675
+ if (record.get('isNew') && record.get('isSaving')) {
1676
+ doSomething();
1677
+ }
1678
+
1679
+ If your state does not set a value for a given flag, the value will
1680
+ be inherited from its parent (or the first place in the state hierarchy
1681
+ where it is defined).
1682
+
1683
+ The current set of flags are defined below. If you want to add a new flag,
1684
+ in addition to the area below, you will also need to declare it in the
1685
+ `DS.Model` class.
1686
+
1687
+ #### Transitions
1688
+
1689
+ Transitions are like event handlers but are called automatically upon
1690
+ entering or exiting a state. To implement a transition, just call a method
1691
+ either `enter` or `exit`:
1692
+
1693
+ myState: DS.State.create({
1694
+ // Gets called automatically when entering
1695
+ // this state.
1696
+ enter: function(manager) {
1697
+ console.log("Entered myState");
1698
+ }
1699
+ })
1700
+
1701
+ Note that enter and exit events are called once per transition. If the
1702
+ current state changes, but changes to another child state of the parent,
1703
+ the transition event on the parent will not be triggered.
1704
+ */
1705
+
1442
1706
  var stateProperty = Ember.computed(function(key) {
1443
1707
  var parent = get(this, 'parentState');
1444
1708
  if (parent) {
@@ -1482,18 +1746,27 @@ DS.State = Ember.State.extend({
1482
1746
  var setProperty = function(manager, context) {
1483
1747
  var key = context.key, value = context.value;
1484
1748
 
1485
- var model = get(manager, 'model'),
1486
- data = get(model, 'data');
1749
+ var record = get(manager, 'record'),
1750
+ data = get(record, 'data');
1487
1751
 
1488
1752
  set(data, key, value);
1489
1753
  };
1490
1754
 
1755
+ var setAssociation = function(manager, context) {
1756
+ var key = context.key, value = context.value;
1757
+
1758
+ var record = get(manager, 'record'),
1759
+ data = get(record, 'data');
1760
+
1761
+ data.setAssociation(key, value);
1762
+ };
1763
+
1491
1764
  var didChangeData = function(manager) {
1492
- var model = get(manager, 'model'),
1493
- data = get(model, 'data');
1765
+ var record = get(manager, 'record'),
1766
+ data = get(record, 'data');
1494
1767
 
1495
1768
  data._savedData = null;
1496
- model.notifyPropertyChange('data');
1769
+ record.notifyPropertyChange('data');
1497
1770
  };
1498
1771
 
1499
1772
  // The waitingOn event shares common functionality
@@ -1503,8 +1776,8 @@ var didChangeData = function(manager) {
1503
1776
  // behavior, and then implement the behavior specific
1504
1777
  // to the state.
1505
1778
  var waitingOn = function(manager, object) {
1506
- var model = get(manager, 'model'),
1507
- pendingQueue = get(model, 'pendingQueue'),
1779
+ var record = get(manager, 'record'),
1780
+ pendingQueue = get(record, 'pendingQueue'),
1508
1781
  objectGuid = guidFor(object);
1509
1782
 
1510
1783
  var observer = function() {
@@ -1559,17 +1832,7 @@ var waitingOn = function(manager, object) {
1559
1832
  // super points to the class definition.
1560
1833
  var Uncommitted = Ember.Mixin.create({
1561
1834
  setProperty: setProperty,
1562
-
1563
- deleteRecord: function(manager) {
1564
- this._super(manager);
1565
-
1566
- var model = get(manager, 'model'),
1567
- dirtyType = get(this, 'dirtyType');
1568
-
1569
- model.withTransaction(function(t) {
1570
- t.modelBecameClean(dirtyType, model);
1571
- });
1572
- }
1835
+ setAssociation: setAssociation,
1573
1836
  });
1574
1837
 
1575
1838
  // These mixins are mixed into substates of the concrete
@@ -1577,8 +1840,12 @@ var Uncommitted = Ember.Mixin.create({
1577
1840
 
1578
1841
  var CreatedUncommitted = Ember.Mixin.create({
1579
1842
  deleteRecord: function(manager) {
1843
+ var record = get(manager, 'record');
1580
1844
  this._super(manager);
1581
1845
 
1846
+ record.withTransaction(function(t) {
1847
+ t.recordBecameClean('created', record);
1848
+ });
1582
1849
  manager.goToState('deleted.saved');
1583
1850
  }
1584
1851
  });
@@ -1587,10 +1854,10 @@ var UpdatedUncommitted = Ember.Mixin.create({
1587
1854
  deleteRecord: function(manager) {
1588
1855
  this._super(manager);
1589
1856
 
1590
- var model = get(manager, 'model');
1857
+ var record = get(manager, 'record');
1591
1858
 
1592
- model.withTransaction(function(t) {
1593
- t.modelBecameClean('created', model);
1859
+ record.withTransaction(function(t) {
1860
+ t.recordBecameClean('updated', record);
1594
1861
  });
1595
1862
 
1596
1863
  manager.goToState('deleted');
@@ -1618,16 +1885,16 @@ var DirtyState = DS.State.extend({
1618
1885
  // TRANSITIONS
1619
1886
  enter: function(manager) {
1620
1887
  var dirtyType = get(this, 'dirtyType'),
1621
- model = get(manager, 'model');
1888
+ record = get(manager, 'record');
1622
1889
 
1623
- model.withTransaction(function (t) {
1624
- t.modelBecameDirty(dirtyType, model);
1890
+ record.withTransaction(function (t) {
1891
+ t.recordBecameDirty(dirtyType, record);
1625
1892
  });
1626
1893
  },
1627
1894
 
1628
1895
  exit: function(manager) {
1629
- var model = get(manager, 'model');
1630
- manager.send('invokeLifecycleCallbacks', model);
1896
+ var record = get(manager, 'record');
1897
+ manager.send('invokeLifecycleCallbacks', record);
1631
1898
  },
1632
1899
 
1633
1900
  // EVENTS
@@ -1640,6 +1907,20 @@ var DirtyState = DS.State.extend({
1640
1907
 
1641
1908
  willCommit: function(manager) {
1642
1909
  manager.goToState('inFlight');
1910
+ },
1911
+
1912
+ rollback: function(manager) {
1913
+ var record = get(manager, 'record'),
1914
+ dirtyType = get(this, 'dirtyType'),
1915
+ data = get(record, 'data');
1916
+
1917
+ data.rollback();
1918
+
1919
+ record.withTransaction(function(t) {
1920
+ t.recordBecameClean(dirtyType, record);
1921
+ });
1922
+
1923
+ manager.goToState('loaded');
1643
1924
  }
1644
1925
  }, Uncommitted),
1645
1926
 
@@ -1653,10 +1934,10 @@ var DirtyState = DS.State.extend({
1653
1934
  // TRANSITIONS
1654
1935
  enter: function(manager) {
1655
1936
  var dirtyType = get(this, 'dirtyType'),
1656
- model = get(manager, 'model');
1937
+ record = get(manager, 'record');
1657
1938
 
1658
- model.withTransaction(function (t) {
1659
- t.modelBecameClean(dirtyType, model);
1939
+ record.withTransaction(function (t) {
1940
+ t.recordBecameClean(dirtyType, record);
1660
1941
  });
1661
1942
  },
1662
1943
 
@@ -1666,9 +1947,9 @@ var DirtyState = DS.State.extend({
1666
1947
  },
1667
1948
 
1668
1949
  becameInvalid: function(manager, errors) {
1669
- var model = get(manager, 'model');
1950
+ var record = get(manager, 'record');
1670
1951
 
1671
- set(model, 'errors', errors);
1952
+ set(record, 'errors', errors);
1672
1953
  manager.goToState('invalid');
1673
1954
  },
1674
1955
 
@@ -1698,8 +1979,8 @@ var DirtyState = DS.State.extend({
1698
1979
  uncommitted: DS.State.extend({
1699
1980
  // EVENTS
1700
1981
  deleteRecord: function(manager) {
1701
- var model = get(manager, 'model'),
1702
- pendingQueue = get(model, 'pendingQueue'),
1982
+ var record = get(manager, 'record'),
1983
+ pendingQueue = get(record, 'pendingQueue'),
1703
1984
  tuple;
1704
1985
 
1705
1986
  // since we are leaving the pending state, remove any
@@ -1717,8 +1998,8 @@ var DirtyState = DS.State.extend({
1717
1998
  },
1718
1999
 
1719
2000
  doneWaitingOn: function(manager, object) {
1720
- var model = get(manager, 'model'),
1721
- pendingQueue = get(model, 'pendingQueue'),
2001
+ var record = get(manager, 'record'),
2002
+ pendingQueue = get(record, 'pendingQueue'),
1722
2003
  objectGuid = guidFor(object);
1723
2004
 
1724
2005
  delete pendingQueue[objectGuid];
@@ -1744,8 +2025,8 @@ var DirtyState = DS.State.extend({
1744
2025
 
1745
2026
  // EVENTS
1746
2027
  doneWaitingOn: function(manager, object) {
1747
- var model = get(manager, 'model'),
1748
- pendingQueue = get(model, 'pendingQueue'),
2028
+ var record = get(manager, 'record'),
2029
+ pendingQueue = get(record, 'pendingQueue'),
1749
2030
  objectGuid = guidFor(object);
1750
2031
 
1751
2032
  delete pendingQueue[objectGuid];
@@ -1756,10 +2037,10 @@ var DirtyState = DS.State.extend({
1756
2037
  },
1757
2038
 
1758
2039
  doneWaiting: function(manager) {
1759
- var model = get(manager, 'model'),
1760
- transaction = get(model, 'transaction');
2040
+ var record = get(manager, 'record'),
2041
+ transaction = get(record, 'transaction');
1761
2042
 
1762
- // Now that the model is no longer pending, schedule
2043
+ // Now that the record is no longer pending, schedule
1763
2044
  // the transaction to commit.
1764
2045
  Ember.run.once(transaction, transaction.commit);
1765
2046
  },
@@ -1783,11 +2064,13 @@ var DirtyState = DS.State.extend({
1783
2064
  manager.goToState('deleted');
1784
2065
  },
1785
2066
 
2067
+ setAssociation: setAssociation,
2068
+
1786
2069
  setProperty: function(manager, context) {
1787
2070
  setProperty(manager, context);
1788
2071
 
1789
- var model = get(manager, 'model'),
1790
- errors = get(model, 'errors'),
2072
+ var record = get(manager, 'record'),
2073
+ errors = get(record, 'errors'),
1791
2074
  key = context.key;
1792
2075
 
1793
2076
  delete errors[key];
@@ -1814,8 +2097,8 @@ var createdState = DirtyState.create({
1814
2097
  isNew: true,
1815
2098
 
1816
2099
  // EVENTS
1817
- invokeLifecycleCallbacks: function(manager, model) {
1818
- model.didCreate();
2100
+ invokeLifecycleCallbacks: function(manager, record) {
2101
+ record.fire('didCreate');
1819
2102
  }
1820
2103
  });
1821
2104
 
@@ -1823,8 +2106,8 @@ var updatedState = DirtyState.create({
1823
2106
  dirtyType: 'updated',
1824
2107
 
1825
2108
  // EVENTS
1826
- invokeLifecycleCallbacks: function(manager, model) {
1827
- model.didUpdate();
2109
+ invokeLifecycleCallbacks: function(manager, record) {
2110
+ record.fire('didUpdate');
1828
2111
  }
1829
2112
  });
1830
2113
 
@@ -1833,6 +2116,15 @@ var updatedState = DirtyState.create({
1833
2116
  createdState.states.uncommitted.reopen(CreatedUncommitted);
1834
2117
  createdState.states.pending.states.uncommitted.reopen(CreatedUncommitted);
1835
2118
 
2119
+ // The created.uncommitted state needs to immediately transition to the
2120
+ // deleted state if it is rolled back.
2121
+ createdState.states.uncommitted.reopen({
2122
+ rollback: function(manager) {
2123
+ this._super(manager);
2124
+ manager.goToState('deleted.saved');
2125
+ }
2126
+ });
2127
+
1836
2128
  // The updated.uncommitted state and updated.pending.uncommitted share
1837
2129
  // some logic defined in UpdatedUncommitted.
1838
2130
  updatedState.states.uncommitted.reopen(UpdatedUncommitted);
@@ -1879,8 +2171,8 @@ var states = {
1879
2171
  loading: DS.State.create({
1880
2172
  // TRANSITIONS
1881
2173
  exit: function(manager) {
1882
- var model = get(manager, 'model');
1883
- model.didLoad();
2174
+ var record = get(manager, 'record');
2175
+ record.fire('didLoad');
1884
2176
  },
1885
2177
 
1886
2178
  // EVENTS
@@ -1914,6 +2206,11 @@ var states = {
1914
2206
  manager.goToState('updated');
1915
2207
  },
1916
2208
 
2209
+ setAssociation: function(manager, context) {
2210
+ setAssociation(manager, context);
2211
+ manager.goToState('updated');
2212
+ },
2213
+
1917
2214
  didChangeData: didChangeData,
1918
2215
 
1919
2216
  deleteRecord: function(manager) {
@@ -1944,6 +2241,14 @@ var states = {
1944
2241
  isLoaded: true,
1945
2242
  isDirty: true,
1946
2243
 
2244
+ // TRANSITIONS
2245
+ enter: function(manager) {
2246
+ var record = get(manager, 'record'),
2247
+ store = get(record, 'store');
2248
+
2249
+ store.removeFromRecordArrays(record);
2250
+ },
2251
+
1947
2252
  // SUBSTATES
1948
2253
 
1949
2254
  // When a record is deleted, it enters the `start`
@@ -1952,21 +2257,27 @@ var states = {
1952
2257
  start: DS.State.create({
1953
2258
  // TRANSITIONS
1954
2259
  enter: function(manager) {
1955
- var model = get(manager, 'model');
1956
- var store = get(model, 'store');
1957
-
1958
- if (store) {
1959
- store.removeFromModelArrays(model);
1960
- }
2260
+ var record = get(manager, 'record');
1961
2261
 
1962
- model.withTransaction(function(t) {
1963
- t.modelBecameDirty('deleted', model);
2262
+ record.withTransaction(function(t) {
2263
+ t.recordBecameDirty('deleted', record);
1964
2264
  });
1965
2265
  },
1966
2266
 
1967
2267
  // EVENTS
1968
2268
  willCommit: function(manager) {
1969
2269
  manager.goToState('inFlight');
2270
+ },
2271
+
2272
+ rollback: function(manager) {
2273
+ var record = get(manager, 'record'),
2274
+ data = get(record, 'data');
2275
+
2276
+ data.rollback();
2277
+ record.withTransaction(function(t) {
2278
+ t.recordBecameClean('deleted', record);
2279
+ });
2280
+ manager.goToState('loaded');
1970
2281
  }
1971
2282
  }),
1972
2283
 
@@ -1980,10 +2291,10 @@ var states = {
1980
2291
 
1981
2292
  // TRANSITIONS
1982
2293
  exit: function(stateManager) {
1983
- var model = get(stateManager, 'model');
2294
+ var record = get(stateManager, 'record');
1984
2295
 
1985
- model.withTransaction(function(t) {
1986
- t.modelBecameClean('deleted', model);
2296
+ record.withTransaction(function(t) {
2297
+ t.recordBecameClean('deleted', record);
1987
2298
  });
1988
2299
  },
1989
2300
 
@@ -2012,24 +2323,21 @@ var states = {
2012
2323
  };
2013
2324
 
2014
2325
  DS.StateManager = Ember.StateManager.extend({
2015
- model: null,
2326
+ record: null,
2016
2327
  initialState: 'rootState',
2017
2328
  states: states
2018
2329
  });
2019
2330
 
2020
- })({});
2331
+ })();
2021
2332
 
2022
2333
 
2023
- (function(exports) {
2024
- var get = Ember.get, set = Ember.set, getPath = Ember.getPath, none = Ember.none;
2025
2334
 
2026
- var retrieveFromCurrentState = Ember.computed(function(key) {
2027
- return get(getPath(this, 'stateManager.currentState'), key);
2028
- }).property('stateManager.currentState').cacheable();
2335
+ (function() {
2336
+ var get = Ember.get, set = Ember.set;
2029
2337
 
2030
2338
  // This object is a regular JS object for performance. It is only
2031
2339
  // used internally for bookkeeping purposes.
2032
- var DataProxy = function(record) {
2340
+ var DataProxy = DS._DataProxy = function(record) {
2033
2341
  this.record = record;
2034
2342
  this.unsavedData = {};
2035
2343
  this.associations = {};
@@ -2111,6 +2419,8 @@ DataProxy.prototype = {
2111
2419
 
2112
2420
  rollback: function() {
2113
2421
  this.unsavedData = {};
2422
+
2423
+ this.record.notifyPropertyChange('data');
2114
2424
  },
2115
2425
 
2116
2426
  adapterDidUpdate: function(data) {
@@ -2118,7 +2428,18 @@ DataProxy.prototype = {
2118
2428
  }
2119
2429
  };
2120
2430
 
2121
- DS.Model = Ember.Object.extend({
2431
+ })();
2432
+
2433
+
2434
+
2435
+ (function() {
2436
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath, none = Ember.none;
2437
+
2438
+ var retrieveFromCurrentState = Ember.computed(function(key) {
2439
+ return get(getPath(this, 'stateManager.currentState'), key);
2440
+ }).property('stateManager.currentState').cacheable();
2441
+
2442
+ DS.Model = Ember.Object.extend(Ember.Evented, {
2122
2443
  isLoaded: retrieveFromCurrentState,
2123
2444
  isDirty: retrieveFromCurrentState,
2124
2445
  isSaving: retrieveFromCurrentState,
@@ -2235,6 +2556,7 @@ DS.Model = Ember.Object.extend({
2235
2556
  }
2236
2557
  }
2237
2558
 
2559
+ key = options.key || get(this, 'namingConvention').keyToJSONKey(key);
2238
2560
  json[key] = records;
2239
2561
  },
2240
2562
 
@@ -2300,7 +2622,7 @@ DS.Model = Ember.Object.extend({
2300
2622
  },
2301
2623
 
2302
2624
  data: Ember.computed(function() {
2303
- return new DataProxy(this);
2625
+ return new DS._DataProxy(this);
2304
2626
  }).cacheable(),
2305
2627
 
2306
2628
  didLoad: Ember.K,
@@ -2309,7 +2631,7 @@ DS.Model = Ember.Object.extend({
2309
2631
 
2310
2632
  init: function() {
2311
2633
  var stateManager = DS.StateManager.create({
2312
- model: this
2634
+ record: this
2313
2635
  });
2314
2636
 
2315
2637
  set(this, 'pendingQueue', {});
@@ -2357,7 +2679,7 @@ DS.Model = Ember.Object.extend({
2357
2679
  var data = get(this, 'data');
2358
2680
 
2359
2681
  if (data && key in data) {
2360
- ember_assert("You attempted to access the " + key + " property on a model without defining an attribute.", false);
2682
+ ember_assert("You attempted to access the " + key + " property on a record without defining an attribute.", false);
2361
2683
  }
2362
2684
  },
2363
2685
 
@@ -2365,7 +2687,7 @@ DS.Model = Ember.Object.extend({
2365
2687
  var data = get(this, 'data');
2366
2688
 
2367
2689
  if (data && key in data) {
2368
- ember_assert("You attempted to set the " + key + " property on a model without defining an attribute.", false);
2690
+ ember_assert("You attempted to set the " + key + " property on a record without defining an attribute.", false);
2369
2691
  } else {
2370
2692
  return this._super(key, value);
2371
2693
  }
@@ -2384,10 +2706,44 @@ DS.Model = Ember.Object.extend({
2384
2706
 
2385
2707
  /** @private */
2386
2708
  hashWasUpdated: function() {
2387
- // At the end of the run loop, notify model arrays that
2709
+ // At the end of the run loop, notify record arrays that
2388
2710
  // this record has changed so they can re-evaluate its contents
2389
2711
  // to determine membership.
2390
2712
  Ember.run.once(this, this.notifyHashWasUpdated);
2713
+ },
2714
+
2715
+ dataDidChange: Ember.observer(function() {
2716
+ var associations = get(this.constructor, 'associationsByName'),
2717
+ data = get(this, 'data'), store = get(this, 'store'),
2718
+ idToClientId = store.idToClientId,
2719
+ cachedValue;
2720
+
2721
+ associations.forEach(function(name, association) {
2722
+ if (association.kind === 'hasMany') {
2723
+ cachedValue = this.cacheFor(name);
2724
+
2725
+ if (cachedValue) {
2726
+ var ids = data.get(name) || [];
2727
+ var clientIds = Ember.ArrayUtils.map(ids, function(id) {
2728
+ return store.clientIdForId(association.type, id);
2729
+ });
2730
+
2731
+ set(cachedValue, 'content', Ember.A(clientIds));
2732
+ cachedValue.fetch();
2733
+ }
2734
+ }
2735
+ }, this);
2736
+ }, 'data'),
2737
+
2738
+ /**
2739
+ @private
2740
+
2741
+ Override the default event firing from Ember.Evented to
2742
+ also call methods with the given name.
2743
+ */
2744
+ fire: function(name) {
2745
+ this[name].apply(this, [].slice.call(arguments, 1));
2746
+ this._super.apply(this, arguments);
2391
2747
  }
2392
2748
  });
2393
2749
 
@@ -2418,10 +2774,11 @@ DS.Model.reopenClass({
2418
2774
  createRecord: storeAlias('createRecord')
2419
2775
  });
2420
2776
 
2421
- })({});
2777
+ })();
2778
+
2422
2779
 
2423
2780
 
2424
- (function(exports) {
2781
+ (function() {
2425
2782
  var get = Ember.get, getPath = Ember.getPath;
2426
2783
  DS.Model.reopenClass({
2427
2784
  attributes: Ember.computed(function() {
@@ -2573,75 +2930,27 @@ DS.attr.transforms = {
2573
2930
  };
2574
2931
 
2575
2932
 
2576
- })({});
2577
-
2578
-
2579
- (function(exports) {
2580
- var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
2581
- DS.Model.reopenClass({
2582
- typeForAssociation: function(name) {
2583
- var association = get(this, 'associationsByName').get(name);
2584
- return association && association.type;
2585
- },
2586
-
2587
- associations: Ember.computed(function() {
2588
- var map = Ember.Map.create();
2589
-
2590
- this.eachComputedProperty(function(name, meta) {
2591
- if (meta.isAssociation) {
2592
- var type = meta.type,
2593
- typeList = map.get(type);
2594
-
2595
- if (typeof type === 'string') {
2596
- type = getPath(this, type, false) || getPath(window, type);
2597
- meta.type = type;
2598
- }
2599
-
2600
- if (!typeList) {
2601
- typeList = [];
2602
- map.set(type, typeList);
2603
- }
2604
-
2605
- typeList.push({ name: name, kind: meta.kind });
2606
- }
2607
- });
2933
+ })();
2608
2934
 
2609
- return map;
2610
- }).cacheable(),
2611
2935
 
2612
- associationsByName: Ember.computed(function() {
2613
- var map = Ember.Map.create(), type;
2614
2936
 
2615
- this.eachComputedProperty(function(name, meta) {
2616
- if (meta.isAssociation) {
2617
- meta.key = name;
2618
- type = meta.type;
2937
+ (function() {
2619
2938
 
2620
- if (typeof type === 'string') {
2621
- type = getPath(this, type, false) || getPath(window, type);
2622
- meta.type = type;
2623
- }
2939
+ })();
2624
2940
 
2625
- map.set(name, meta);
2626
- }
2627
- });
2628
2941
 
2629
- return map;
2630
- }).cacheable()
2631
- });
2632
2942
 
2943
+ (function() {
2944
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath,
2945
+ none = Ember.none;
2633
2946
 
2634
2947
  var embeddedFindRecord = function(store, type, data, key, one) {
2635
- var association = data ? get(data, key) : one ? null : [];
2636
- if (one) {
2637
- return association ? store.load(type, association).id : null;
2638
- } else {
2639
- return association ? store.loadMany(type, association).ids : [];
2640
- }
2948
+ var association = get(data, key);
2949
+ return none(association) ? undefined : store.load(type, association).id;
2641
2950
  };
2642
2951
 
2643
2952
  var referencedFindRecord = function(store, type, data, key, one) {
2644
- return data ? get(data, key) : one ? null : [];
2953
+ return get(data, key);
2645
2954
  };
2646
2955
 
2647
2956
  var hasAssociation = function(type, options, one) {
@@ -2650,12 +2959,7 @@ var hasAssociation = function(type, options, one) {
2650
2959
  var embedded = options.embedded,
2651
2960
  findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
2652
2961
 
2653
- var meta = { type: type, isAssociation: true, options: options };
2654
- if (one) {
2655
- meta.kind = 'belongsTo';
2656
- } else {
2657
- meta.kind = 'hasMany';
2658
- }
2962
+ var meta = { type: type, isAssociation: true, options: options, kind: 'belongsTo' };
2659
2963
 
2660
2964
  return Ember.computed(function(key, value) {
2661
2965
  var data = get(this, 'data'), ids, id, association,
@@ -2665,57 +2969,477 @@ var hasAssociation = function(type, options, one) {
2665
2969
  type = getPath(this, type, false) || getPath(window, type);
2666
2970
  }
2667
2971
 
2668
- if (one) {
2669
- if (arguments.length === 2) {
2670
- key = options.key || get(this, 'namingConvention').foreignKey(key);
2671
- data.setAssociation(key, get(value, 'clientId'));
2672
- // put the client id in `key` in the data hash
2673
- return value;
2674
- } else {
2675
- // Embedded belongsTo associations should not look for
2676
- // a foreign key.
2677
- if (embedded) {
2678
- key = options.key || key;
2972
+ if (arguments.length === 2) {
2973
+ key = options.key || get(this, 'namingConvention').foreignKey(key);
2974
+ this.send('setAssociation', { key: key, value: value === null ? null : get(value, 'clientId') });
2975
+ //data.setAssociation(key, get(value, 'clientId'));
2976
+ // put the client id in `key` in the data hash
2977
+ return value;
2978
+ } else {
2979
+ // Embedded belongsTo associations should not look for
2980
+ // a foreign key.
2981
+ if (embedded) {
2982
+ key = options.key || get(this, 'namingConvention').keyToJSONKey(key);
2679
2983
 
2680
- // Non-embedded associations should look for a foreign key.
2681
- // For example, instead of person, we might look for person_id
2682
- } else {
2683
- key = options.key || get(this, 'namingConvention').foreignKey(key);
2684
- }
2685
- id = findRecord(store, type, data, key, true);
2686
- association = id ? store.find(type, id) : null;
2984
+ // Non-embedded associations should look for a foreign key.
2985
+ // For example, instead of person, we might look for person_id
2986
+ } else {
2987
+ key = options.key || get(this, 'namingConvention').foreignKey(key);
2687
2988
  }
2688
- } else {
2689
- key = options.key || key;
2690
- ids = findRecord(store, type, data, key);
2691
- association = store.findMany(type, ids);
2692
- set(association, 'parentRecord', this);
2989
+ id = findRecord(store, type, data, key, true);
2990
+ association = id ? store.find(type, id) : null;
2693
2991
  }
2694
2992
 
2695
2993
  return association;
2696
2994
  }).property('data').cacheable().meta(meta);
2697
2995
  };
2698
2996
 
2699
- DS.hasMany = function(type, options) {
2700
- ember_assert("The type passed to DS.hasMany must be defined", !!type);
2997
+ DS.belongsTo = function(type, options) {
2998
+ ember_assert("The type passed to DS.belongsTo must be defined", !!type);
2701
2999
  return hasAssociation(type, options);
2702
3000
  };
2703
3001
 
2704
- DS.hasOne = function(type, options) {
2705
- ember_assert("The type passed to DS.belongsTo must be defined", !!type);
2706
- return hasAssociation(type, options, true);
2707
- };
3002
+ })();
2708
3003
 
2709
- DS.belongsTo = DS.hasOne;
2710
3004
 
2711
- })({});
2712
3005
 
3006
+ (function() {
3007
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
3008
+ var embeddedFindRecord = function(store, type, data, key) {
3009
+ var association = get(data, key);
3010
+ return association ? store.loadMany(type, association).ids : [];
3011
+ };
2713
3012
 
2714
- (function(exports) {
2715
- })({});
3013
+ var referencedFindRecord = function(store, type, data, key, one) {
3014
+ return get(data, key);
3015
+ };
3016
+
3017
+ var hasAssociation = function(type, options) {
3018
+ options = options || {};
3019
+
3020
+ var embedded = options.embedded,
3021
+ findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
3022
+
3023
+ var meta = { type: type, isAssociation: true, options: options, kind: 'hasMany' };
3024
+
3025
+ return Ember.computed(function(key, value) {
3026
+ var data = get(this, 'data'),
3027
+ store = get(this, 'store'),
3028
+ ids, id, association;
3029
+
3030
+ if (typeof type === 'string') {
3031
+ type = getPath(this, type, false) || getPath(window, type);
3032
+ }
3033
+
3034
+ key = options.key || key;
3035
+ ids = findRecord(store, type, data, key);
3036
+ association = store.findMany(type, ids);
3037
+ set(association, 'parentRecord', this);
3038
+
3039
+ return association;
3040
+ }).property().cacheable().meta(meta);
3041
+ };
3042
+
3043
+ DS.hasMany = function(type, options) {
3044
+ ember_assert("The type passed to DS.hasMany must be defined", !!type);
3045
+ return hasAssociation(type, options);
3046
+ };
3047
+
3048
+ })();
2716
3049
 
2717
3050
 
2718
- (function(exports) {
3051
+
3052
+ (function() {
3053
+ var get = Ember.get, getPath = Ember.getPath;
3054
+
3055
+ DS.Model.reopenClass({
3056
+ typeForAssociation: function(name) {
3057
+ var association = get(this, 'associationsByName').get(name);
3058
+ return association && association.type;
3059
+ },
3060
+
3061
+ associations: Ember.computed(function() {
3062
+ var map = Ember.Map.create();
3063
+
3064
+ this.eachComputedProperty(function(name, meta) {
3065
+ if (meta.isAssociation) {
3066
+ var type = meta.type,
3067
+ typeList = map.get(type);
3068
+
3069
+ if (typeof type === 'string') {
3070
+ type = getPath(this, type, false) || getPath(window, type);
3071
+ meta.type = type;
3072
+ }
3073
+
3074
+ if (!typeList) {
3075
+ typeList = [];
3076
+ map.set(type, typeList);
3077
+ }
3078
+
3079
+ typeList.push({ name: name, kind: meta.kind });
3080
+ }
3081
+ });
3082
+
3083
+ return map;
3084
+ }).cacheable(),
3085
+
3086
+ associationsByName: Ember.computed(function() {
3087
+ var map = Ember.Map.create(), type;
3088
+
3089
+ this.eachComputedProperty(function(name, meta) {
3090
+ if (meta.isAssociation) {
3091
+ meta.key = name;
3092
+ type = meta.type;
3093
+
3094
+ if (typeof type === 'string') {
3095
+ type = getPath(this, type, false) || getPath(window, type);
3096
+ meta.type = type;
3097
+ }
3098
+
3099
+ map.set(name, meta);
3100
+ }
3101
+ });
3102
+
3103
+ return map;
3104
+ }).cacheable()
3105
+ });
3106
+
3107
+ })();
3108
+
3109
+
3110
+
3111
+ (function() {
3112
+
3113
+ })();
3114
+
3115
+
3116
+
3117
+ (function() {
3118
+ DS.Adapter = Ember.Object.extend({
3119
+ commit: function(store, commitDetails) {
3120
+ commitDetails.updated.eachType(function(type, array) {
3121
+ this.updateRecords(store, type, array.slice());
3122
+ }, this);
3123
+
3124
+ commitDetails.created.eachType(function(type, array) {
3125
+ this.createRecords(store, type, array.slice());
3126
+ }, this);
3127
+
3128
+ commitDetails.deleted.eachType(function(type, array) {
3129
+ this.deleteRecords(store, type, array.slice());
3130
+ }, this);
3131
+ },
3132
+
3133
+ createRecords: function(store, type, records) {
3134
+ records.forEach(function(record) {
3135
+ this.createRecord(store, type, record);
3136
+ }, this);
3137
+ },
3138
+
3139
+ updateRecords: function(store, type, records) {
3140
+ records.forEach(function(record) {
3141
+ this.updateRecord(store, type, record);
3142
+ }, this);
3143
+ },
3144
+
3145
+ deleteRecords: function(store, type, records) {
3146
+ records.forEach(function(record) {
3147
+ this.deleteRecord(store, type, record);
3148
+ }, this);
3149
+ },
3150
+
3151
+ findMany: function(store, type, ids) {
3152
+ ids.forEach(function(id) {
3153
+ this.find(store, type, id);
3154
+ }, this);
3155
+ }
3156
+ });
3157
+
3158
+ })();
3159
+
3160
+
3161
+
3162
+ (function() {
3163
+ DS.fixtureAdapter = DS.Adapter.create({
3164
+ find: function(store, type, id) {
3165
+ var fixtures = type.FIXTURES;
3166
+
3167
+ ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
3168
+ if (fixtures.hasLoaded) { return; }
3169
+
3170
+ setTimeout(function() {
3171
+ store.loadMany(type, fixtures);
3172
+ fixtures.hasLoaded = true;
3173
+ }, 300);
3174
+ },
3175
+
3176
+ findMany: function() {
3177
+ this.find.apply(this, arguments);
3178
+ },
3179
+
3180
+ findAll: function(store, type) {
3181
+ var fixtures = type.FIXTURES;
3182
+
3183
+ ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
3184
+
3185
+ var ids = fixtures.map(function(item, index, self){ return item.id; });
3186
+ store.loadMany(type, ids, fixtures);
3187
+ }
3188
+
3189
+ });
3190
+
3191
+ })();
3192
+
3193
+
3194
+
3195
+ (function() {
3196
+ /*global jQuery*/
3197
+
3198
+ var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
3199
+
3200
+ DS.RESTAdapter = DS.Adapter.extend({
3201
+ createRecord: function(store, type, record) {
3202
+ var root = this.rootForType(type);
3203
+
3204
+ var data = {};
3205
+ data[root] = record.toJSON();
3206
+
3207
+ this.ajax(this.buildURL(root), "POST", {
3208
+ data: data,
3209
+ success: function(json) {
3210
+ this.sideload(store, type, json, root);
3211
+ store.didCreateRecord(record, json[root]);
3212
+ }
3213
+ });
3214
+ },
3215
+
3216
+ createRecords: function(store, type, records) {
3217
+ if (get(this, 'bulkCommit') === false) {
3218
+ return this._super(store, type, records);
3219
+ }
3220
+
3221
+ var root = this.rootForType(type),
3222
+ plural = this.pluralize(root);
3223
+
3224
+ var data = {};
3225
+ data[plural] = records.map(function(record) {
3226
+ return record.toJSON();
3227
+ });
3228
+
3229
+ this.ajax(this.buildURL(root), "POST", {
3230
+ data: data,
3231
+
3232
+ success: function(json) {
3233
+ this.sideload(store, type, json, plural);
3234
+ store.didCreateRecords(type, records, json[plural]);
3235
+ }
3236
+ });
3237
+ },
3238
+
3239
+ updateRecord: function(store, type, record) {
3240
+ var id = get(record, 'id');
3241
+ var root = this.rootForType(type);
3242
+
3243
+ var data = {};
3244
+ data[root] = record.toJSON();
3245
+
3246
+ this.ajax(this.buildURL(root, id), "PUT", {
3247
+ data: data,
3248
+ success: function(json) {
3249
+ this.sideload(store, type, json, root);
3250
+ store.didUpdateRecord(record, json && json[root]);
3251
+ }
3252
+ });
3253
+ },
3254
+
3255
+ updateRecords: function(store, type, records) {
3256
+ if (get(this, 'bulkCommit') === false) {
3257
+ return this._super(store, type, records);
3258
+ }
3259
+
3260
+ var root = this.rootForType(type),
3261
+ plural = this.pluralize(root);
3262
+
3263
+ var data = {};
3264
+ data[plural] = records.map(function(record) {
3265
+ return record.toJSON();
3266
+ });
3267
+
3268
+ this.ajax(this.buildURL(root, "bulk"), "PUT", {
3269
+ data: data,
3270
+ success: function(json) {
3271
+ this.sideload(store, type, json, plural);
3272
+ store.didUpdateRecords(records, json[plural]);
3273
+ }
3274
+ });
3275
+ },
3276
+
3277
+ deleteRecord: function(store, type, record) {
3278
+ var id = get(record, 'id');
3279
+ var root = this.rootForType(type);
3280
+
3281
+ this.ajax(this.buildURL(root, id), "DELETE", {
3282
+ success: function(json) {
3283
+ if (json) { this.sideload(store, type, json); }
3284
+ store.didDeleteRecord(record);
3285
+ }
3286
+ });
3287
+ },
3288
+
3289
+ deleteRecords: function(store, type, records) {
3290
+ if (get(this, 'bulkCommit') === false) {
3291
+ return this._super(store, type, records);
3292
+ }
3293
+
3294
+ var root = this.rootForType(type),
3295
+ plural = this.pluralize(root);
3296
+
3297
+ var data = {};
3298
+ data[plural] = records.map(function(record) {
3299
+ return get(record, 'id');
3300
+ });
3301
+
3302
+ this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
3303
+ data: data,
3304
+ success: function(json) {
3305
+ if (json) { this.sideload(store, type, json); }
3306
+ store.didDeleteRecords(records);
3307
+ }
3308
+ });
3309
+ },
3310
+
3311
+ find: function(store, type, id) {
3312
+ var root = this.rootForType(type);
3313
+
3314
+ this.ajax(this.buildURL(root, id), "GET", {
3315
+ success: function(json) {
3316
+ store.load(type, json[root]);
3317
+ this.sideload(store, type, json, root);
3318
+ }
3319
+ });
3320
+ },
3321
+
3322
+ findMany: function(store, type, ids) {
3323
+ var root = this.rootForType(type), plural = this.pluralize(root);
3324
+
3325
+ this.ajax(this.buildURL(root), "GET", {
3326
+ data: { ids: ids },
3327
+ success: function(json) {
3328
+ store.loadMany(type, ids, json[plural]);
3329
+ this.sideload(store, type, json, plural);
3330
+ }
3331
+ });
3332
+ },
3333
+
3334
+ findAll: function(store, type) {
3335
+ var root = this.rootForType(type), plural = this.pluralize(root);
3336
+
3337
+ this.ajax(this.buildURL(root), "GET", {
3338
+ success: function(json) {
3339
+ store.loadMany(type, json[plural]);
3340
+ this.sideload(store, type, json, plural);
3341
+ }
3342
+ });
3343
+ },
3344
+
3345
+ findQuery: function(store, type, query, recordArray) {
3346
+ var root = this.rootForType(type), plural = this.pluralize(root);
3347
+
3348
+ this.ajax(this.buildURL(root), "GET", {
3349
+ data: query,
3350
+ success: function(json) {
3351
+ recordArray.load(json[plural]);
3352
+ this.sideload(store, type, json, plural);
3353
+ }
3354
+ });
3355
+ },
3356
+
3357
+ // HELPERS
3358
+
3359
+ plurals: {},
3360
+
3361
+ // define a plurals hash in your subclass to define
3362
+ // special-case pluralization
3363
+ pluralize: function(name) {
3364
+ return this.plurals[name] || name + "s";
3365
+ },
3366
+
3367
+ rootForType: function(type) {
3368
+ if (type.url) { return type.url; }
3369
+
3370
+ // use the last part of the name as the URL
3371
+ var parts = type.toString().split(".");
3372
+ var name = parts[parts.length - 1];
3373
+ return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
3374
+ },
3375
+
3376
+ ajax: function(url, type, hash) {
3377
+ hash.url = url;
3378
+ hash.type = type;
3379
+ hash.dataType = 'json';
3380
+ hash.contentType = 'application/json';
3381
+ hash.context = this;
3382
+
3383
+ if (hash.data && type !== 'GET') {
3384
+ hash.data = JSON.stringify(hash.data);
3385
+ }
3386
+
3387
+ jQuery.ajax(hash);
3388
+ },
3389
+
3390
+ sideload: function(store, type, json, root) {
3391
+ var sideloadedType, mappings;
3392
+
3393
+ for (var prop in json) {
3394
+ if (!json.hasOwnProperty(prop)) { continue; }
3395
+ if (prop === root) { continue; }
3396
+
3397
+ sideloadedType = type.typeForAssociation(prop);
3398
+
3399
+ if (!sideloadedType) {
3400
+ mappings = get(this, 'mappings');
3401
+
3402
+ ember_assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
3403
+
3404
+ sideloadedType = get(get(this, 'mappings'), prop);
3405
+
3406
+ ember_assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
3407
+ }
3408
+
3409
+ this.loadValue(store, sideloadedType, json[prop]);
3410
+ }
3411
+ },
3412
+
3413
+ loadValue: function(store, type, value) {
3414
+ if (value instanceof Array) {
3415
+ store.loadMany(type, value);
3416
+ } else {
3417
+ store.load(type, value);
3418
+ }
3419
+ },
3420
+
3421
+ buildURL: function(record, suffix) {
3422
+ var url = [""];
3423
+
3424
+ if (this.namespace !== undefined) {
3425
+ url.push(this.namespace);
3426
+ }
3427
+
3428
+ url.push(this.pluralize(record));
3429
+ if (suffix !== undefined) {
3430
+ url.push(suffix);
3431
+ }
3432
+
3433
+ return url.join("/");
3434
+ }
3435
+ });
3436
+
3437
+
3438
+ })();
3439
+
3440
+
3441
+
3442
+ (function() {
2719
3443
  //Copyright (C) 2011 by Living Social, Inc.
2720
3444
 
2721
3445
  //Permission is hereby granted, free of charge, to any person obtaining a copy of
@@ -2735,4 +3459,6 @@ DS.belongsTo = DS.hasOne;
2735
3459
  //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2736
3460
  //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2737
3461
  //SOFTWARE.
2738
- })({});
3462
+
3463
+ })();
3464
+