rasputin 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +17 -17
- data/README.md +4 -0
- data/lib/rasputin/version.rb +1 -1
- data/vendor/assets/javascripts/ember-data.js +2145 -856
- data/vendor/assets/javascripts/ember.js +8475 -6910
- metadata +16 -10
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rasputin (0.
|
4
|
+
rasputin (0.15.0)
|
5
5
|
actionpack (~> 3.1)
|
6
6
|
jquery-rails (>= 1.0)
|
7
7
|
railties (~> 3.1)
|
@@ -10,42 +10,42 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: http://rubygems.org/
|
12
12
|
specs:
|
13
|
-
actionpack (3.2.
|
14
|
-
activemodel (= 3.2.
|
15
|
-
activesupport (= 3.2.
|
13
|
+
actionpack (3.2.3)
|
14
|
+
activemodel (= 3.2.3)
|
15
|
+
activesupport (= 3.2.3)
|
16
16
|
builder (~> 3.0.0)
|
17
17
|
erubis (~> 2.7.0)
|
18
18
|
journey (~> 1.0.1)
|
19
19
|
rack (~> 1.4.0)
|
20
|
-
rack-cache (~> 1.
|
20
|
+
rack-cache (~> 1.2)
|
21
21
|
rack-test (~> 0.6.1)
|
22
22
|
sprockets (~> 2.1.2)
|
23
|
-
activemodel (3.2.
|
24
|
-
activesupport (= 3.2.
|
23
|
+
activemodel (3.2.3)
|
24
|
+
activesupport (= 3.2.3)
|
25
25
|
builder (~> 3.0.0)
|
26
|
-
activesupport (3.2.
|
26
|
+
activesupport (3.2.3)
|
27
27
|
i18n (~> 0.6)
|
28
28
|
multi_json (~> 1.0)
|
29
29
|
builder (3.0.0)
|
30
30
|
erubis (2.7.0)
|
31
31
|
hike (1.2.1)
|
32
32
|
i18n (0.6.0)
|
33
|
-
journey (1.0.
|
34
|
-
jquery-rails (2.0.
|
35
|
-
railties (>= 3.2.0
|
33
|
+
journey (1.0.3)
|
34
|
+
jquery-rails (2.0.2)
|
35
|
+
railties (>= 3.2.0, < 5.0)
|
36
36
|
thor (~> 0.14)
|
37
|
-
json (1.6.
|
38
|
-
multi_json (1.0
|
37
|
+
json (1.6.6)
|
38
|
+
multi_json (1.2.0)
|
39
39
|
rack (1.4.1)
|
40
|
-
rack-cache (1.
|
40
|
+
rack-cache (1.2)
|
41
41
|
rack (>= 0.4)
|
42
42
|
rack-ssl (1.3.2)
|
43
43
|
rack
|
44
44
|
rack-test (0.6.1)
|
45
45
|
rack (>= 1.0)
|
46
|
-
railties (3.2.
|
47
|
-
actionpack (= 3.2.
|
48
|
-
activesupport (= 3.2.
|
46
|
+
railties (3.2.3)
|
47
|
+
actionpack (= 3.2.3)
|
48
|
+
activesupport (= 3.2.3)
|
49
49
|
rack-ssl (~> 1.3.2)
|
50
50
|
rake (>= 0.8.7)
|
51
51
|
rdoc (~> 3.4)
|
data/README.md
CHANGED
data/lib/rasputin/version.rb
CHANGED
@@ -1,270 +1,39 @@
|
|
1
|
-
|
2
|
-
(
|
3
|
-
|
4
|
-
|
5
|
-
})({});
|
6
|
-
|
7
|
-
|
8
|
-
(function(exports) {
|
9
|
-
DS.Adapter = Ember.Object.extend({
|
10
|
-
commit: function(store, commitDetails) {
|
11
|
-
commitDetails.updated.eachType(function(type, array) {
|
12
|
-
this.updateRecords(store, type, array.slice());
|
13
|
-
}, this);
|
14
|
-
|
15
|
-
commitDetails.created.eachType(function(type, array) {
|
16
|
-
this.createRecords(store, type, array.slice());
|
17
|
-
}, this);
|
18
|
-
|
19
|
-
commitDetails.deleted.eachType(function(type, array) {
|
20
|
-
this.deleteRecords(store, type, array.slice());
|
21
|
-
}, this);
|
22
|
-
},
|
23
|
-
|
24
|
-
createRecords: function(store, type, models) {
|
25
|
-
models.forEach(function(model) {
|
26
|
-
this.createRecord(store, type, model);
|
27
|
-
}, this);
|
28
|
-
},
|
29
|
-
|
30
|
-
updateRecords: function(store, type, models) {
|
31
|
-
models.forEach(function(model) {
|
32
|
-
this.updateRecord(store, type, model);
|
33
|
-
}, this);
|
34
|
-
},
|
35
|
-
|
36
|
-
deleteRecords: function(store, type, models) {
|
37
|
-
models.forEach(function(model) {
|
38
|
-
this.deleteRecord(store, type, model);
|
39
|
-
}, this);
|
40
|
-
},
|
41
|
-
|
42
|
-
findMany: function(store, type, ids) {
|
43
|
-
ids.forEach(function(id) {
|
44
|
-
this.find(store, type, id);
|
45
|
-
}, this);
|
46
|
-
}
|
47
|
-
});
|
48
|
-
})({});
|
49
|
-
|
50
|
-
|
51
|
-
(function(exports) {
|
52
|
-
DS.fixtureAdapter = DS.Adapter.create({
|
53
|
-
find: function(store, type, id) {
|
54
|
-
var fixtures = type.FIXTURES;
|
55
|
-
|
56
|
-
ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
|
57
|
-
if (fixtures.hasLoaded) { return; }
|
58
|
-
|
59
|
-
setTimeout(function() {
|
60
|
-
store.loadMany(type, fixtures);
|
61
|
-
fixtures.hasLoaded = true;
|
62
|
-
}, 300);
|
63
|
-
},
|
64
|
-
|
65
|
-
findMany: function() {
|
66
|
-
this.find.apply(this, arguments);
|
67
|
-
}
|
1
|
+
(function() {
|
2
|
+
window.DS = Ember.Namespace.create({
|
3
|
+
CURRENT_API_REVISION: 4
|
68
4
|
});
|
69
5
|
|
70
|
-
})(
|
71
|
-
|
72
|
-
|
73
|
-
(function(exports) {
|
74
|
-
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
75
|
-
|
76
|
-
DS.RESTAdapter = DS.Adapter.extend({
|
77
|
-
createRecord: function(store, type, model) {
|
78
|
-
var root = this.rootForType(type);
|
79
|
-
|
80
|
-
var data = {};
|
81
|
-
data[root] = get(model, 'data');
|
82
|
-
|
83
|
-
this.ajax("/" + this.pluralize(root), "POST", {
|
84
|
-
data: data,
|
85
|
-
success: function(json) {
|
86
|
-
store.didCreateRecord(model, json[root]);
|
87
|
-
}
|
88
|
-
});
|
89
|
-
},
|
90
|
-
|
91
|
-
createRecords: function(store, type, models) {
|
92
|
-
if (get(this, 'bulkCommit') === false) {
|
93
|
-
return this._super(store, type, models);
|
94
|
-
}
|
95
|
-
|
96
|
-
var root = this.rootForType(type),
|
97
|
-
plural = this.pluralize(root);
|
98
|
-
|
99
|
-
var data = {};
|
100
|
-
data[plural] = models.map(function(model) {
|
101
|
-
return get(model, 'data');
|
102
|
-
});
|
103
|
-
|
104
|
-
this.ajax("/" + this.pluralize(root), "POST", {
|
105
|
-
data: data,
|
106
|
-
success: function(json) {
|
107
|
-
store.didCreateRecords(type, models, json[plural]);
|
108
|
-
}
|
109
|
-
});
|
110
|
-
},
|
111
|
-
|
112
|
-
updateRecord: function(store, type, model) {
|
113
|
-
var id = get(model, 'id');
|
114
|
-
var root = this.rootForType(type);
|
115
|
-
|
116
|
-
var data = {};
|
117
|
-
data[root] = get(model, 'data');
|
118
|
-
|
119
|
-
var url = ["", this.pluralize(root), id].join("/");
|
120
|
-
|
121
|
-
this.ajax(url, "PUT", {
|
122
|
-
data: data,
|
123
|
-
success: function(json) {
|
124
|
-
store.didUpdateRecord(model, json[root]);
|
125
|
-
}
|
126
|
-
});
|
127
|
-
},
|
128
|
-
|
129
|
-
updateRecords: function(store, type, models) {
|
130
|
-
if (get(this, 'bulkCommit') === false) {
|
131
|
-
return this._super(store, type, models);
|
132
|
-
}
|
133
|
-
|
134
|
-
var root = this.rootForType(type),
|
135
|
-
plural = this.pluralize(root);
|
136
|
-
|
137
|
-
var data = {};
|
138
|
-
data[plural] = models.map(function(model) {
|
139
|
-
return get(model, 'data');
|
140
|
-
});
|
141
|
-
|
142
|
-
this.ajax("/" + this.pluralize(root), "POST", {
|
143
|
-
data: data,
|
144
|
-
success: function(json) {
|
145
|
-
store.didUpdateRecords(models, json[plural]);
|
146
|
-
}
|
147
|
-
});
|
148
|
-
},
|
149
|
-
|
150
|
-
deleteRecord: function(store, type, model) {
|
151
|
-
var id = get(model, 'id');
|
152
|
-
var root = this.rootForType(type);
|
153
|
-
|
154
|
-
var url = ["", this.pluralize(root), id].join("/");
|
155
|
-
|
156
|
-
this.ajax(url, "DELETE", {
|
157
|
-
success: function(json) {
|
158
|
-
store.didDeleteRecord(model);
|
159
|
-
}
|
160
|
-
});
|
161
|
-
},
|
162
|
-
|
163
|
-
deleteRecords: function(store, type, models) {
|
164
|
-
if (get(this, 'bulkCommit') === false) {
|
165
|
-
return this._super(store, type, models);
|
166
|
-
}
|
167
|
-
|
168
|
-
var root = this.rootForType(type),
|
169
|
-
plural = this.pluralize(root),
|
170
|
-
primaryKey = getPath(type, 'proto.primaryKey');
|
171
|
-
|
172
|
-
var data = {};
|
173
|
-
data[plural] = models.map(function(model) {
|
174
|
-
return get(model, primaryKey);
|
175
|
-
});
|
176
|
-
|
177
|
-
this.ajax("/" + this.pluralize(root) + "/delete", "POST", {
|
178
|
-
data: data,
|
179
|
-
success: function(json) {
|
180
|
-
store.didDeleteRecords(models);
|
181
|
-
}
|
182
|
-
});
|
183
|
-
},
|
184
|
-
|
185
|
-
find: function(store, type, id) {
|
186
|
-
var root = this.rootForType(type);
|
187
|
-
|
188
|
-
var url = ["", this.pluralize(root), id].join("/");
|
189
|
-
|
190
|
-
this.ajax(url, "GET", {
|
191
|
-
success: function(json) {
|
192
|
-
store.load(type, json[root]);
|
193
|
-
}
|
194
|
-
});
|
195
|
-
},
|
196
|
-
|
197
|
-
findMany: function(store, type, ids) {
|
198
|
-
var root = this.rootForType(type), plural = this.pluralize(root);
|
199
|
-
|
200
|
-
this.ajax("/" + plural, "GET", {
|
201
|
-
data: { ids: ids },
|
202
|
-
success: function(json) {
|
203
|
-
store.loadMany(type, ids, json[plural]);
|
204
|
-
}
|
205
|
-
});
|
206
|
-
var url = "/" + plural;
|
207
|
-
},
|
208
|
-
|
209
|
-
findAll: function(store, type) {
|
210
|
-
var root = this.rootForType(type), plural = this.pluralize(root);
|
211
|
-
|
212
|
-
this.ajax("/" + plural, "GET", {
|
213
|
-
success: function(json) {
|
214
|
-
store.loadMany(type, json[plural]);
|
215
|
-
}
|
216
|
-
});
|
217
|
-
},
|
218
|
-
|
219
|
-
findQuery: function(store, type, query, modelArray) {
|
220
|
-
var root = this.rootForType(type), plural = this.pluralize(root);
|
221
|
-
|
222
|
-
this.ajax("/" + plural, "GET", {
|
223
|
-
data: query,
|
224
|
-
success: function(json) {
|
225
|
-
modelArray.load(json[plural]);
|
226
|
-
}
|
227
|
-
});
|
228
|
-
},
|
229
|
-
|
230
|
-
// HELPERS
|
231
|
-
|
232
|
-
plurals: {},
|
233
|
-
|
234
|
-
// define a plurals hash in your subclass to define
|
235
|
-
// special-case pluralization
|
236
|
-
pluralize: function(name) {
|
237
|
-
return this.plurals[name] || name + "s";
|
238
|
-
},
|
239
|
-
|
240
|
-
rootForType: function(type) {
|
241
|
-
if (type.url) { return type.url; }
|
6
|
+
})();
|
242
7
|
|
243
|
-
// use the last part of the name as the URL
|
244
|
-
var parts = type.toString().split(".");
|
245
|
-
var name = parts[parts.length - 1];
|
246
|
-
return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
|
247
|
-
},
|
248
|
-
|
249
|
-
ajax: function(url, type, hash) {
|
250
|
-
hash.url = url;
|
251
|
-
hash.type = type;
|
252
|
-
hash.dataType = "json";
|
253
8
|
|
254
|
-
jQuery.ajax(hash);
|
255
|
-
}
|
256
|
-
});
|
257
9
|
|
10
|
+
(function() {
|
11
|
+
var get = Ember.get, set = Ember.set;
|
258
12
|
|
259
|
-
|
13
|
+
/**
|
14
|
+
A model array is an array that contains records of a certain type. The model
|
15
|
+
array materializes records as needed when they are retrieved for the first
|
16
|
+
time. You should not create model arrays yourself. Instead, an instance of
|
17
|
+
DS.ModelArray or its subclasses will be returned by your application's store
|
18
|
+
in response to queries.
|
19
|
+
*/
|
260
20
|
|
21
|
+
DS.ModelArray = Ember.ArrayProxy.extend({
|
261
22
|
|
262
|
-
|
263
|
-
|
23
|
+
/**
|
24
|
+
The model type contained by this model array.
|
264
25
|
|
265
|
-
DS.
|
26
|
+
@type DS.Model
|
27
|
+
*/
|
266
28
|
type: null,
|
29
|
+
|
30
|
+
// The array of client ids backing the model array. When a
|
31
|
+
// record is requested from the model array, the record
|
32
|
+
// for the client id at the same index is materialized, if
|
33
|
+
// necessary, by the store.
|
267
34
|
content: null,
|
35
|
+
|
36
|
+
// The store that created this model array.
|
268
37
|
store: null,
|
269
38
|
|
270
39
|
init: function() {
|
@@ -274,7 +43,7 @@ DS.ModelArray = Ember.ArrayProxy.extend({
|
|
274
43
|
|
275
44
|
arrayDidChange: function(array, index, removed, added) {
|
276
45
|
var modelCache = get(this, 'modelCache');
|
277
|
-
modelCache.replace(index, 0, Array(added));
|
46
|
+
modelCache.replace(index, 0, new Array(added));
|
278
47
|
|
279
48
|
this._super(array, index, removed, added);
|
280
49
|
},
|
@@ -306,19 +75,43 @@ DS.ModelArray = Ember.ArrayProxy.extend({
|
|
306
75
|
}
|
307
76
|
});
|
308
77
|
|
78
|
+
})();
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
(function() {
|
83
|
+
var get = Ember.get;
|
84
|
+
|
309
85
|
DS.FilteredModelArray = DS.ModelArray.extend({
|
310
86
|
filterFunction: null,
|
311
87
|
|
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.");
|
91
|
+
},
|
92
|
+
|
312
93
|
updateFilter: Ember.observer(function() {
|
313
94
|
var store = get(this, 'store');
|
314
95
|
store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
|
315
96
|
}, 'filterFunction')
|
316
97
|
});
|
317
98
|
|
99
|
+
})();
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
(function() {
|
104
|
+
var get = Ember.get, set = Ember.set;
|
105
|
+
|
318
106
|
DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
319
107
|
query: null,
|
320
108
|
isLoaded: false,
|
321
109
|
|
110
|
+
replace: function() {
|
111
|
+
var type = get(this, 'type').toString();
|
112
|
+
throw new Error("The result of a server query (on " + type + ") is immutable.");
|
113
|
+
},
|
114
|
+
|
322
115
|
load: function(array) {
|
323
116
|
var store = get(this, 'store'), type = get(this, 'type');
|
324
117
|
|
@@ -331,125 +124,225 @@ DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
|
331
124
|
}
|
332
125
|
});
|
333
126
|
|
334
|
-
})({});
|
335
127
|
|
128
|
+
})();
|
336
129
|
|
337
|
-
(function(exports) {
|
338
|
-
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
339
130
|
|
340
|
-
var OrderedSet = Ember.Object.extend({
|
341
|
-
init: function() {
|
342
|
-
this.clear();
|
343
|
-
},
|
344
131
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
132
|
+
(function() {
|
133
|
+
var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor;
|
134
|
+
|
135
|
+
var Set = function() {
|
136
|
+
this.hash = {};
|
137
|
+
this.list = [];
|
138
|
+
};
|
349
139
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
140
|
+
Set.prototype = {
|
141
|
+
add: function(item) {
|
142
|
+
var hash = this.hash,
|
143
|
+
guid = guidFor(item);
|
354
144
|
|
355
|
-
if (guid
|
145
|
+
if (hash.hasOwnProperty(guid)) { return; }
|
356
146
|
|
357
|
-
|
358
|
-
list.
|
147
|
+
hash[guid] = true;
|
148
|
+
this.list.push(item);
|
359
149
|
},
|
360
150
|
|
361
|
-
remove: function(
|
362
|
-
var
|
363
|
-
|
364
|
-
list = get(this, 'list');
|
151
|
+
remove: function(item) {
|
152
|
+
var hash = this.hash,
|
153
|
+
guid = guidFor(item);
|
365
154
|
|
366
|
-
|
367
|
-
list.removeObject(obj);
|
368
|
-
},
|
155
|
+
if (!hash.hasOwnProperty(guid)) { return; }
|
369
156
|
|
370
|
-
|
371
|
-
|
372
|
-
|
157
|
+
delete hash[guid];
|
158
|
+
var list = this.list,
|
159
|
+
index = Ember.ArrayUtils.indexOf(this, item);
|
373
160
|
|
374
|
-
|
375
|
-
get(this, 'list').forEach(function(item) {
|
376
|
-
fn.call(self, item);
|
377
|
-
});
|
161
|
+
list.splice(index, 1);
|
378
162
|
},
|
379
163
|
|
380
|
-
|
381
|
-
return
|
164
|
+
isEmpty: function() {
|
165
|
+
return this.list.length === 0;
|
382
166
|
}
|
383
|
-
}
|
167
|
+
};
|
384
168
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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);
|
178
|
+
}
|
179
|
+
};
|
389
180
|
|
390
|
-
|
181
|
+
record.addObserver('isDirty', observer);
|
182
|
+
},
|
391
183
|
|
392
|
-
|
393
|
-
|
394
|
-
|
184
|
+
recordWasRemoved: function(manager, record) {
|
185
|
+
var dirty = manager.dirty, observer;
|
186
|
+
dirty.add(record);
|
395
187
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
188
|
+
observer = function() {
|
189
|
+
record.removeObserver('isDirty', observer);
|
190
|
+
if (!get(record, 'isDirty')) { manager.send('childWasSaved', record); }
|
191
|
+
};
|
192
|
+
|
193
|
+
record.addObserver('isDirty', observer);
|
194
|
+
}
|
195
|
+
});
|
196
|
+
|
197
|
+
var states = {
|
198
|
+
clean: ManyArrayState.create({
|
199
|
+
isDirty: false,
|
200
|
+
|
201
|
+
recordWasAdded: function(manager, record) {
|
202
|
+
this._super(manager, record);
|
203
|
+
manager.goToState('dirty');
|
204
|
+
},
|
205
|
+
|
206
|
+
update: function(manager, clientIds) {
|
207
|
+
var manyArray = manager.manyArray;
|
208
|
+
set(manyArray, 'content', clientIds);
|
209
|
+
}
|
210
|
+
}),
|
211
|
+
|
212
|
+
dirty: ManyArrayState.create({
|
213
|
+
isDirty: true,
|
214
|
+
|
215
|
+
childWasSaved: function(manager, child) {
|
216
|
+
var dirty = manager.dirty;
|
217
|
+
dirty.remove(child);
|
218
|
+
|
219
|
+
if (dirty.isEmpty()) { manager.send('arrayBecameSaved'); }
|
220
|
+
},
|
221
|
+
|
222
|
+
arrayBecameSaved: function(manager) {
|
223
|
+
manager.goToState('clean');
|
224
|
+
}
|
225
|
+
})
|
226
|
+
};
|
227
|
+
|
228
|
+
DS.ManyArrayStateManager = Ember.StateManager.extend({
|
229
|
+
manyArray: null,
|
230
|
+
initialState: 'clean',
|
231
|
+
states: states,
|
401
232
|
|
402
|
-
var Hash = Ember.Object.extend({
|
403
233
|
init: function() {
|
404
|
-
|
405
|
-
|
406
|
-
}
|
234
|
+
this._super();
|
235
|
+
this.dirty = new Set();
|
236
|
+
}
|
237
|
+
});
|
407
238
|
|
408
|
-
|
409
|
-
var keys = get(this, 'keys'), values = get(this, 'values');
|
410
|
-
var guid = Ember.guidFor(key);
|
239
|
+
})();
|
411
240
|
|
412
|
-
keys.add(key);
|
413
|
-
values[guid] = value;
|
414
241
|
|
415
|
-
|
242
|
+
|
243
|
+
(function() {
|
244
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
245
|
+
|
246
|
+
DS.ManyArray = DS.ModelArray.extend({
|
247
|
+
init: function() {
|
248
|
+
set(this, 'stateManager', DS.ManyArrayStateManager.create({ manyArray: this }));
|
249
|
+
|
250
|
+
return this._super();
|
416
251
|
},
|
417
252
|
|
418
|
-
|
419
|
-
var keys = get(this, 'keys'), values = get(this, 'values');
|
420
|
-
var guid = Ember.guidFor(key), value;
|
253
|
+
parentRecord: null,
|
421
254
|
|
422
|
-
|
255
|
+
isDirty: Ember.computed(function() {
|
256
|
+
return getPath(this, 'stateManager.currentState.isDirty');
|
257
|
+
}).property('stateManager.currentState').cacheable(),
|
423
258
|
|
424
|
-
|
425
|
-
|
259
|
+
fetch: function() {
|
260
|
+
var clientIds = get(this, 'content'),
|
261
|
+
store = get(this, 'store'),
|
262
|
+
type = get(this, 'type');
|
426
263
|
|
427
|
-
|
264
|
+
var ids = clientIds.map(function(clientId) {
|
265
|
+
return store.clientIdToId[clientId];
|
266
|
+
});
|
267
|
+
|
268
|
+
store.fetchMany(type, ids);
|
428
269
|
},
|
429
270
|
|
430
|
-
|
431
|
-
|
432
|
-
var
|
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');
|
276
|
+
|
277
|
+
added = added.map(function(record) {
|
278
|
+
|
279
|
+
|
280
|
+
if (pendingParent) {
|
281
|
+
record.send('waitingOn', parentRecord);
|
282
|
+
}
|
283
|
+
|
284
|
+
this.assignInverse(record, parentRecord);
|
285
|
+
|
286
|
+
stateManager.send('recordWasAdded', record);
|
287
|
+
|
288
|
+
return record.get('clientId');
|
289
|
+
}, this);
|
290
|
+
|
291
|
+
var store = this.store;
|
292
|
+
|
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
|
+
}
|
433
300
|
|
434
|
-
|
301
|
+
this._super(index, removed, added);
|
435
302
|
},
|
436
303
|
|
437
|
-
|
438
|
-
var
|
304
|
+
assignInverse: function(record, parentRecord, remove) {
|
305
|
+
var associationMap = get(record.constructor, 'associations'),
|
306
|
+
possibleAssociations = associationMap.get(parentRecord.constructor),
|
307
|
+
possible, actual;
|
439
308
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
309
|
+
if (!possibleAssociations) { return; }
|
310
|
+
|
311
|
+
for (var i = 0, l = possibleAssociations.length; i < l; i++) {
|
312
|
+
possible = possibleAssociations[i];
|
313
|
+
|
314
|
+
if (possible.kind === 'belongsTo') {
|
315
|
+
actual = possible;
|
316
|
+
break;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
if (actual) {
|
321
|
+
set(record, actual.name, remove ? null : parentRecord);
|
322
|
+
}
|
444
323
|
}
|
445
324
|
});
|
446
325
|
|
326
|
+
})();
|
327
|
+
|
328
|
+
|
329
|
+
|
330
|
+
(function() {
|
331
|
+
|
332
|
+
})();
|
333
|
+
|
334
|
+
|
335
|
+
|
336
|
+
(function() {
|
337
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
338
|
+
|
447
339
|
DS.Transaction = Ember.Object.extend({
|
448
340
|
init: function() {
|
449
|
-
set(this, '
|
450
|
-
|
451
|
-
|
452
|
-
|
341
|
+
set(this, 'buckets', {
|
342
|
+
clean: Ember.Map.create(),
|
343
|
+
created: Ember.Map.create(),
|
344
|
+
updated: Ember.Map.create(),
|
345
|
+
deleted: Ember.Map.create()
|
453
346
|
});
|
454
347
|
},
|
455
348
|
|
@@ -459,44 +352,102 @@ DS.Transaction = Ember.Object.extend({
|
|
459
352
|
return store.createRecord(type, hash, this);
|
460
353
|
},
|
461
354
|
|
462
|
-
add: function(
|
463
|
-
|
464
|
-
|
355
|
+
add: function(record) {
|
356
|
+
// we could probably make this work if someone has a valid use case. Do you?
|
357
|
+
|
358
|
+
|
359
|
+
var modelTransaction = get(record, 'transaction'),
|
360
|
+
defaultTransaction = getPath(this, 'store.defaultTransaction');
|
361
|
+
|
362
|
+
|
363
|
+
this.adoptRecord(record);
|
364
|
+
},
|
365
|
+
|
366
|
+
remove: function(record) {
|
367
|
+
var defaultTransaction = getPath(this, 'store.defaultTransaction');
|
368
|
+
|
369
|
+
defaultTransaction.adoptRecord(record);
|
370
|
+
},
|
371
|
+
|
372
|
+
/**
|
373
|
+
@private
|
374
|
+
|
375
|
+
This method moves a record into a different transaction without the normal
|
376
|
+
checks that ensure that the user is not doing something weird, like moving
|
377
|
+
a dirty record into a new transaction.
|
378
|
+
|
379
|
+
It is designed for internal use, such as when we are moving a clean record
|
380
|
+
into a new transaction when the transaction is committed.
|
465
381
|
|
466
|
-
|
382
|
+
This method must not be called unless the record is clean.
|
383
|
+
*/
|
384
|
+
adoptRecord: function(record) {
|
385
|
+
var oldTransaction = get(record, 'transaction');
|
386
|
+
|
387
|
+
if (oldTransaction) {
|
388
|
+
oldTransaction.removeFromBucket('clean', record);
|
389
|
+
}
|
390
|
+
|
391
|
+
this.addToBucket('clean', record);
|
392
|
+
set(record, 'transaction', this);
|
467
393
|
},
|
468
394
|
|
469
|
-
modelBecameDirty: function(kind,
|
470
|
-
|
471
|
-
|
395
|
+
modelBecameDirty: function(kind, record) {
|
396
|
+
this.removeFromBucket('clean', record);
|
397
|
+
this.addToBucket(kind, record);
|
398
|
+
},
|
399
|
+
|
400
|
+
/** @private */
|
401
|
+
addToBucket: function(kind, record) {
|
402
|
+
var bucket = get(get(this, 'buckets'), kind),
|
403
|
+
type = record.constructor;
|
472
404
|
|
473
|
-
var
|
405
|
+
var records = bucket.get(type);
|
406
|
+
|
407
|
+
if (!records) {
|
408
|
+
records = Ember.OrderedSet.create();
|
409
|
+
bucket.set(type, records);
|
410
|
+
}
|
474
411
|
|
475
|
-
|
476
|
-
models.add(model);
|
412
|
+
records.add(record);
|
477
413
|
},
|
478
414
|
|
479
|
-
|
480
|
-
|
481
|
-
|
415
|
+
/** @private */
|
416
|
+
removeFromBucket: function(kind, record) {
|
417
|
+
var bucket = get(get(this, 'buckets'), kind),
|
418
|
+
type = record.constructor;
|
482
419
|
|
483
|
-
var
|
484
|
-
|
420
|
+
var records = bucket.get(type);
|
421
|
+
records.remove(record);
|
422
|
+
},
|
423
|
+
|
424
|
+
modelBecameClean: function(kind, record) {
|
425
|
+
this.removeFromBucket(kind, record);
|
485
426
|
|
486
|
-
|
427
|
+
var defaultTransaction = getPath(this, 'store.defaultTransaction');
|
428
|
+
defaultTransaction.adoptRecord(record);
|
487
429
|
},
|
488
430
|
|
489
431
|
commit: function() {
|
490
|
-
var
|
432
|
+
var buckets = get(this, 'buckets');
|
491
433
|
|
492
434
|
var iterate = function(kind, fn, binding) {
|
493
|
-
var dirty = get(
|
435
|
+
var dirty = get(buckets, kind);
|
494
436
|
|
495
437
|
dirty.forEach(function(type, models) {
|
496
438
|
if (models.isEmpty()) { return; }
|
497
439
|
|
498
|
-
|
499
|
-
|
440
|
+
var array = [];
|
441
|
+
|
442
|
+
models.forEach(function(model) {
|
443
|
+
model.send('willCommit');
|
444
|
+
|
445
|
+
if (get(model, 'isPending') === false) {
|
446
|
+
array.push(model);
|
447
|
+
}
|
448
|
+
});
|
449
|
+
|
450
|
+
fn.call(binding, type, array);
|
500
451
|
});
|
501
452
|
};
|
502
453
|
|
@@ -516,57 +467,39 @@ DS.Transaction = Ember.Object.extend({
|
|
516
467
|
|
517
468
|
var store = get(this, 'store');
|
518
469
|
var adapter = get(store, '_adapter');
|
470
|
+
|
471
|
+
var clean = get(buckets, 'clean');
|
472
|
+
var defaultTransaction = get(store, 'defaultTransaction');
|
473
|
+
|
474
|
+
clean.forEach(function(type, records) {
|
475
|
+
records.forEach(function(record) {
|
476
|
+
this.remove(record);
|
477
|
+
}, this);
|
478
|
+
}, this);
|
479
|
+
|
519
480
|
if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
|
520
481
|
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
|
521
482
|
}
|
522
483
|
});
|
523
484
|
|
524
|
-
})(
|
525
|
-
|
526
|
-
|
527
|
-
(function(exports) {
|
528
|
-
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
529
|
-
|
530
|
-
var OrderedSet = Ember.Object.extend({
|
531
|
-
init: function() {
|
532
|
-
this.clear();
|
533
|
-
},
|
534
|
-
|
535
|
-
clear: function() {
|
536
|
-
this.set('presenceSet', {});
|
537
|
-
this.set('list', Ember.NativeArray.apply([]));
|
538
|
-
},
|
539
|
-
|
540
|
-
add: function(obj) {
|
541
|
-
var guid = Ember.guidFor(obj),
|
542
|
-
presenceSet = get(this, 'presenceSet'),
|
543
|
-
list = get(this, 'list');
|
485
|
+
})();
|
544
486
|
|
545
|
-
if (guid in presenceSet) { return; }
|
546
487
|
|
547
|
-
presenceSet[guid] = true;
|
548
|
-
list.pushObject(obj);
|
549
|
-
},
|
550
|
-
|
551
|
-
remove: function(obj) {
|
552
|
-
var guid = Ember.guidFor(obj),
|
553
|
-
presenceSet = get(this, 'presenceSet'),
|
554
|
-
list = get(this, 'list');
|
555
|
-
|
556
|
-
delete presenceSet[guid];
|
557
|
-
list.removeObject(obj);
|
558
|
-
},
|
559
488
|
|
560
|
-
|
561
|
-
|
562
|
-
},
|
489
|
+
(function() {
|
490
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
563
491
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
});
|
492
|
+
var DATA_PROXY = {
|
493
|
+
get: function(name) {
|
494
|
+
return this.savedData[name];
|
568
495
|
}
|
569
|
-
}
|
496
|
+
};
|
497
|
+
|
498
|
+
// These values are used in the data cache when clientIds are
|
499
|
+
// needed but the underlying data has not yet been loaded by
|
500
|
+
// the server.
|
501
|
+
var UNLOADED = 'unloaded';
|
502
|
+
var LOADING = 'loading';
|
570
503
|
|
571
504
|
// Implementors Note:
|
572
505
|
//
|
@@ -615,33 +548,49 @@ DS.Store = Ember.Object.extend({
|
|
615
548
|
The init method registers this store as the default if none is specified.
|
616
549
|
*/
|
617
550
|
init: function() {
|
551
|
+
// Enforce API revisioning. See BREAKING_CHANGES.md for more.
|
552
|
+
var revision = get(this, 'revision');
|
553
|
+
|
554
|
+
if (revision !== DS.CURRENT_API_REVISION && !Ember.ENV.TESTING) {
|
555
|
+
throw new Error("Error: The Ember Data library has had breaking API changes since the last time you updated the library. Please review the list of breaking changes at https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md, then update your store's `revision` property to " + DS.CURRENT_API_REVISION);
|
556
|
+
}
|
557
|
+
|
618
558
|
if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
|
619
559
|
set(DS, 'defaultStore', this);
|
620
560
|
}
|
621
561
|
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
562
|
+
// internal bookkeeping; not observable
|
563
|
+
this.typeMaps = {};
|
564
|
+
this.recordCache = [];
|
565
|
+
this.clientIdToId = {};
|
566
|
+
this.modelArraysByClientId = {};
|
567
|
+
|
568
|
+
set(this, 'defaultTransaction', this.transaction());
|
628
569
|
|
629
570
|
return this._super();
|
630
571
|
},
|
631
572
|
|
573
|
+
/**
|
574
|
+
Returns a new transaction scoped to this store.
|
575
|
+
|
576
|
+
@see {DS.Transaction}
|
577
|
+
@returns DS.Transaction
|
578
|
+
*/
|
632
579
|
transaction: function() {
|
633
580
|
return DS.Transaction.create({ store: this });
|
634
581
|
},
|
635
582
|
|
636
|
-
|
637
|
-
|
638
|
-
var ret = modelArrays[clientId];
|
583
|
+
/**
|
584
|
+
@private
|
639
585
|
|
640
|
-
|
641
|
-
|
642
|
-
|
586
|
+
This is used only by the model's DataProxy. Do not use this directly.
|
587
|
+
*/
|
588
|
+
dataForRecord: function(record) {
|
589
|
+
var type = record.constructor,
|
590
|
+
clientId = get(record, 'clientId'),
|
591
|
+
typeMap = this.typeMapFor(type);
|
643
592
|
|
644
|
-
return
|
593
|
+
return typeMap.cidToHash[clientId];
|
645
594
|
},
|
646
595
|
|
647
596
|
/**
|
@@ -654,53 +603,95 @@ DS.Store = Ember.Object.extend({
|
|
654
603
|
*/
|
655
604
|
adapter: null,
|
656
605
|
|
606
|
+
/**
|
607
|
+
@private
|
608
|
+
|
609
|
+
This property returns the adapter, after resolving a possible String.
|
610
|
+
|
611
|
+
@returns DS.Adapter
|
612
|
+
*/
|
657
613
|
_adapter: Ember.computed(function() {
|
658
614
|
var adapter = get(this, 'adapter');
|
659
615
|
if (typeof adapter === 'string') {
|
660
|
-
return getPath(this, adapter);
|
616
|
+
return getPath(this, adapter, false) || getPath(window, adapter);
|
661
617
|
}
|
662
618
|
return adapter;
|
663
619
|
}).property('adapter').cacheable(),
|
664
620
|
|
665
|
-
|
621
|
+
// A monotonically increasing number to be used to uniquely identify
|
622
|
+
// data hashes and records.
|
623
|
+
clientIdCounter: 1,
|
666
624
|
|
667
625
|
// ....................
|
668
626
|
// . CREATE NEW MODEL .
|
669
627
|
// ....................
|
670
628
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
var id = hash[getPath(type, 'proto.primaryKey')] || null;
|
629
|
+
/**
|
630
|
+
Create a new record in the current store. The properties passed
|
631
|
+
to this method are set on the newly created record.
|
675
632
|
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
633
|
+
@param {subclass of DS.Model} type
|
634
|
+
@param {Object} properties a hash of properties to set on the
|
635
|
+
newly created record.
|
636
|
+
@returns DS.Model
|
637
|
+
*/
|
638
|
+
createRecord: function(type, properties, transaction) {
|
639
|
+
properties = properties || {};
|
640
|
+
|
641
|
+
// Create a new instance of the model `type` and put it
|
642
|
+
// into the specified `transaction`. If no transaction is
|
643
|
+
// specified, the default transaction will be used.
|
644
|
+
//
|
645
|
+
// NOTE: A `transaction` is specified when the
|
646
|
+
// `transaction.createRecord` API is used.
|
647
|
+
var record = type._create({
|
648
|
+
store: this
|
680
649
|
});
|
681
650
|
|
682
|
-
|
651
|
+
transaction = transaction || get(this, 'defaultTransaction');
|
652
|
+
transaction.adoptRecord(record);
|
683
653
|
|
684
|
-
|
685
|
-
|
654
|
+
// Extract the primary key from the `properties` hash,
|
655
|
+
// based on the `primaryKey` for the model type.
|
656
|
+
var id = properties[get(record, 'primaryKey')] || null;
|
686
657
|
|
687
|
-
var
|
658
|
+
var hash = {}, clientId;
|
688
659
|
|
689
|
-
|
660
|
+
// Push the hash into the store. If present, associate the
|
661
|
+
// extracted `id` with the hash.
|
662
|
+
clientId = this.pushHash(hash, id, type);
|
690
663
|
|
691
|
-
|
664
|
+
record.send('didChangeData');
|
692
665
|
|
693
|
-
this
|
666
|
+
var recordCache = get(this, 'recordCache');
|
694
667
|
|
695
|
-
|
668
|
+
// Now that we have a clientId, attach it to the record we
|
669
|
+
// just created.
|
670
|
+
set(record, 'clientId', clientId);
|
671
|
+
|
672
|
+
// Store the record we just created in the record cache for
|
673
|
+
// this clientId.
|
674
|
+
recordCache[clientId] = record;
|
675
|
+
|
676
|
+
// Set the properties specified on the record.
|
677
|
+
record.setProperties(properties);
|
678
|
+
|
679
|
+
this.updateModelArrays(type, clientId, get(record, 'data'));
|
680
|
+
|
681
|
+
return record;
|
696
682
|
},
|
697
683
|
|
698
684
|
// ................
|
699
685
|
// . DELETE MODEL .
|
700
686
|
// ................
|
701
687
|
|
702
|
-
|
703
|
-
|
688
|
+
/**
|
689
|
+
For symmetry, a record can be deleted via the store.
|
690
|
+
|
691
|
+
@param {DS.Model} record
|
692
|
+
*/
|
693
|
+
deleteRecord: function(record) {
|
694
|
+
record.send('deleteRecord');
|
704
695
|
},
|
705
696
|
|
706
697
|
// ...............
|
@@ -708,26 +699,64 @@ DS.Store = Ember.Object.extend({
|
|
708
699
|
// ...............
|
709
700
|
|
710
701
|
/**
|
711
|
-
|
712
|
-
|
713
|
-
immediately. Otherwise, an empty DS.Model instance will be returned in
|
714
|
-
the loading state. As soon as the requested data is available, the model
|
715
|
-
will be moved into the loaded state and all of the information will be
|
716
|
-
available.
|
702
|
+
This is the main entry point into finding records. The first
|
703
|
+
parameter to this method is always a subclass of `DS.Model`.
|
717
704
|
|
718
|
-
|
719
|
-
|
705
|
+
You can use the `find` method on a subclass of `DS.Model`
|
706
|
+
directly if your application only has one store. For
|
707
|
+
example, instead of `store.find(App.Person, 1)`, you could
|
708
|
+
say `App.Person.find(1)`.
|
720
709
|
|
721
|
-
|
710
|
+
---
|
722
711
|
|
723
|
-
|
712
|
+
To find a record by ID, pass the `id` as the second parameter:
|
724
713
|
|
725
|
-
|
726
|
-
|
714
|
+
store.find(App.Person, 1);
|
715
|
+
App.Person.find(1);
|
716
|
+
|
717
|
+
If the record with that `id` had not previously been loaded,
|
718
|
+
the store will return an empty record immediately and ask
|
719
|
+
the adapter to find the data by calling its `find` method.
|
720
|
+
|
721
|
+
The `find` method will always return the same object for a
|
722
|
+
given type and `id`. To check whether the adapter has populated
|
723
|
+
a record, you can check its `isLoaded` property.
|
724
|
+
|
725
|
+
---
|
726
|
+
|
727
|
+
To find all records for a type, call `find` with no additional
|
728
|
+
parameters:
|
729
|
+
|
730
|
+
store.find(App.Person);
|
731
|
+
App.Person.find();
|
732
|
+
|
733
|
+
This will return a `ModelArray` representing all known records
|
734
|
+
for the given type and kick off a request to the adapter's
|
735
|
+
`findAll` method to load any additional records for the type.
|
736
|
+
|
737
|
+
The `ModelArray` returned by `find()` is live. If any more
|
738
|
+
records for the type are added at a later time through any
|
739
|
+
mechanism, it will automatically update to reflect the change.
|
740
|
+
|
741
|
+
---
|
742
|
+
|
743
|
+
To find a record by a query, call `find` with a hash as the
|
744
|
+
second parameter:
|
745
|
+
|
746
|
+
store.find(App.Person, { page: 1 });
|
747
|
+
App.Person.find({ page: 1 });
|
748
|
+
|
749
|
+
This will return a `ModelArray` immediately, but it will always
|
750
|
+
be an empty `ModelArray` at first. It will call the adapter's
|
751
|
+
`findQuery` method, which will populate the `ModelArray` once
|
752
|
+
the server has returned results.
|
753
|
+
|
754
|
+
You can check whether a query results `ModelArray` has loaded
|
755
|
+
by checking its `isLoaded` property.
|
727
756
|
*/
|
728
757
|
find: function(type, id, query) {
|
729
758
|
if (id === undefined) {
|
730
|
-
return this.
|
759
|
+
return this.findAll(type);
|
731
760
|
}
|
732
761
|
|
733
762
|
if (query !== undefined) {
|
@@ -740,16 +769,15 @@ DS.Store = Ember.Object.extend({
|
|
740
769
|
return this.findMany(type, id);
|
741
770
|
}
|
742
771
|
|
743
|
-
var clientId = this.
|
772
|
+
var clientId = this.typeMapFor(type).idToCid[id];
|
744
773
|
|
745
774
|
return this.findByClientId(type, clientId, id);
|
746
775
|
},
|
747
776
|
|
748
777
|
findByClientId: function(type, clientId, id) {
|
749
|
-
var
|
750
|
-
|
751
|
-
|
752
|
-
var data = this.clientIdToHashMap(type);
|
778
|
+
var recordCache = get(this, 'recordCache'),
|
779
|
+
dataCache = this.typeMapFor(type).cidToHash,
|
780
|
+
model;
|
753
781
|
|
754
782
|
// If there is already a clientId assigned for this
|
755
783
|
// type/id combination, try to find an existing
|
@@ -757,37 +785,55 @@ DS.Store = Ember.Object.extend({
|
|
757
785
|
// materialize a new model and set its data to the
|
758
786
|
// value we already have.
|
759
787
|
if (clientId !== undefined) {
|
760
|
-
model =
|
788
|
+
model = recordCache[clientId];
|
761
789
|
|
762
790
|
if (!model) {
|
763
791
|
// create a new instance of the model in the
|
764
792
|
// 'isLoading' state
|
765
|
-
model = this.
|
793
|
+
model = this.materializeRecord(type, clientId);
|
766
794
|
|
767
|
-
|
768
|
-
|
795
|
+
if (typeof dataCache[clientId] === 'object') {
|
796
|
+
model.send('didChangeData');
|
797
|
+
}
|
769
798
|
}
|
770
799
|
} else {
|
771
|
-
clientId = this.pushHash(
|
800
|
+
clientId = this.pushHash(LOADING, id, type);
|
772
801
|
|
773
802
|
// create a new instance of the model in the
|
774
803
|
// 'isLoading' state
|
775
|
-
model = this.
|
804
|
+
model = this.materializeRecord(type, clientId);
|
776
805
|
|
777
806
|
// let the adapter set the data, possibly async
|
778
807
|
var adapter = get(this, '_adapter');
|
779
808
|
if (adapter && adapter.find) { adapter.find(this, type, id); }
|
780
|
-
else { throw fmt("Adapter is either null or
|
809
|
+
else { throw fmt("Adapter is either null or does not implement `find` method", this); }
|
781
810
|
}
|
782
811
|
|
783
812
|
return model;
|
784
813
|
},
|
785
814
|
|
786
|
-
/**
|
815
|
+
/**
|
816
|
+
@private
|
817
|
+
|
818
|
+
Ask the adapter to fetch IDs that are not already loaded.
|
819
|
+
|
820
|
+
This method will convert `id`s to `clientId`s, filter out
|
821
|
+
`clientId`s that already have a data hash present, and pass
|
822
|
+
the remaining `id`s to the adapter.
|
823
|
+
|
824
|
+
@param {Class} type A model class
|
825
|
+
@param {Array} ids An array of ids
|
826
|
+
@param {Object} query
|
827
|
+
|
828
|
+
@returns {Array} An Array of all clientIds for the
|
829
|
+
specified ids.
|
787
830
|
*/
|
788
|
-
|
789
|
-
var
|
790
|
-
|
831
|
+
fetchMany: function(type, ids, query) {
|
832
|
+
var typeMap = this.typeMapFor(type),
|
833
|
+
idToClientIdMap = typeMap.idToCid,
|
834
|
+
dataCache = typeMap.cidToHash,
|
835
|
+
data = typeMap.cidToHash,
|
836
|
+
needed;
|
791
837
|
|
792
838
|
var clientIds = Ember.A([]);
|
793
839
|
|
@@ -795,32 +841,59 @@ DS.Store = Ember.Object.extend({
|
|
795
841
|
needed = [];
|
796
842
|
|
797
843
|
ids.forEach(function(id) {
|
844
|
+
// Get the clientId for the given id
|
798
845
|
var clientId = idToClientIdMap[id];
|
799
|
-
|
800
|
-
|
846
|
+
|
847
|
+
// If there is no `clientId` yet
|
848
|
+
if (clientId === undefined) {
|
849
|
+
// Create a new `clientId`, marking its data hash
|
850
|
+
// as loading. Once the adapter returns the data
|
851
|
+
// hash, it will be updated
|
852
|
+
clientId = this.pushHash(LOADING, id, type);
|
853
|
+
needed.push(id);
|
854
|
+
|
855
|
+
// If there is a clientId, but its data hash is
|
856
|
+
// marked as unloaded (this happens when a
|
857
|
+
// hasMany association creates clientIds for its
|
858
|
+
// referenced ids before they were loaded)
|
859
|
+
} else if (clientId && data[clientId] === UNLOADED) {
|
860
|
+
// change the data hash marker to loading
|
861
|
+
dataCache[clientId] = LOADING;
|
801
862
|
needed.push(id);
|
802
863
|
}
|
803
864
|
|
865
|
+
// this method is expected to return a list of
|
866
|
+
// all of the clientIds for the specified ids,
|
867
|
+
// unconditionally add it.
|
804
868
|
clientIds.push(clientId);
|
805
869
|
}, this);
|
806
870
|
} else {
|
807
871
|
needed = null;
|
808
872
|
}
|
809
873
|
|
874
|
+
// If there are any needed ids, ask the adapter to load them
|
810
875
|
if ((needed && get(needed, 'length') > 0) || query) {
|
811
876
|
var adapter = get(this, '_adapter');
|
812
877
|
if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
|
813
|
-
else { throw fmt("Adapter is either null or
|
878
|
+
else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
|
814
879
|
}
|
815
880
|
|
816
|
-
return
|
881
|
+
return clientIds;
|
882
|
+
},
|
883
|
+
|
884
|
+
/** @private
|
885
|
+
*/
|
886
|
+
findMany: function(type, ids, query) {
|
887
|
+
var clientIds = this.fetchMany(type, ids, query);
|
888
|
+
|
889
|
+
return this.createManyArray(type, clientIds);
|
817
890
|
},
|
818
891
|
|
819
892
|
findQuery: function(type, query) {
|
820
893
|
var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
|
821
894
|
var adapter = get(this, '_adapter');
|
822
895
|
if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
|
823
|
-
else { throw fmt("Adapter is either null or
|
896
|
+
else { throw fmt("Adapter is either null or does not implement `findQuery` method", this); }
|
824
897
|
return array;
|
825
898
|
},
|
826
899
|
|
@@ -841,7 +914,14 @@ DS.Store = Ember.Object.extend({
|
|
841
914
|
return array;
|
842
915
|
},
|
843
916
|
|
844
|
-
filter: function(type, filter) {
|
917
|
+
filter: function(type, query, filter) {
|
918
|
+
// allow an optional server query
|
919
|
+
if (arguments.length === 3) {
|
920
|
+
this.findQuery(type, query);
|
921
|
+
} else if (arguments.length === 2) {
|
922
|
+
filter = query;
|
923
|
+
}
|
924
|
+
|
845
925
|
var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
|
846
926
|
|
847
927
|
this.registerModelArray(array, type, filter);
|
@@ -853,11 +933,8 @@ DS.Store = Ember.Object.extend({
|
|
853
933
|
// . UPDATING .
|
854
934
|
// ............
|
855
935
|
|
856
|
-
hashWasUpdated: function(type, clientId) {
|
857
|
-
|
858
|
-
var hash = clientIdToHashMap[clientId];
|
859
|
-
|
860
|
-
this.updateModelArrays(type, clientId, hash);
|
936
|
+
hashWasUpdated: function(type, clientId, record) {
|
937
|
+
this.updateModelArrays(type, clientId, get(record, 'data'));
|
861
938
|
},
|
862
939
|
|
863
940
|
// ..............
|
@@ -865,11 +942,14 @@ DS.Store = Ember.Object.extend({
|
|
865
942
|
// ..............
|
866
943
|
|
867
944
|
commit: function() {
|
868
|
-
get(this, 'defaultTransaction')
|
945
|
+
var defaultTransaction = get(this, 'defaultTransaction');
|
946
|
+
set(this, 'defaultTransaction', this.transaction());
|
947
|
+
|
948
|
+
defaultTransaction.commit();
|
869
949
|
},
|
870
950
|
|
871
951
|
didUpdateRecords: function(array, hashes) {
|
872
|
-
if (
|
952
|
+
if (hashes) {
|
873
953
|
array.forEach(function(model, idx) {
|
874
954
|
this.didUpdateRecord(model, hashes[idx]);
|
875
955
|
}, this);
|
@@ -881,72 +961,91 @@ DS.Store = Ember.Object.extend({
|
|
881
961
|
},
|
882
962
|
|
883
963
|
didUpdateRecord: function(model, hash) {
|
884
|
-
if (
|
885
|
-
var clientId = get(model, 'clientId')
|
886
|
-
|
964
|
+
if (hash) {
|
965
|
+
var clientId = get(model, 'clientId'),
|
966
|
+
dataCache = this.typeMapFor(model.constructor).cidToHash;
|
887
967
|
|
888
|
-
|
889
|
-
model.
|
968
|
+
dataCache[clientId] = hash;
|
969
|
+
model.send('didChangeData');
|
970
|
+
model.hashWasUpdated();
|
890
971
|
}
|
891
972
|
|
892
|
-
model.
|
973
|
+
model.send('didCommit');
|
893
974
|
},
|
894
975
|
|
895
976
|
didDeleteRecords: function(array) {
|
896
977
|
array.forEach(function(model) {
|
897
|
-
model.
|
978
|
+
model.send('didCommit');
|
898
979
|
});
|
899
980
|
},
|
900
981
|
|
901
982
|
didDeleteRecord: function(model) {
|
902
|
-
model.
|
983
|
+
model.send('didCommit');
|
903
984
|
},
|
904
985
|
|
905
|
-
|
906
|
-
var
|
986
|
+
_didCreateRecord: function(record, hash, typeMap, clientId, primaryKey) {
|
987
|
+
var recordData = get(record, 'data'), id, changes;
|
907
988
|
|
908
|
-
|
909
|
-
|
910
|
-
|
989
|
+
if (hash) {
|
990
|
+
typeMap.cidToHash[clientId] = hash;
|
991
|
+
|
992
|
+
// If the server returns a hash, we assume that the server's version
|
993
|
+
// of the data supercedes the local changes.
|
994
|
+
record.beginPropertyChanges();
|
995
|
+
record.send('didChangeData');
|
996
|
+
recordData.adapterDidUpdate(hash);
|
997
|
+
record.hashWasUpdated();
|
998
|
+
record.endPropertyChanges();
|
911
999
|
|
912
|
-
for (var i=0, l=get(array, 'length'); i<l; i++) {
|
913
|
-
var model = array[i], hash = hashes[i];
|
914
1000
|
id = hash[primaryKey];
|
915
|
-
clientId = get(model, 'clientId');
|
916
1001
|
|
917
|
-
|
918
|
-
|
1002
|
+
typeMap.idToCid[id] = clientId;
|
1003
|
+
this.clientIdToId[clientId] = id;
|
1004
|
+
} else {
|
1005
|
+
recordData.commit();
|
1006
|
+
}
|
1007
|
+
|
1008
|
+
record.send('didCommit');
|
1009
|
+
},
|
919
1010
|
|
920
|
-
idToClientIdMap[id] = clientId;
|
921
|
-
idList.push(id);
|
922
1011
|
|
923
|
-
|
1012
|
+
didCreateRecords: function(type, array, hashes) {
|
1013
|
+
var primaryKey = type.proto().primaryKey,
|
1014
|
+
typeMap = this.typeMapFor(type),
|
1015
|
+
id, clientId;
|
1016
|
+
|
1017
|
+
for (var i=0, l=get(array, 'length'); i<l; i++) {
|
1018
|
+
var model = array[i], hash = hashes[i];
|
1019
|
+
clientId = get(model, 'clientId');
|
1020
|
+
|
1021
|
+
this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
|
924
1022
|
}
|
925
1023
|
},
|
926
1024
|
|
927
1025
|
didCreateRecord: function(model, hash) {
|
928
|
-
var type = model.constructor
|
1026
|
+
var type = model.constructor,
|
1027
|
+
typeMap = this.typeMapFor(type),
|
1028
|
+
id, clientId, primaryKey;
|
929
1029
|
|
930
|
-
|
1030
|
+
// The hash is optional, but if it is not provided, the client must have
|
1031
|
+
// provided a primary key.
|
931
1032
|
|
932
|
-
|
933
|
-
var data = this.clientIdToHashMap(type);
|
934
|
-
var idList = this.idList(type);
|
1033
|
+
primaryKey = type.proto().primaryKey;
|
935
1034
|
|
936
|
-
|
1035
|
+
// TODO: Make ember_assert more flexible and convert this into an ember_assert
|
1036
|
+
if (hash) {
|
937
1037
|
|
938
|
-
|
939
|
-
data[clientId] = hash;
|
940
|
-
set(model, 'data', hash);
|
1038
|
+
} else {
|
941
1039
|
|
942
|
-
|
943
|
-
|
1040
|
+
}
|
1041
|
+
|
1042
|
+
clientId = get(model, 'clientId');
|
944
1043
|
|
945
|
-
|
1044
|
+
this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
|
946
1045
|
},
|
947
1046
|
|
948
1047
|
recordWasInvalid: function(record, errors) {
|
949
|
-
record.
|
1048
|
+
record.send('becameInvalid', errors);
|
950
1049
|
},
|
951
1050
|
|
952
1051
|
// ................
|
@@ -954,16 +1053,15 @@ DS.Store = Ember.Object.extend({
|
|
954
1053
|
// ................
|
955
1054
|
|
956
1055
|
registerModelArray: function(array, type, filter) {
|
957
|
-
var modelArrays =
|
958
|
-
var idToClientIdMap = this.idToClientIdMap(type);
|
1056
|
+
var modelArrays = this.typeMapFor(type).modelArrays;
|
959
1057
|
|
960
1058
|
modelArrays.push(array);
|
961
1059
|
|
962
1060
|
this.updateModelArrayFilter(array, type, filter);
|
963
1061
|
},
|
964
1062
|
|
965
|
-
|
966
|
-
var array = DS.
|
1063
|
+
createManyArray: function(type, clientIds) {
|
1064
|
+
var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
|
967
1065
|
|
968
1066
|
clientIds.forEach(function(clientId) {
|
969
1067
|
var modelArrays = this.modelArraysForClientId(clientId);
|
@@ -974,40 +1072,47 @@ DS.Store = Ember.Object.extend({
|
|
974
1072
|
},
|
975
1073
|
|
976
1074
|
updateModelArrayFilter: function(array, type, filter) {
|
977
|
-
var
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
1075
|
+
var typeMap = this.typeMapFor(type),
|
1076
|
+
dataCache = typeMap.cidToHash,
|
1077
|
+
clientIds = typeMap.clientIds,
|
1078
|
+
clientId, hash, proxy;
|
1079
|
+
|
1080
|
+
var recordCache = get(this, 'recordCache'), record;
|
1081
|
+
|
1082
|
+
for (var i=0, l=clientIds.length; i<l; i++) {
|
1083
|
+
clientId = clientIds[i];
|
1084
|
+
|
1085
|
+
hash = dataCache[clientId];
|
1086
|
+
if (typeof hash === 'object') {
|
1087
|
+
if (record = recordCache[clientId]) {
|
1088
|
+
proxy = get(record, 'data');
|
1089
|
+
} else {
|
1090
|
+
DATA_PROXY.savedData = hash;
|
1091
|
+
proxy = DATA_PROXY;
|
1092
|
+
}
|
984
1093
|
|
985
|
-
|
986
|
-
this.updateModelArray(array, filter, type, clientId, hash);
|
1094
|
+
this.updateModelArray(array, filter, type, clientId, proxy);
|
987
1095
|
}
|
988
1096
|
}
|
989
1097
|
},
|
990
1098
|
|
991
|
-
updateModelArrays: function(type, clientId,
|
992
|
-
var modelArrays =
|
1099
|
+
updateModelArrays: function(type, clientId, dataProxy) {
|
1100
|
+
var modelArrays = this.typeMapFor(type).modelArrays,
|
1101
|
+
modelArrayType, filter;
|
993
1102
|
|
994
1103
|
modelArrays.forEach(function(array) {
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
if (type !== modelArrayType) { return; }
|
999
|
-
|
1000
|
-
this.updateModelArray(array, filter, type, clientId, hash);
|
1104
|
+
filter = get(array, 'filterFunction');
|
1105
|
+
this.updateModelArray(array, filter, type, clientId, dataProxy);
|
1001
1106
|
}, this);
|
1002
1107
|
},
|
1003
1108
|
|
1004
|
-
updateModelArray: function(array, filter, type, clientId,
|
1109
|
+
updateModelArray: function(array, filter, type, clientId, dataProxy) {
|
1005
1110
|
var shouldBeInArray;
|
1006
1111
|
|
1007
1112
|
if (!filter) {
|
1008
1113
|
shouldBeInArray = true;
|
1009
1114
|
} else {
|
1010
|
-
shouldBeInArray = filter(
|
1115
|
+
shouldBeInArray = filter(dataProxy);
|
1011
1116
|
}
|
1012
1117
|
|
1013
1118
|
var content = get(array, 'content');
|
@@ -1035,61 +1140,53 @@ DS.Store = Ember.Object.extend({
|
|
1035
1140
|
},
|
1036
1141
|
|
1037
1142
|
// ............
|
1038
|
-
// .
|
1143
|
+
// . INDEXING .
|
1039
1144
|
// ............
|
1040
1145
|
|
1146
|
+
modelArraysForClientId: function(clientId) {
|
1147
|
+
var modelArrays = get(this, 'modelArraysByClientId');
|
1148
|
+
var ret = modelArrays[clientId];
|
1149
|
+
|
1150
|
+
if (!ret) {
|
1151
|
+
ret = modelArrays[clientId] = Ember.OrderedSet.create();
|
1152
|
+
}
|
1153
|
+
|
1154
|
+
return ret;
|
1155
|
+
},
|
1156
|
+
|
1041
1157
|
typeMapFor: function(type) {
|
1042
|
-
var
|
1158
|
+
var typeMaps = get(this, 'typeMaps');
|
1043
1159
|
var guidForType = Ember.guidFor(type);
|
1044
1160
|
|
1045
|
-
var typeMap =
|
1161
|
+
var typeMap = typeMaps[guidForType];
|
1046
1162
|
|
1047
1163
|
if (typeMap) {
|
1048
1164
|
return typeMap;
|
1049
1165
|
} else {
|
1050
|
-
return (
|
1166
|
+
return (typeMaps[guidForType] =
|
1051
1167
|
{
|
1052
1168
|
idToCid: {},
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1169
|
+
clientIds: [],
|
1170
|
+
cidToHash: {},
|
1171
|
+
modelArrays: []
|
1056
1172
|
});
|
1057
1173
|
}
|
1058
1174
|
},
|
1059
1175
|
|
1060
|
-
idToClientIdMap: function(type) {
|
1061
|
-
return this.typeMapFor(type).idToCid;
|
1062
|
-
},
|
1063
|
-
|
1064
|
-
idList: function(type) {
|
1065
|
-
return this.typeMapFor(type).idList;
|
1066
|
-
},
|
1067
|
-
|
1068
|
-
clientIdList: function(type) {
|
1069
|
-
return this.typeMapFor(type).cidList;
|
1070
|
-
},
|
1071
|
-
|
1072
|
-
clientIdToHashMap: function(type) {
|
1073
|
-
return this.typeMapFor(type).cidToHash;
|
1074
|
-
},
|
1075
|
-
|
1076
1176
|
/** @private
|
1077
1177
|
|
1078
1178
|
For a given type and id combination, returns the client id used by the store.
|
1079
|
-
If no client id has been assigned yet,
|
1179
|
+
If no client id has been assigned yet, one will be created and returned.
|
1080
1180
|
|
1081
1181
|
@param {DS.Model} type
|
1082
1182
|
@param {String|Number} id
|
1083
1183
|
*/
|
1084
1184
|
clientIdForId: function(type, id) {
|
1085
|
-
|
1086
|
-
},
|
1185
|
+
var clientId = this.typeMapFor(type).idToCid[id];
|
1087
1186
|
|
1088
|
-
|
1089
|
-
var primaryKey = getPath(type, 'proto.primaryKey');
|
1187
|
+
if (clientId !== undefined) { return clientId; }
|
1090
1188
|
|
1091
|
-
|
1092
|
-
return hash[primaryKey];
|
1189
|
+
return this.pushHash(UNLOADED, id, type);
|
1093
1190
|
},
|
1094
1191
|
|
1095
1192
|
// ................
|
@@ -1111,29 +1208,29 @@ DS.Store = Ember.Object.extend({
|
|
1111
1208
|
load: function(type, id, hash) {
|
1112
1209
|
if (hash === undefined) {
|
1113
1210
|
hash = id;
|
1114
|
-
var primaryKey =
|
1115
|
-
|
1211
|
+
var primaryKey = type.proto().primaryKey;
|
1212
|
+
|
1116
1213
|
id = hash[primaryKey];
|
1117
1214
|
}
|
1118
1215
|
|
1119
|
-
var
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1216
|
+
var typeMap = this.typeMapFor(type),
|
1217
|
+
dataCache = typeMap.cidToHash,
|
1218
|
+
clientId = typeMap.idToCid[id],
|
1219
|
+
recordCache = get(this, 'recordCache');
|
1123
1220
|
|
1124
1221
|
if (clientId !== undefined) {
|
1125
|
-
|
1222
|
+
dataCache[clientId] = hash;
|
1126
1223
|
|
1127
|
-
var
|
1128
|
-
if (
|
1129
|
-
|
1130
|
-
model.setData(hash);
|
1224
|
+
var record = recordCache[clientId];
|
1225
|
+
if (record) {
|
1226
|
+
record.send('didChangeData');
|
1131
1227
|
}
|
1132
1228
|
} else {
|
1133
1229
|
clientId = this.pushHash(hash, id, type);
|
1134
1230
|
}
|
1135
1231
|
|
1136
|
-
|
1232
|
+
DATA_PROXY.savedData = hash;
|
1233
|
+
this.updateModelArrays(type, clientId, DATA_PROXY);
|
1137
1234
|
|
1138
1235
|
return { id: id, clientId: clientId };
|
1139
1236
|
},
|
@@ -1144,10 +1241,9 @@ DS.Store = Ember.Object.extend({
|
|
1144
1241
|
if (hashes === undefined) {
|
1145
1242
|
hashes = ids;
|
1146
1243
|
ids = [];
|
1147
|
-
var primaryKey =
|
1244
|
+
var primaryKey = type.proto().primaryKey;
|
1148
1245
|
|
1149
|
-
ids =
|
1150
|
-
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
1246
|
+
ids = Ember.ArrayUtils.map(hashes, function(hash) {
|
1151
1247
|
return hash[primaryKey];
|
1152
1248
|
});
|
1153
1249
|
}
|
@@ -1171,23 +1267,25 @@ DS.Store = Ember.Object.extend({
|
|
1171
1267
|
@returns {Number}
|
1172
1268
|
*/
|
1173
1269
|
pushHash: function(hash, id, type) {
|
1174
|
-
var
|
1175
|
-
|
1176
|
-
var
|
1177
|
-
|
1270
|
+
var typeMap = this.typeMapFor(type);
|
1271
|
+
|
1272
|
+
var idToClientIdMap = typeMap.idToCid,
|
1273
|
+
clientIdToIdMap = this.clientIdToId,
|
1274
|
+
clientIds = typeMap.clientIds,
|
1275
|
+
dataCache = typeMap.cidToHash;
|
1178
1276
|
|
1179
|
-
var clientId = this.
|
1277
|
+
var clientId = ++this.clientIdCounter;
|
1180
1278
|
|
1181
|
-
|
1279
|
+
dataCache[clientId] = hash;
|
1182
1280
|
|
1183
1281
|
// if we're creating an item, this process will be done
|
1184
1282
|
// later, once the object has been persisted.
|
1185
1283
|
if (id) {
|
1186
1284
|
idToClientIdMap[id] = clientId;
|
1187
|
-
|
1285
|
+
clientIdToIdMap[clientId] = id;
|
1188
1286
|
}
|
1189
1287
|
|
1190
|
-
|
1288
|
+
clientIds.push(clientId);
|
1191
1289
|
|
1192
1290
|
return clientId;
|
1193
1291
|
},
|
@@ -1196,22 +1294,35 @@ DS.Store = Ember.Object.extend({
|
|
1196
1294
|
// . MODEL MATERIALIZATION .
|
1197
1295
|
// .........................
|
1198
1296
|
|
1199
|
-
|
1297
|
+
materializeRecord: function(type, clientId) {
|
1200
1298
|
var model;
|
1201
1299
|
|
1202
|
-
get(this, '
|
1203
|
-
|
1204
|
-
|
1300
|
+
get(this, 'recordCache')[clientId] = model = type._create({
|
1301
|
+
store: this,
|
1302
|
+
clientId: clientId
|
1303
|
+
});
|
1304
|
+
|
1305
|
+
get(this, 'defaultTransaction').adoptRecord(model);
|
1306
|
+
|
1307
|
+
model.send('loadingData');
|
1205
1308
|
return model;
|
1309
|
+
},
|
1310
|
+
|
1311
|
+
destroy: function() {
|
1312
|
+
if (get(DS, 'defaultStore') === this) {
|
1313
|
+
set(DS, 'defaultStore', null);
|
1314
|
+
}
|
1315
|
+
|
1316
|
+
return this._super();
|
1206
1317
|
}
|
1207
1318
|
});
|
1208
1319
|
|
1320
|
+
})();
|
1209
1321
|
|
1210
|
-
})({});
|
1211
1322
|
|
1212
1323
|
|
1213
|
-
(function(
|
1214
|
-
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
1324
|
+
(function() {
|
1325
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, guidFor = Ember.guidFor;
|
1215
1326
|
|
1216
1327
|
var stateProperty = Ember.computed(function(key) {
|
1217
1328
|
var parent = get(this, 'parentState');
|
@@ -1220,116 +1331,413 @@ var stateProperty = Ember.computed(function(key) {
|
|
1220
1331
|
}
|
1221
1332
|
}).property();
|
1222
1333
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
isDeleted: stateProperty,
|
1228
|
-
isError: stateProperty,
|
1229
|
-
isNew: stateProperty,
|
1230
|
-
isValid: stateProperty
|
1231
|
-
});
|
1334
|
+
var isEmptyObject = function(object) {
|
1335
|
+
for (var name in object) {
|
1336
|
+
if (object.hasOwnProperty(name)) { return false; }
|
1337
|
+
}
|
1232
1338
|
|
1233
|
-
|
1234
|
-
// TODO: get the current state name
|
1235
|
-
throw "You cannot load data into the store when its associated model is in its current state";
|
1339
|
+
return true;
|
1236
1340
|
};
|
1237
1341
|
|
1238
|
-
var
|
1239
|
-
for (var
|
1240
|
-
if (
|
1241
|
-
return false;
|
1342
|
+
var hasDefinedProperties = function(object) {
|
1343
|
+
for (var name in object) {
|
1344
|
+
if (object.hasOwnProperty(name) && object[name]) { return true; }
|
1242
1345
|
}
|
1243
1346
|
|
1244
|
-
return
|
1347
|
+
return false;
|
1245
1348
|
};
|
1246
1349
|
|
1350
|
+
DS.State = Ember.State.extend({
|
1351
|
+
isLoaded: stateProperty,
|
1352
|
+
isDirty: stateProperty,
|
1353
|
+
isSaving: stateProperty,
|
1354
|
+
isDeleted: stateProperty,
|
1355
|
+
isError: stateProperty,
|
1356
|
+
isNew: stateProperty,
|
1357
|
+
isValid: stateProperty,
|
1358
|
+
isPending: stateProperty,
|
1359
|
+
|
1360
|
+
// For states that are substates of a
|
1361
|
+
// DirtyState (updated or created), it is
|
1362
|
+
// useful to be able to determine which
|
1363
|
+
// type of dirty state it is.
|
1364
|
+
dirtyType: stateProperty
|
1365
|
+
});
|
1366
|
+
|
1247
1367
|
var setProperty = function(manager, context) {
|
1248
1368
|
var key = context.key, value = context.value;
|
1249
1369
|
|
1250
|
-
var model = get(manager, 'model'),
|
1251
|
-
|
1252
|
-
|
1370
|
+
var model = get(manager, 'model'),
|
1371
|
+
data = get(model, 'data');
|
1372
|
+
|
1373
|
+
set(data, key, value);
|
1374
|
+
};
|
1375
|
+
|
1376
|
+
var setAssociation = function(manager, context) {
|
1377
|
+
var key = context.key, value = context.value;
|
1378
|
+
|
1379
|
+
var model = get(manager, 'model'),
|
1380
|
+
data = get(model, 'data');
|
1253
1381
|
|
1254
|
-
data
|
1382
|
+
data.setAssociation(key, value);
|
1383
|
+
};
|
1384
|
+
|
1385
|
+
var didChangeData = function(manager) {
|
1386
|
+
var model = get(manager, 'model'),
|
1387
|
+
data = get(model, 'data');
|
1255
1388
|
|
1256
|
-
|
1389
|
+
data._savedData = null;
|
1390
|
+
model.notifyPropertyChange('data');
|
1257
1391
|
};
|
1258
1392
|
|
1259
|
-
//
|
1260
|
-
//
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1393
|
+
// The waitingOn event shares common functionality
|
1394
|
+
// between the different dirty states, but each is
|
1395
|
+
// treated slightly differently. This method is exposed
|
1396
|
+
// so that each implementation can invoke the common
|
1397
|
+
// behavior, and then implement the behavior specific
|
1398
|
+
// to the state.
|
1399
|
+
var waitingOn = function(manager, object) {
|
1400
|
+
var model = get(manager, 'model'),
|
1401
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1402
|
+
objectGuid = guidFor(object);
|
1403
|
+
|
1404
|
+
var observer = function() {
|
1405
|
+
if (get(object, 'id')) {
|
1406
|
+
manager.send('doneWaitingOn', object);
|
1407
|
+
Ember.removeObserver(object, 'id', observer);
|
1408
|
+
}
|
1409
|
+
};
|
1410
|
+
|
1411
|
+
pendingQueue[objectGuid] = [object, observer];
|
1412
|
+
Ember.addObserver(object, 'id', observer);
|
1413
|
+
};
|
1414
|
+
|
1415
|
+
// Implementation notes:
|
1416
|
+
//
|
1417
|
+
// Each state has a boolean value for all of the following flags:
|
1418
|
+
//
|
1419
|
+
// * isLoaded: The record has a populated `data` property. When a
|
1420
|
+
// record is loaded via `store.find`, `isLoaded` is false
|
1421
|
+
// until the adapter sets it. When a record is created locally,
|
1422
|
+
// its `isLoaded` property is always true.
|
1423
|
+
// * isDirty: The record has local changes that have not yet been
|
1424
|
+
// saved by the adapter. This includes records that have been
|
1425
|
+
// created (but not yet saved) or deleted.
|
1426
|
+
// * isSaving: The record's transaction has been committed, but
|
1427
|
+
// the adapter has not yet acknowledged that the changes have
|
1428
|
+
// been persisted to the backend.
|
1429
|
+
// * isDeleted: The record was marked for deletion. When `isDeleted`
|
1430
|
+
// is true and `isDirty` is true, the record is deleted locally
|
1431
|
+
// but the deletion was not yet persisted. When `isSaving` is
|
1432
|
+
// true, the change is in-flight. When both `isDirty` and
|
1433
|
+
// `isSaving` are false, the change has persisted.
|
1434
|
+
// * isError: The adapter reported that it was unable to save
|
1435
|
+
// local changes to the backend. This may also result in the
|
1436
|
+
// record having its `isValid` property become false if the
|
1437
|
+
// adapter reported that server-side validations failed.
|
1438
|
+
// * isNew: The record was created on the client and the adapter
|
1439
|
+
// did not yet report that it was successfully saved.
|
1440
|
+
// * isValid: No client-side validations have failed and the
|
1441
|
+
// adapter did not report any server-side validation failures.
|
1442
|
+
// * isPending: A record `isPending` when it belongs to an
|
1443
|
+
// association on another record and that record has not been
|
1444
|
+
// saved. A record in this state cannot be saved because it
|
1445
|
+
// lacks a "foreign key" that will be supplied by its parent
|
1446
|
+
// association when the parent record has been created. When
|
1447
|
+
// the adapter reports that the parent has saved, the
|
1448
|
+
// `isPending` property on all children will become `false`
|
1449
|
+
// and the transaction will try to commit the records.
|
1450
|
+
|
1451
|
+
// This mixin is mixed into various uncommitted states. Make
|
1452
|
+
// sure to mix it in *after* the class definition, so its
|
1453
|
+
// super points to the class definition.
|
1454
|
+
var Uncommitted = Ember.Mixin.create({
|
1455
|
+
setProperty: setProperty,
|
1456
|
+
setAssociation: setAssociation,
|
1269
1457
|
|
1270
|
-
|
1271
|
-
|
1272
|
-
model = get(manager, 'model');
|
1458
|
+
deleteRecord: function(manager) {
|
1459
|
+
this._super(manager);
|
1273
1460
|
|
1274
|
-
model
|
1275
|
-
|
1461
|
+
var model = get(manager, 'model'),
|
1462
|
+
dirtyType = get(this, 'dirtyType');
|
1463
|
+
|
1464
|
+
model.withTransaction(function(t) {
|
1465
|
+
t.modelBecameClean(dirtyType, model);
|
1276
1466
|
});
|
1277
|
-
}
|
1467
|
+
}
|
1468
|
+
});
|
1469
|
+
|
1470
|
+
// These mixins are mixed into substates of the concrete
|
1471
|
+
// subclasses of DirtyState.
|
1472
|
+
|
1473
|
+
var CreatedUncommitted = Ember.Mixin.create({
|
1474
|
+
deleteRecord: function(manager) {
|
1475
|
+
this._super(manager);
|
1476
|
+
|
1477
|
+
manager.goToState('deleted.saved');
|
1478
|
+
}
|
1479
|
+
});
|
1278
1480
|
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1481
|
+
var UpdatedUncommitted = Ember.Mixin.create({
|
1482
|
+
deleteRecord: function(manager) {
|
1483
|
+
this._super(manager);
|
1282
1484
|
|
1283
|
-
|
1485
|
+
var model = get(manager, 'model');
|
1284
1486
|
|
1285
|
-
model.withTransaction(function
|
1286
|
-
t.modelBecameClean(
|
1487
|
+
model.withTransaction(function(t) {
|
1488
|
+
t.modelBecameClean('created', model);
|
1287
1489
|
});
|
1288
|
-
},
|
1289
1490
|
|
1290
|
-
|
1491
|
+
manager.goToState('deleted');
|
1492
|
+
}
|
1493
|
+
});
|
1291
1494
|
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1495
|
+
// The dirty state is a abstract state whose functionality is
|
1496
|
+
// shared between the `created` and `updated` states.
|
1497
|
+
//
|
1498
|
+
// The deleted state shares the `isDirty` flag with the
|
1499
|
+
// subclasses of `DirtyState`, but with a very different
|
1500
|
+
// implementation.
|
1501
|
+
var DirtyState = DS.State.extend({
|
1502
|
+
initialState: 'uncommitted',
|
1295
1503
|
|
1296
|
-
|
1504
|
+
// FLAGS
|
1505
|
+
isDirty: true,
|
1506
|
+
|
1507
|
+
// SUBSTATES
|
1508
|
+
|
1509
|
+
// When a record first becomes dirty, it is `uncommitted`.
|
1510
|
+
// This means that there are local pending changes,
|
1511
|
+
// but they have not yet begun to be saved.
|
1512
|
+
uncommitted: DS.State.extend({
|
1513
|
+
// TRANSITIONS
|
1514
|
+
enter: function(manager) {
|
1515
|
+
var dirtyType = get(this, 'dirtyType'),
|
1516
|
+
model = get(manager, 'model');
|
1517
|
+
|
1518
|
+
model.withTransaction(function (t) {
|
1519
|
+
t.modelBecameDirty(dirtyType, model);
|
1520
|
+
});
|
1521
|
+
},
|
1522
|
+
|
1523
|
+
exit: function(manager) {
|
1524
|
+
var model = get(manager, 'model');
|
1525
|
+
manager.send('invokeLifecycleCallbacks', model);
|
1526
|
+
},
|
1527
|
+
|
1528
|
+
// EVENTS
|
1529
|
+
deleteRecord: Ember.K,
|
1530
|
+
|
1531
|
+
waitingOn: function(manager, object) {
|
1532
|
+
waitingOn(manager, object);
|
1533
|
+
manager.goToState('pending');
|
1534
|
+
},
|
1535
|
+
|
1536
|
+
willCommit: function(manager) {
|
1537
|
+
manager.goToState('inFlight');
|
1538
|
+
}
|
1539
|
+
}, Uncommitted),
|
1540
|
+
|
1541
|
+
// Once a record has been handed off to the adapter to be
|
1542
|
+
// saved, it is in the 'in flight' state. Changes to the
|
1543
|
+
// record cannot be made during this window.
|
1544
|
+
inFlight: DS.State.extend({
|
1545
|
+
// FLAGS
|
1297
1546
|
isSaving: true,
|
1298
1547
|
|
1299
|
-
|
1548
|
+
// TRANSITIONS
|
1549
|
+
enter: function(manager) {
|
1550
|
+
var dirtyType = get(this, 'dirtyType'),
|
1551
|
+
model = get(manager, 'model');
|
1552
|
+
|
1553
|
+
model.withTransaction(function (t) {
|
1554
|
+
t.modelBecameClean(dirtyType, model);
|
1555
|
+
});
|
1556
|
+
},
|
1557
|
+
|
1558
|
+
// EVENTS
|
1559
|
+
didCommit: function(manager) {
|
1300
1560
|
manager.goToState('loaded');
|
1301
1561
|
},
|
1302
1562
|
|
1303
|
-
|
1563
|
+
becameInvalid: function(manager, errors) {
|
1304
1564
|
var model = get(manager, 'model');
|
1305
1565
|
|
1306
1566
|
set(model, 'errors', errors);
|
1307
1567
|
manager.goToState('invalid');
|
1308
|
-
}
|
1568
|
+
},
|
1569
|
+
|
1570
|
+
didChangeData: didChangeData
|
1571
|
+
}),
|
1572
|
+
|
1573
|
+
// If a record becomes associated with a newly created
|
1574
|
+
// parent record, it will be `pending` until the parent
|
1575
|
+
// record has successfully persisted. Once this happens,
|
1576
|
+
// this record can use the parent's primary key as its
|
1577
|
+
// foreign key.
|
1578
|
+
//
|
1579
|
+
// If the record's transaction had already started to
|
1580
|
+
// commit, the record will transition to the `inFlight`
|
1581
|
+
// state. If it had not, the record will transition to
|
1582
|
+
// the `uncommitted` state.
|
1583
|
+
pending: DS.State.extend({
|
1584
|
+
initialState: 'uncommitted',
|
1585
|
+
|
1586
|
+
// FLAGS
|
1587
|
+
isPending: true,
|
1588
|
+
|
1589
|
+
// SUBSTATES
|
1590
|
+
|
1591
|
+
// A pending record whose transaction has not yet
|
1592
|
+
// started to commit is in this state.
|
1593
|
+
uncommitted: DS.State.extend({
|
1594
|
+
// EVENTS
|
1595
|
+
deleteRecord: function(manager) {
|
1596
|
+
var model = get(manager, 'model'),
|
1597
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1598
|
+
tuple;
|
1599
|
+
|
1600
|
+
// since we are leaving the pending state, remove any
|
1601
|
+
// observers we have registered on other records.
|
1602
|
+
for (var prop in pendingQueue) {
|
1603
|
+
if (!pendingQueue.hasOwnProperty(prop)) { continue; }
|
1604
|
+
|
1605
|
+
tuple = pendingQueue[prop];
|
1606
|
+
Ember.removeObserver(tuple[0], 'id', tuple[1]);
|
1607
|
+
}
|
1608
|
+
},
|
1609
|
+
|
1610
|
+
willCommit: function(manager) {
|
1611
|
+
manager.goToState('committing');
|
1612
|
+
},
|
1613
|
+
|
1614
|
+
doneWaitingOn: function(manager, object) {
|
1615
|
+
var model = get(manager, 'model'),
|
1616
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1617
|
+
objectGuid = guidFor(object);
|
1618
|
+
|
1619
|
+
delete pendingQueue[objectGuid];
|
1620
|
+
|
1621
|
+
if (isEmptyObject(pendingQueue)) {
|
1622
|
+
manager.send('doneWaiting');
|
1623
|
+
}
|
1624
|
+
},
|
1625
|
+
|
1626
|
+
doneWaiting: function(manager) {
|
1627
|
+
var dirtyType = get(this, 'dirtyType');
|
1628
|
+
manager.goToState(dirtyType + '.uncommitted');
|
1629
|
+
}
|
1630
|
+
}, Uncommitted),
|
1631
|
+
|
1632
|
+
// A pending record whose transaction has started
|
1633
|
+
// to commit is in this state. Since it has not yet
|
1634
|
+
// been sent to the adapter, it is not `inFlight`
|
1635
|
+
// until all of its dependencies have been committed.
|
1636
|
+
committing: DS.State.extend({
|
1637
|
+
// FLAGS
|
1638
|
+
isSaving: true,
|
1639
|
+
|
1640
|
+
// EVENTS
|
1641
|
+
doneWaitingOn: function(manager, object) {
|
1642
|
+
var model = get(manager, 'model'),
|
1643
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1644
|
+
objectGuid = guidFor(object);
|
1645
|
+
|
1646
|
+
delete pendingQueue[objectGuid];
|
1647
|
+
|
1648
|
+
if (isEmptyObject(pendingQueue)) {
|
1649
|
+
manager.send('doneWaiting');
|
1650
|
+
}
|
1651
|
+
},
|
1652
|
+
|
1653
|
+
doneWaiting: function(manager) {
|
1654
|
+
var model = get(manager, 'model'),
|
1655
|
+
transaction = get(model, 'transaction');
|
1656
|
+
|
1657
|
+
// Now that the model is no longer pending, schedule
|
1658
|
+
// the transaction to commit.
|
1659
|
+
Ember.run.once(transaction, transaction.commit);
|
1660
|
+
},
|
1661
|
+
|
1662
|
+
willCommit: function(manager) {
|
1663
|
+
var dirtyType = get(this, 'dirtyType');
|
1664
|
+
manager.goToState(dirtyType + '.inFlight');
|
1665
|
+
}
|
1666
|
+
})
|
1309
1667
|
}),
|
1310
1668
|
|
1669
|
+
// A record is in the `invalid` state when its client-side
|
1670
|
+
// invalidations have failed, or if the adapter has indicated
|
1671
|
+
// the the record failed server-side invalidations.
|
1311
1672
|
invalid: DS.State.extend({
|
1673
|
+
// FLAGS
|
1312
1674
|
isValid: false,
|
1313
1675
|
|
1676
|
+
// EVENTS
|
1677
|
+
deleteRecord: function(manager) {
|
1678
|
+
manager.goToState('deleted');
|
1679
|
+
},
|
1680
|
+
|
1681
|
+
setAssociation: setAssociation,
|
1682
|
+
|
1314
1683
|
setProperty: function(manager, context) {
|
1315
1684
|
setProperty(manager, context);
|
1316
1685
|
|
1317
|
-
var
|
1318
|
-
model = get(manager, 'model'),
|
1686
|
+
var model = get(manager, 'model'),
|
1319
1687
|
errors = get(model, 'errors'),
|
1320
1688
|
key = context.key;
|
1321
1689
|
|
1322
1690
|
delete errors[key];
|
1323
1691
|
|
1324
|
-
if (
|
1325
|
-
manager.
|
1692
|
+
if (!hasDefinedProperties(errors)) {
|
1693
|
+
manager.send('becameValid');
|
1326
1694
|
}
|
1695
|
+
},
|
1696
|
+
|
1697
|
+
becameValid: function(manager) {
|
1698
|
+
manager.goToState('uncommitted');
|
1327
1699
|
}
|
1328
1700
|
})
|
1329
1701
|
});
|
1330
1702
|
|
1703
|
+
// The created and updated states are created outside the state
|
1704
|
+
// chart so we can reopen their substates and add mixins as
|
1705
|
+
// necessary.
|
1706
|
+
|
1707
|
+
var createdState = DirtyState.create({
|
1708
|
+
dirtyType: 'created',
|
1709
|
+
|
1710
|
+
// FLAGS
|
1711
|
+
isNew: true,
|
1712
|
+
|
1713
|
+
// EVENTS
|
1714
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
1715
|
+
model.didCreate();
|
1716
|
+
}
|
1717
|
+
});
|
1718
|
+
|
1719
|
+
var updatedState = DirtyState.create({
|
1720
|
+
dirtyType: 'updated',
|
1721
|
+
|
1722
|
+
// EVENTS
|
1723
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
1724
|
+
model.didUpdate();
|
1725
|
+
}
|
1726
|
+
});
|
1727
|
+
|
1728
|
+
// The created.uncommitted state and created.pending.uncommitted share
|
1729
|
+
// some logic defined in CreatedUncommitted.
|
1730
|
+
createdState.states.uncommitted.reopen(CreatedUncommitted);
|
1731
|
+
createdState.states.pending.states.uncommitted.reopen(CreatedUncommitted);
|
1732
|
+
|
1733
|
+
// The updated.uncommitted state and updated.pending.uncommitted share
|
1734
|
+
// some logic defined in UpdatedUncommitted.
|
1735
|
+
updatedState.states.uncommitted.reopen(UpdatedUncommitted);
|
1736
|
+
updatedState.states.pending.states.uncommitted.reopen(UpdatedUncommitted);
|
1737
|
+
|
1331
1738
|
var states = {
|
1332
1739
|
rootState: Ember.State.create({
|
1740
|
+
// FLAGS
|
1333
1741
|
isLoaded: false,
|
1334
1742
|
isDirty: false,
|
1335
1743
|
isSaving: false,
|
@@ -1337,118 +1745,168 @@ var states = {
|
|
1337
1745
|
isError: false,
|
1338
1746
|
isNew: false,
|
1339
1747
|
isValid: true,
|
1748
|
+
isPending: false,
|
1340
1749
|
|
1341
|
-
|
1342
|
-
|
1343
|
-
didCreate: function(manager) {
|
1344
|
-
manager.goToState('loaded.created');
|
1345
|
-
},
|
1750
|
+
// SUBSTATES
|
1346
1751
|
|
1752
|
+
// A record begins its lifecycle in the `empty` state.
|
1753
|
+
// If its data will come from the adapter, it will
|
1754
|
+
// transition into the `loading` state. Otherwise, if
|
1755
|
+
// the record is being created on the client, it will
|
1756
|
+
// transition into the `created` state.
|
1347
1757
|
empty: DS.State.create({
|
1758
|
+
// EVENTS
|
1348
1759
|
loadingData: function(manager) {
|
1349
1760
|
manager.goToState('loading');
|
1761
|
+
},
|
1762
|
+
|
1763
|
+
didChangeData: function(manager) {
|
1764
|
+
didChangeData(manager);
|
1765
|
+
|
1766
|
+
manager.goToState('loaded.created');
|
1350
1767
|
}
|
1351
1768
|
}),
|
1352
1769
|
|
1770
|
+
// A record enters this state when the store askes
|
1771
|
+
// the adapter for its data. It remains in this state
|
1772
|
+
// until the adapter provides the requested data.
|
1773
|
+
//
|
1774
|
+
// Usually, this process is asynchronous, using an
|
1775
|
+
// XHR to retrieve the data.
|
1353
1776
|
loading: DS.State.create({
|
1354
|
-
|
1355
|
-
|
1777
|
+
// TRANSITIONS
|
1356
1778
|
exit: function(manager) {
|
1357
1779
|
var model = get(manager, 'model');
|
1358
1780
|
model.didLoad();
|
1359
1781
|
},
|
1360
1782
|
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
if (data !== null) {
|
1368
|
-
manager.goToState('loaded');
|
1369
|
-
}
|
1783
|
+
// EVENTS
|
1784
|
+
didChangeData: function(manager, data) {
|
1785
|
+
didChangeData(manager);
|
1786
|
+
manager.send('loadedData');
|
1787
|
+
},
|
1370
1788
|
|
1371
|
-
|
1789
|
+
loadedData: function(manager) {
|
1790
|
+
manager.goToState('loaded');
|
1372
1791
|
}
|
1373
1792
|
}),
|
1374
1793
|
|
1794
|
+
// A record enters this state when its data is populated.
|
1795
|
+
// Most of a record's lifecycle is spent inside substates
|
1796
|
+
// of the `loaded` state.
|
1375
1797
|
loaded: DS.State.create({
|
1798
|
+
initialState: 'saved',
|
1799
|
+
|
1800
|
+
// FLAGS
|
1376
1801
|
isLoaded: true,
|
1377
1802
|
|
1378
|
-
|
1803
|
+
// SUBSTATES
|
1379
1804
|
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1805
|
+
// If there are no local changes to a record, it remains
|
1806
|
+
// in the `saved` state.
|
1807
|
+
saved: DS.State.create({
|
1808
|
+
// EVENTS
|
1809
|
+
setProperty: function(manager, context) {
|
1810
|
+
setProperty(manager, context);
|
1811
|
+
manager.goToState('updated');
|
1812
|
+
},
|
1384
1813
|
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1814
|
+
setAssociation: function(manager, context) {
|
1815
|
+
setAssociation(manager, context);
|
1816
|
+
manager.goToState('updated');
|
1817
|
+
},
|
1388
1818
|
|
1389
|
-
|
1390
|
-
stateName: 'created',
|
1391
|
-
isNew: true,
|
1819
|
+
didChangeData: didChangeData,
|
1392
1820
|
|
1393
|
-
|
1394
|
-
|
1821
|
+
deleteRecord: function(manager) {
|
1822
|
+
manager.goToState('deleted');
|
1823
|
+
},
|
1824
|
+
|
1825
|
+
waitingOn: function(manager, object) {
|
1826
|
+
waitingOn(manager, object);
|
1827
|
+
manager.goToState('updated.pending');
|
1395
1828
|
}
|
1396
1829
|
}),
|
1397
1830
|
|
1398
|
-
|
1399
|
-
|
1831
|
+
// A record is in this state after it has been locally
|
1832
|
+
// created but before the adapter has indicated that
|
1833
|
+
// it has been saved.
|
1834
|
+
created: createdState,
|
1400
1835
|
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1836
|
+
// A record is in this state if it has already been
|
1837
|
+
// saved to the server, but there are new local changes
|
1838
|
+
// that have not yet been saved.
|
1839
|
+
updated: updatedState
|
1405
1840
|
}),
|
1406
1841
|
|
1842
|
+
// A record is in this state if it was deleted from the store.
|
1407
1843
|
deleted: DS.State.create({
|
1844
|
+
// FLAGS
|
1408
1845
|
isDeleted: true,
|
1409
1846
|
isLoaded: true,
|
1410
1847
|
isDirty: true,
|
1411
1848
|
|
1412
|
-
|
1849
|
+
// SUBSTATES
|
1413
1850
|
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1851
|
+
// When a record is deleted, it enters the `start`
|
1852
|
+
// state. It will exit this state when the record's
|
1853
|
+
// transaction starts to commit.
|
1854
|
+
start: DS.State.create({
|
1855
|
+
// TRANSITIONS
|
1856
|
+
enter: function(manager) {
|
1857
|
+
var model = get(manager, 'model');
|
1858
|
+
var store = get(model, 'store');
|
1417
1859
|
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1860
|
+
if (store) {
|
1861
|
+
store.removeFromModelArrays(model);
|
1862
|
+
}
|
1421
1863
|
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1864
|
+
model.withTransaction(function(t) {
|
1865
|
+
t.modelBecameDirty('deleted', model);
|
1866
|
+
});
|
1867
|
+
},
|
1426
1868
|
|
1427
|
-
|
1428
|
-
manager
|
1429
|
-
|
1869
|
+
// EVENTS
|
1870
|
+
willCommit: function(manager) {
|
1871
|
+
manager.goToState('inFlight');
|
1872
|
+
}
|
1873
|
+
}),
|
1430
1874
|
|
1431
|
-
|
1875
|
+
// After a record's transaction is committing, but
|
1876
|
+
// before the adapter indicates that the deletion
|
1877
|
+
// has saved to the server, a record is in the
|
1878
|
+
// `inFlight` substate of `deleted`.
|
1879
|
+
inFlight: DS.State.create({
|
1880
|
+
// FLAGS
|
1432
1881
|
isSaving: true,
|
1433
1882
|
|
1434
|
-
|
1435
|
-
manager.goToState('saved');
|
1436
|
-
},
|
1437
|
-
|
1883
|
+
// TRANSITIONS
|
1438
1884
|
exit: function(stateManager) {
|
1439
1885
|
var model = get(stateManager, 'model');
|
1440
1886
|
|
1441
1887
|
model.withTransaction(function(t) {
|
1442
1888
|
t.modelBecameClean('deleted', model);
|
1443
1889
|
});
|
1890
|
+
},
|
1891
|
+
|
1892
|
+
// EVENTS
|
1893
|
+
didCommit: function(manager) {
|
1894
|
+
manager.goToState('saved');
|
1444
1895
|
}
|
1445
1896
|
}),
|
1446
1897
|
|
1898
|
+
// Once the adapter indicates that the deletion has
|
1899
|
+
// been saved, the record enters the `saved` substate
|
1900
|
+
// of `deleted`.
|
1447
1901
|
saved: DS.State.create({
|
1902
|
+
// FLAGS
|
1448
1903
|
isDirty: false
|
1449
1904
|
})
|
1450
1905
|
}),
|
1451
1906
|
|
1907
|
+
// If the adapter indicates that there was an unknown
|
1908
|
+
// error saving a record, the record enters the `error`
|
1909
|
+
// state.
|
1452
1910
|
error: DS.State.create({
|
1453
1911
|
isError: true
|
1454
1912
|
})
|
@@ -1461,210 +1919,536 @@ DS.StateManager = Ember.StateManager.extend({
|
|
1461
1919
|
states: states
|
1462
1920
|
});
|
1463
1921
|
|
1464
|
-
|
1465
|
-
return get(getPath(this, 'stateManager.currentState'), key);
|
1466
|
-
}).property('stateManager.currentState').cacheable();
|
1922
|
+
})();
|
1467
1923
|
|
1468
|
-
DS.Model = Ember.Object.extend({
|
1469
|
-
isLoaded: retrieveFromCurrentState,
|
1470
|
-
isDirty: retrieveFromCurrentState,
|
1471
|
-
isSaving: retrieveFromCurrentState,
|
1472
|
-
isDeleted: retrieveFromCurrentState,
|
1473
|
-
isError: retrieveFromCurrentState,
|
1474
|
-
isNew: retrieveFromCurrentState,
|
1475
|
-
isValid: retrieveFromCurrentState,
|
1476
1924
|
|
1477
|
-
clientId: null,
|
1478
1925
|
|
1479
|
-
|
1480
|
-
|
1481
|
-
primaryKey: 'id',
|
1482
|
-
data: null,
|
1483
|
-
transaction: null,
|
1926
|
+
(function() {
|
1927
|
+
var get = Ember.get, set = Ember.set;
|
1484
1928
|
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1929
|
+
// This object is a regular JS object for performance. It is only
|
1930
|
+
// used internally for bookkeeping purposes.
|
1931
|
+
var DataProxy = DS._DataProxy = function(record) {
|
1932
|
+
this.record = record;
|
1933
|
+
this.unsavedData = {};
|
1934
|
+
this.associations = {};
|
1935
|
+
};
|
1488
1936
|
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
});
|
1937
|
+
DataProxy.prototype = {
|
1938
|
+
get: function(key) { return Ember.get(this, key); },
|
1939
|
+
set: function(key, value) { return Ember.set(this, key, value); },
|
1493
1940
|
|
1494
|
-
|
1495
|
-
|
1941
|
+
setAssociation: function(key, value) {
|
1942
|
+
this.associations[key] = value;
|
1496
1943
|
},
|
1497
1944
|
|
1498
|
-
|
1499
|
-
var
|
1945
|
+
savedData: function() {
|
1946
|
+
var savedData = this._savedData;
|
1947
|
+
if (savedData) { return savedData; }
|
1500
1948
|
|
1501
|
-
|
1502
|
-
|
1949
|
+
var record = this.record,
|
1950
|
+
clientId = get(record, 'clientId'),
|
1951
|
+
store = get(record, 'store');
|
1503
1952
|
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1953
|
+
if (store) {
|
1954
|
+
savedData = store.dataForRecord(record);
|
1955
|
+
this._savedData = savedData;
|
1956
|
+
return savedData;
|
1957
|
+
}
|
1507
1958
|
},
|
1508
1959
|
|
1509
|
-
|
1510
|
-
var
|
1511
|
-
|
1512
|
-
|
1960
|
+
unknownProperty: function(key) {
|
1961
|
+
var unsavedData = this.unsavedData,
|
1962
|
+
associations = this.associations,
|
1963
|
+
savedData = this.savedData(),
|
1964
|
+
store;
|
1513
1965
|
|
1514
|
-
|
1515
|
-
var stateManager = get(this, 'stateManager');
|
1516
|
-
stateManager.send('delete');
|
1517
|
-
},
|
1966
|
+
var value = unsavedData[key], association;
|
1518
1967
|
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
},
|
1968
|
+
// if this is a belongsTo association, this will
|
1969
|
+
// be a clientId.
|
1970
|
+
association = associations[key];
|
1523
1971
|
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1972
|
+
if (association !== undefined) {
|
1973
|
+
store = get(this.record, 'store');
|
1974
|
+
return store.clientIdToId[association];
|
1975
|
+
}
|
1528
1976
|
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
},
|
1977
|
+
if (savedData && value === undefined) {
|
1978
|
+
value = savedData[key];
|
1979
|
+
}
|
1533
1980
|
|
1534
|
-
|
1535
|
-
var stateManager = get(this, 'stateManager');
|
1536
|
-
stateManager.send('willCommit');
|
1981
|
+
return value;
|
1537
1982
|
},
|
1538
1983
|
|
1539
|
-
|
1540
|
-
var
|
1541
|
-
|
1542
|
-
},
|
1984
|
+
setUnknownProperty: function(key, value) {
|
1985
|
+
var record = this.record,
|
1986
|
+
unsavedData = this.unsavedData;
|
1543
1987
|
|
1544
|
-
|
1545
|
-
var stateManager = get(this, 'stateManager');
|
1546
|
-
stateManager.send('didCreate');
|
1547
|
-
},
|
1988
|
+
unsavedData[key] = value;
|
1548
1989
|
|
1549
|
-
|
1550
|
-
var stateManager = get(this, 'stateManager');
|
1551
|
-
stateManager.send('didDelete');
|
1552
|
-
},
|
1990
|
+
record.hashWasUpdated();
|
1553
1991
|
|
1554
|
-
|
1555
|
-
var stateManager = get(this, 'stateManager');
|
1556
|
-
stateManager.send('wasInvalid', errors);
|
1992
|
+
return value;
|
1557
1993
|
},
|
1558
1994
|
|
1559
|
-
|
1560
|
-
var
|
1995
|
+
commit: function() {
|
1996
|
+
var record = this.record;
|
1997
|
+
|
1998
|
+
var unsavedData = this.unsavedData;
|
1999
|
+
var savedData = this.savedData();
|
1561
2000
|
|
1562
|
-
|
1563
|
-
|
2001
|
+
for (var prop in unsavedData) {
|
2002
|
+
if (unsavedData.hasOwnProperty(prop)) {
|
2003
|
+
savedData[prop] = unsavedData[prop];
|
2004
|
+
delete unsavedData[prop];
|
2005
|
+
}
|
1564
2006
|
}
|
2007
|
+
|
2008
|
+
record.notifyPropertyChange('data');
|
1565
2009
|
},
|
1566
2010
|
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
2011
|
+
rollback: function() {
|
2012
|
+
this.unsavedData = {};
|
2013
|
+
},
|
1570
2014
|
|
1571
|
-
|
1572
|
-
|
2015
|
+
adapterDidUpdate: function(data) {
|
2016
|
+
this.unsavedData = {};
|
1573
2017
|
}
|
1574
|
-
}
|
2018
|
+
};
|
1575
2019
|
|
1576
|
-
|
1577
|
-
var transform = DS.attr.transforms[type];
|
1578
|
-
var transformFrom = transform.from;
|
1579
|
-
var transformTo = transform.to;
|
2020
|
+
})();
|
1580
2021
|
|
1581
|
-
return Ember.computed(function(key, value) {
|
1582
|
-
var data = get(this, 'data');
|
1583
2022
|
|
1584
|
-
key = (options && options.key) ? options.key : key;
|
1585
2023
|
|
1586
|
-
|
1587
|
-
|
2024
|
+
(function() {
|
2025
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, none = Ember.none;
|
1588
2026
|
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
2027
|
+
var retrieveFromCurrentState = Ember.computed(function(key) {
|
2028
|
+
return get(getPath(this, 'stateManager.currentState'), key);
|
2029
|
+
}).property('stateManager.currentState').cacheable();
|
1592
2030
|
|
1593
|
-
|
1594
|
-
|
2031
|
+
DS.Model = Ember.Object.extend({
|
2032
|
+
isLoaded: retrieveFromCurrentState,
|
2033
|
+
isDirty: retrieveFromCurrentState,
|
2034
|
+
isSaving: retrieveFromCurrentState,
|
2035
|
+
isDeleted: retrieveFromCurrentState,
|
2036
|
+
isError: retrieveFromCurrentState,
|
2037
|
+
isNew: retrieveFromCurrentState,
|
2038
|
+
isPending: retrieveFromCurrentState,
|
2039
|
+
isValid: retrieveFromCurrentState,
|
2040
|
+
|
2041
|
+
clientId: null,
|
2042
|
+
transaction: null,
|
2043
|
+
stateManager: null,
|
2044
|
+
pendingQueue: null,
|
2045
|
+
errors: null,
|
2046
|
+
|
2047
|
+
// because unknownProperty is used, any internal property
|
2048
|
+
// must be initialized here.
|
2049
|
+
primaryKey: 'id',
|
2050
|
+
id: Ember.computed(function(key, value) {
|
2051
|
+
var primaryKey = get(this, 'primaryKey'),
|
2052
|
+
data = get(this, 'data');
|
2053
|
+
|
2054
|
+
if (arguments.length === 2) {
|
2055
|
+
set(data, primaryKey, value);
|
1595
2056
|
return value;
|
1596
2057
|
}
|
1597
|
-
}).property('data');
|
1598
|
-
};
|
1599
2058
|
|
1600
|
-
|
1601
|
-
|
1602
|
-
if (one) {
|
1603
|
-
return association ? store.load(type, association).id : null;
|
1604
|
-
} else {
|
1605
|
-
return association ? store.loadMany(type, association).ids : [];
|
1606
|
-
}
|
1607
|
-
};
|
2059
|
+
return data && get(data, primaryKey);
|
2060
|
+
}).property('primaryKey', 'data'),
|
1608
2061
|
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
2062
|
+
// The following methods are callbacks invoked by `getJSON`. You
|
2063
|
+
// can override one of the callbacks to override specific behavior,
|
2064
|
+
// or getJSON itself.
|
2065
|
+
//
|
2066
|
+
// If you override getJSON, you can invoke these callbacks manually
|
2067
|
+
// to get the default behavior.
|
1612
2068
|
|
1613
|
-
|
1614
|
-
|
1615
|
-
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
2069
|
+
/**
|
2070
|
+
Add the record's primary key to the JSON hash.
|
1616
2071
|
|
1617
|
-
|
1618
|
-
|
1619
|
-
store = get(this, 'store');
|
2072
|
+
The default implementation uses the record's specified `primaryKey`
|
2073
|
+
and the `id` computed property, which are passed in as parameters.
|
1620
2074
|
|
1621
|
-
|
2075
|
+
@param {Object} json the JSON hash being built
|
2076
|
+
@param {Number|String} id the record's id
|
2077
|
+
@param {String} key the primaryKey for the record
|
2078
|
+
*/
|
2079
|
+
addIdToJSON: function(json, id, key) {
|
2080
|
+
if (id) { json[key] = id; }
|
2081
|
+
},
|
1622
2082
|
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
2083
|
+
/**
|
2084
|
+
Add the attributes' current values to the JSON hash.
|
2085
|
+
|
2086
|
+
The default implementation gets the current value of each
|
2087
|
+
attribute from the `data`, and uses a `defaultValue` if
|
2088
|
+
specified in the `DS.attr` definition.
|
2089
|
+
|
2090
|
+
@param {Object} json the JSON hash being build
|
2091
|
+
@param {Ember.Map} attributes a Map of attributes
|
2092
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2093
|
+
*/
|
2094
|
+
addAttributesToJSON: function(json, attributes, data) {
|
2095
|
+
attributes.forEach(function(name, meta) {
|
2096
|
+
var key = meta.key(this.constructor),
|
2097
|
+
value = get(data, key);
|
2098
|
+
|
2099
|
+
if (value === undefined) {
|
2100
|
+
value = meta.options.defaultValue;
|
2101
|
+
}
|
2102
|
+
|
2103
|
+
json[key] = value;
|
2104
|
+
}, this);
|
2105
|
+
},
|
2106
|
+
|
2107
|
+
/**
|
2108
|
+
Add the value of a `hasMany` association to the JSON hash.
|
2109
|
+
|
2110
|
+
The default implementation honors the `embedded` option
|
2111
|
+
passed to `DS.hasMany`. If embedded, `toJSON` is recursively
|
2112
|
+
called on the child records. If not, the `id` of each
|
2113
|
+
record is added.
|
2114
|
+
|
2115
|
+
Note that if a record is not embedded and does not
|
2116
|
+
yet have an `id` (usually provided by the server), it
|
2117
|
+
will not be included in the output.
|
2118
|
+
|
2119
|
+
@param {Object} json the JSON hash being built
|
2120
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2121
|
+
@param {Object} meta information about the association
|
2122
|
+
@param {Object} options options passed to `toJSON`
|
2123
|
+
*/
|
2124
|
+
addHasManyToJSON: function(json, data, meta, options) {
|
2125
|
+
var key = meta.key,
|
2126
|
+
manyArray = get(this, key),
|
2127
|
+
records = [],
|
2128
|
+
clientId, id;
|
2129
|
+
|
2130
|
+
if (meta.options.embedded) {
|
2131
|
+
// TODO: Avoid materializing embedded hashes if possible
|
2132
|
+
manyArray.forEach(function(record) {
|
2133
|
+
records.push(record.toJSON(options));
|
2134
|
+
});
|
1627
2135
|
} else {
|
1628
|
-
|
1629
|
-
|
2136
|
+
var clientIds = get(manyArray, 'content');
|
2137
|
+
|
2138
|
+
for (var i=0, l=clientIds.length; i<l; i++) {
|
2139
|
+
clientId = clientIds[i];
|
2140
|
+
id = get(this, 'store').clientIdToId[clientId];
|
2141
|
+
|
2142
|
+
if (id !== undefined) {
|
2143
|
+
records.push(id);
|
2144
|
+
}
|
2145
|
+
}
|
1630
2146
|
}
|
1631
2147
|
|
1632
|
-
|
1633
|
-
}
|
1634
|
-
};
|
2148
|
+
json[key] = records;
|
2149
|
+
},
|
1635
2150
|
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
2151
|
+
/**
|
2152
|
+
Add the value of a `belongsTo` association to the JSON hash.
|
2153
|
+
|
2154
|
+
The default implementation always includes the `id`.
|
2155
|
+
|
2156
|
+
@param {Object} json the JSON hash being built
|
2157
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2158
|
+
@param {Object} meta information about the association
|
2159
|
+
@param {Object} options options passed to `toJSON`
|
2160
|
+
*/
|
2161
|
+
addBelongsToToJSON: function(json, data, meta, options) {
|
2162
|
+
var key = meta.key, value, id;
|
2163
|
+
|
2164
|
+
if (options.embedded) {
|
2165
|
+
key = options.key || get(this, 'namingConvention').keyToJSONKey(key);
|
2166
|
+
value = get(data.record, key);
|
2167
|
+
json[key] = value ? value.toJSON(options) : null;
|
2168
|
+
} else {
|
2169
|
+
key = options.key || get(this, 'namingConvention').foreignKey(key);
|
2170
|
+
id = data.get(key);
|
2171
|
+
json[key] = none(id) ? null : id;
|
2172
|
+
}
|
2173
|
+
},
|
2174
|
+
/**
|
2175
|
+
Create a JSON representation of the record, including its `id`,
|
2176
|
+
attributes and associations. Honor any settings defined on the
|
2177
|
+
attributes or associations (such as `embedded` or `key`).
|
2178
|
+
*/
|
2179
|
+
toJSON: function(options) {
|
2180
|
+
var data = get(this, 'data'),
|
2181
|
+
result = {},
|
2182
|
+
type = this.constructor,
|
2183
|
+
attributes = get(type, 'attributes'),
|
2184
|
+
primaryKey = get(this, 'primaryKey'),
|
2185
|
+
id = get(this, 'id'),
|
2186
|
+
store = get(this, 'store'),
|
2187
|
+
associations;
|
2188
|
+
|
2189
|
+
options = options || {};
|
2190
|
+
|
2191
|
+
// delegate to `addIdToJSON` callback
|
2192
|
+
this.addIdToJSON(result, id, primaryKey);
|
2193
|
+
|
2194
|
+
// delegate to `addAttributesToJSON` callback
|
2195
|
+
this.addAttributesToJSON(result, attributes, data);
|
2196
|
+
|
2197
|
+
associations = get(type, 'associationsByName');
|
2198
|
+
|
2199
|
+
// add associations, delegating to `addHasManyToJSON` and
|
2200
|
+
// `addBelongsToToJSON`.
|
2201
|
+
associations.forEach(function(key, meta) {
|
2202
|
+
if (options.associations && meta.kind === 'hasMany') {
|
2203
|
+
this.addHasManyToJSON(result, data, meta, options);
|
2204
|
+
} else if (meta.kind === 'belongsTo') {
|
2205
|
+
this.addBelongsToToJSON(result, data, meta, options);
|
2206
|
+
}
|
2207
|
+
}, this);
|
2208
|
+
|
2209
|
+
return result;
|
2210
|
+
},
|
2211
|
+
|
2212
|
+
data: Ember.computed(function() {
|
2213
|
+
return new DS._DataProxy(this);
|
2214
|
+
}).cacheable(),
|
2215
|
+
|
2216
|
+
didLoad: Ember.K,
|
2217
|
+
didUpdate: Ember.K,
|
2218
|
+
didCreate: Ember.K,
|
2219
|
+
|
2220
|
+
init: function() {
|
2221
|
+
var stateManager = DS.StateManager.create({
|
2222
|
+
model: this
|
2223
|
+
});
|
2224
|
+
|
2225
|
+
set(this, 'pendingQueue', {});
|
2226
|
+
|
2227
|
+
set(this, 'stateManager', stateManager);
|
2228
|
+
stateManager.goToState('empty');
|
2229
|
+
},
|
2230
|
+
|
2231
|
+
destroy: function() {
|
2232
|
+
if (!get(this, 'isDeleted')) {
|
2233
|
+
this.deleteRecord();
|
2234
|
+
}
|
2235
|
+
this._super();
|
2236
|
+
},
|
2237
|
+
|
2238
|
+
send: function(name, context) {
|
2239
|
+
return get(this, 'stateManager').send(name, context);
|
2240
|
+
},
|
2241
|
+
|
2242
|
+
withTransaction: function(fn) {
|
2243
|
+
var transaction = get(this, 'transaction');
|
2244
|
+
if (transaction) { fn(transaction); }
|
2245
|
+
},
|
2246
|
+
|
2247
|
+
setProperty: function(key, value) {
|
2248
|
+
this.send('setProperty', { key: key, value: value });
|
2249
|
+
},
|
2250
|
+
|
2251
|
+
deleteRecord: function() {
|
2252
|
+
this.send('deleteRecord');
|
2253
|
+
},
|
2254
|
+
|
2255
|
+
waitingOn: function(record) {
|
2256
|
+
this.send('waitingOn', record);
|
2257
|
+
},
|
2258
|
+
|
2259
|
+
notifyHashWasUpdated: function() {
|
2260
|
+
var store = get(this, 'store');
|
2261
|
+
if (store) {
|
2262
|
+
store.hashWasUpdated(this.constructor, get(this, 'clientId'), this);
|
2263
|
+
}
|
2264
|
+
},
|
2265
|
+
|
2266
|
+
unknownProperty: function(key) {
|
2267
|
+
var data = get(this, 'data');
|
2268
|
+
|
2269
|
+
if (data && key in data) {
|
2270
|
+
|
2271
|
+
}
|
2272
|
+
},
|
2273
|
+
|
2274
|
+
setUnknownProperty: function(key, value) {
|
2275
|
+
var data = get(this, 'data');
|
2276
|
+
|
2277
|
+
if (data && key in data) {
|
2278
|
+
|
2279
|
+
} else {
|
2280
|
+
return this._super(key, value);
|
2281
|
+
}
|
2282
|
+
},
|
2283
|
+
|
2284
|
+
namingConvention: {
|
2285
|
+
keyToJSONKey: function(key) {
|
2286
|
+
// TODO: Strip off `is` from the front. Example: `isHipster` becomes `hipster`
|
2287
|
+
return Ember.String.decamelize(key);
|
2288
|
+
},
|
2289
|
+
|
2290
|
+
foreignKey: function(key) {
|
2291
|
+
return Ember.String.decamelize(key) + '_id';
|
2292
|
+
}
|
2293
|
+
},
|
2294
|
+
|
2295
|
+
/** @private */
|
2296
|
+
hashWasUpdated: function() {
|
2297
|
+
// At the end of the run loop, notify model arrays that
|
2298
|
+
// this record has changed so they can re-evaluate its contents
|
2299
|
+
// to determine membership.
|
2300
|
+
Ember.run.once(this, this.notifyHashWasUpdated);
|
2301
|
+
},
|
2302
|
+
|
2303
|
+
dataDidChange: Ember.observer(function() {
|
2304
|
+
var associations = get(this.constructor, 'associationsByName'),
|
2305
|
+
data = get(this, 'data'), store = get(this, 'store'),
|
2306
|
+
idToClientId = store.idToClientId,
|
2307
|
+
cachedValue;
|
2308
|
+
|
2309
|
+
associations.forEach(function(name, association) {
|
2310
|
+
if (association.kind === 'hasMany') {
|
2311
|
+
cachedValue = this.cacheFor(name);
|
2312
|
+
|
2313
|
+
if (cachedValue) {
|
2314
|
+
var ids = data.get(name) || [];
|
2315
|
+
var clientIds = Ember.ArrayUtils.map(ids, function(id) {
|
2316
|
+
return store.clientIdForId(association.type, id);
|
2317
|
+
});
|
2318
|
+
|
2319
|
+
set(cachedValue, 'content', Ember.A(clientIds));
|
2320
|
+
cachedValue.fetch();
|
2321
|
+
}
|
2322
|
+
}
|
2323
|
+
}, this);
|
2324
|
+
}, 'data')
|
2325
|
+
});
|
2326
|
+
|
2327
|
+
// Helper function to generate store aliases.
|
2328
|
+
// This returns a function that invokes the named alias
|
2329
|
+
// on the default store, but injects the class as the
|
2330
|
+
// first parameter.
|
2331
|
+
var storeAlias = function(methodName) {
|
2332
|
+
return function() {
|
2333
|
+
var store = get(DS, 'defaultStore'),
|
2334
|
+
args = [].slice.call(arguments);
|
2335
|
+
|
2336
|
+
args.unshift(this);
|
2337
|
+
return store[methodName].apply(store, args);
|
2338
|
+
};
|
1639
2339
|
};
|
1640
2340
|
|
1641
|
-
DS.
|
1642
|
-
|
1643
|
-
|
2341
|
+
DS.Model.reopenClass({
|
2342
|
+
find: storeAlias('find'),
|
2343
|
+
filter: storeAlias('filter'),
|
2344
|
+
|
2345
|
+
_create: DS.Model.create,
|
2346
|
+
|
2347
|
+
create: function() {
|
2348
|
+
throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.");
|
2349
|
+
},
|
2350
|
+
|
2351
|
+
createRecord: storeAlias('createRecord')
|
2352
|
+
});
|
2353
|
+
|
2354
|
+
})();
|
2355
|
+
|
2356
|
+
|
2357
|
+
|
2358
|
+
(function() {
|
2359
|
+
var get = Ember.get, getPath = Ember.getPath;
|
2360
|
+
DS.Model.reopenClass({
|
2361
|
+
attributes: Ember.computed(function() {
|
2362
|
+
var map = Ember.Map.create();
|
2363
|
+
|
2364
|
+
this.eachComputedProperty(function(name, meta) {
|
2365
|
+
if (meta.isAttribute) { map.set(name, meta); }
|
2366
|
+
});
|
2367
|
+
|
2368
|
+
return map;
|
2369
|
+
}).cacheable(),
|
2370
|
+
|
2371
|
+
processAttributeKeys: function() {
|
2372
|
+
if (this.processedAttributeKeys) { return; }
|
2373
|
+
|
2374
|
+
var namingConvention = this.proto().namingConvention;
|
2375
|
+
|
2376
|
+
this.eachComputedProperty(function(name, meta) {
|
2377
|
+
if (meta.isAttribute && !meta.options.key) {
|
2378
|
+
meta.options.key = namingConvention.keyToJSONKey(name, this);
|
2379
|
+
}
|
2380
|
+
}, this);
|
2381
|
+
}
|
2382
|
+
});
|
2383
|
+
|
2384
|
+
DS.attr = function(type, options) {
|
2385
|
+
var transform = DS.attr.transforms[type];
|
2386
|
+
|
2387
|
+
|
2388
|
+
var transformFrom = transform.from;
|
2389
|
+
var transformTo = transform.to;
|
2390
|
+
|
2391
|
+
options = options || {};
|
2392
|
+
|
2393
|
+
var meta = {
|
2394
|
+
type: type,
|
2395
|
+
isAttribute: true,
|
2396
|
+
options: options,
|
2397
|
+
|
2398
|
+
// this will ensure that the key always takes naming
|
2399
|
+
// conventions into consideration.
|
2400
|
+
key: function(recordType) {
|
2401
|
+
recordType.processAttributeKeys();
|
2402
|
+
return options.key;
|
2403
|
+
}
|
2404
|
+
};
|
2405
|
+
|
2406
|
+
return Ember.computed(function(key, value) {
|
2407
|
+
var data;
|
2408
|
+
|
2409
|
+
key = meta.key(this.constructor);
|
2410
|
+
|
2411
|
+
if (arguments.length === 2) {
|
2412
|
+
value = transformTo(value);
|
2413
|
+
this.setProperty(key, value);
|
2414
|
+
} else {
|
2415
|
+
data = get(this, 'data');
|
2416
|
+
value = get(data, key);
|
2417
|
+
|
2418
|
+
if (value === undefined) {
|
2419
|
+
value = options.defaultValue;
|
2420
|
+
}
|
2421
|
+
}
|
2422
|
+
|
2423
|
+
return transformFrom(value);
|
2424
|
+
// `data` is never set directly. However, it may be
|
2425
|
+
// invalidated from the state manager's setData
|
2426
|
+
// event.
|
2427
|
+
}).property('data').cacheable().meta(meta);
|
1644
2428
|
};
|
1645
2429
|
|
1646
2430
|
DS.attr.transforms = {
|
1647
2431
|
string: {
|
1648
2432
|
from: function(serialized) {
|
1649
|
-
return
|
2433
|
+
return Ember.none(serialized) ? null : String(serialized);
|
1650
2434
|
},
|
1651
2435
|
|
1652
2436
|
to: function(deserialized) {
|
1653
|
-
return
|
2437
|
+
return Ember.none(deserialized) ? null : String(deserialized);
|
1654
2438
|
}
|
1655
2439
|
},
|
1656
2440
|
|
1657
|
-
|
2441
|
+
number: {
|
1658
2442
|
from: function(serialized) {
|
1659
|
-
return
|
2443
|
+
return Ember.none(serialized) ? null : Number(serialized);
|
1660
2444
|
},
|
1661
2445
|
|
1662
2446
|
to: function(deserialized) {
|
1663
|
-
return
|
2447
|
+
return Ember.none(deserialized) ? null : Number(deserialized);
|
1664
2448
|
}
|
1665
2449
|
},
|
1666
2450
|
|
1667
|
-
boolean: {
|
2451
|
+
'boolean': {
|
1668
2452
|
from: function(serialized) {
|
1669
2453
|
return Boolean(serialized);
|
1670
2454
|
},
|
@@ -1722,27 +2506,532 @@ DS.attr.transforms = {
|
|
1722
2506
|
}
|
1723
2507
|
};
|
1724
2508
|
|
1725
|
-
})({});
|
1726
2509
|
|
2510
|
+
})();
|
1727
2511
|
|
1728
|
-
(function(exports) {
|
1729
|
-
//Copyright (C) 2011 by Living Social, Inc.
|
1730
2512
|
|
1731
|
-
//Permission is hereby granted, free of charge, to any person obtaining a copy of
|
1732
|
-
//this software and associated documentation files (the "Software"), to deal in
|
1733
|
-
//the Software without restriction, including without limitation the rights to
|
1734
|
-
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
1735
|
-
//of the Software, and to permit persons to whom the Software is furnished to do
|
1736
|
-
//so, subject to the following conditions:
|
1737
2513
|
|
1738
|
-
|
1739
|
-
|
2514
|
+
(function() {
|
2515
|
+
|
2516
|
+
})();
|
2517
|
+
|
2518
|
+
|
2519
|
+
|
2520
|
+
(function() {
|
2521
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath,
|
2522
|
+
none = Ember.none;
|
2523
|
+
|
2524
|
+
var embeddedFindRecord = function(store, type, data, key, one) {
|
2525
|
+
var association = get(data, key);
|
2526
|
+
return none(association) ? undefined : store.load(type, association).id;
|
2527
|
+
};
|
2528
|
+
|
2529
|
+
var referencedFindRecord = function(store, type, data, key, one) {
|
2530
|
+
return get(data, key);
|
2531
|
+
};
|
2532
|
+
|
2533
|
+
var hasAssociation = function(type, options, one) {
|
2534
|
+
options = options || {};
|
2535
|
+
|
2536
|
+
var embedded = options.embedded,
|
2537
|
+
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
2538
|
+
|
2539
|
+
var meta = { type: type, isAssociation: true, options: options, kind: 'belongsTo' };
|
2540
|
+
|
2541
|
+
return Ember.computed(function(key, value) {
|
2542
|
+
var data = get(this, 'data'), ids, id, association,
|
2543
|
+
store = get(this, 'store');
|
2544
|
+
|
2545
|
+
if (typeof type === 'string') {
|
2546
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2547
|
+
}
|
2548
|
+
|
2549
|
+
if (arguments.length === 2) {
|
2550
|
+
key = options.key || get(this, 'namingConvention').foreignKey(key);
|
2551
|
+
this.send('setAssociation', { key: key, value: value === null ? null : get(value, 'clientId') });
|
2552
|
+
//data.setAssociation(key, get(value, 'clientId'));
|
2553
|
+
// put the client id in `key` in the data hash
|
2554
|
+
return value;
|
2555
|
+
} else {
|
2556
|
+
// Embedded belongsTo associations should not look for
|
2557
|
+
// a foreign key.
|
2558
|
+
if (embedded) {
|
2559
|
+
key = options.key || key;
|
2560
|
+
|
2561
|
+
// Non-embedded associations should look for a foreign key.
|
2562
|
+
// For example, instead of person, we might look for person_id
|
2563
|
+
} else {
|
2564
|
+
key = options.key || get(this, 'namingConvention').foreignKey(key);
|
2565
|
+
}
|
2566
|
+
id = findRecord(store, type, data, key, true);
|
2567
|
+
association = id ? store.find(type, id) : null;
|
2568
|
+
}
|
2569
|
+
|
2570
|
+
return association;
|
2571
|
+
}).property('data').cacheable().meta(meta);
|
2572
|
+
};
|
2573
|
+
|
2574
|
+
DS.belongsTo = function(type, options) {
|
2575
|
+
|
2576
|
+
return hasAssociation(type, options);
|
2577
|
+
};
|
2578
|
+
|
2579
|
+
})();
|
2580
|
+
|
2581
|
+
|
2582
|
+
|
2583
|
+
(function() {
|
2584
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
2585
|
+
var embeddedFindRecord = function(store, type, data, key) {
|
2586
|
+
var association = get(data, key);
|
2587
|
+
return association ? store.loadMany(type, association).ids : [];
|
2588
|
+
};
|
2589
|
+
|
2590
|
+
var referencedFindRecord = function(store, type, data, key, one) {
|
2591
|
+
return get(data, key);
|
2592
|
+
};
|
2593
|
+
|
2594
|
+
var hasAssociation = function(type, options) {
|
2595
|
+
options = options || {};
|
2596
|
+
|
2597
|
+
var embedded = options.embedded,
|
2598
|
+
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
2599
|
+
|
2600
|
+
var meta = { type: type, isAssociation: true, options: options, kind: 'hasMany' };
|
2601
|
+
|
2602
|
+
return Ember.computed(function(key, value) {
|
2603
|
+
var data = get(this, 'data'),
|
2604
|
+
store = get(this, 'store'),
|
2605
|
+
ids, id, association;
|
2606
|
+
|
2607
|
+
if (typeof type === 'string') {
|
2608
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2609
|
+
}
|
2610
|
+
|
2611
|
+
key = options.key || key;
|
2612
|
+
ids = findRecord(store, type, data, key);
|
2613
|
+
association = store.findMany(type, ids);
|
2614
|
+
set(association, 'parentRecord', this);
|
2615
|
+
|
2616
|
+
return association;
|
2617
|
+
}).property().cacheable().meta(meta);
|
2618
|
+
};
|
2619
|
+
|
2620
|
+
DS.hasMany = function(type, options) {
|
2621
|
+
|
2622
|
+
return hasAssociation(type, options);
|
2623
|
+
};
|
2624
|
+
|
2625
|
+
})();
|
2626
|
+
|
2627
|
+
|
2628
|
+
|
2629
|
+
(function() {
|
2630
|
+
var get = Ember.get, getPath = Ember.getPath;
|
2631
|
+
|
2632
|
+
DS.Model.reopenClass({
|
2633
|
+
typeForAssociation: function(name) {
|
2634
|
+
var association = get(this, 'associationsByName').get(name);
|
2635
|
+
return association && association.type;
|
2636
|
+
},
|
2637
|
+
|
2638
|
+
associations: Ember.computed(function() {
|
2639
|
+
var map = Ember.Map.create();
|
2640
|
+
|
2641
|
+
this.eachComputedProperty(function(name, meta) {
|
2642
|
+
if (meta.isAssociation) {
|
2643
|
+
var type = meta.type,
|
2644
|
+
typeList = map.get(type);
|
2645
|
+
|
2646
|
+
if (typeof type === 'string') {
|
2647
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2648
|
+
meta.type = type;
|
2649
|
+
}
|
2650
|
+
|
2651
|
+
if (!typeList) {
|
2652
|
+
typeList = [];
|
2653
|
+
map.set(type, typeList);
|
2654
|
+
}
|
2655
|
+
|
2656
|
+
typeList.push({ name: name, kind: meta.kind });
|
2657
|
+
}
|
2658
|
+
});
|
2659
|
+
|
2660
|
+
return map;
|
2661
|
+
}).cacheable(),
|
2662
|
+
|
2663
|
+
associationsByName: Ember.computed(function() {
|
2664
|
+
var map = Ember.Map.create(), type;
|
2665
|
+
|
2666
|
+
this.eachComputedProperty(function(name, meta) {
|
2667
|
+
if (meta.isAssociation) {
|
2668
|
+
meta.key = name;
|
2669
|
+
type = meta.type;
|
2670
|
+
|
2671
|
+
if (typeof type === 'string') {
|
2672
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2673
|
+
meta.type = type;
|
2674
|
+
}
|
2675
|
+
|
2676
|
+
map.set(name, meta);
|
2677
|
+
}
|
2678
|
+
});
|
2679
|
+
|
2680
|
+
return map;
|
2681
|
+
}).cacheable()
|
2682
|
+
});
|
2683
|
+
|
2684
|
+
})();
|
2685
|
+
|
2686
|
+
|
2687
|
+
|
2688
|
+
(function() {
|
2689
|
+
|
2690
|
+
})();
|
2691
|
+
|
2692
|
+
|
2693
|
+
|
2694
|
+
(function() {
|
2695
|
+
DS.Adapter = Ember.Object.extend({
|
2696
|
+
commit: function(store, commitDetails) {
|
2697
|
+
commitDetails.updated.eachType(function(type, array) {
|
2698
|
+
this.updateRecords(store, type, array.slice());
|
2699
|
+
}, this);
|
2700
|
+
|
2701
|
+
commitDetails.created.eachType(function(type, array) {
|
2702
|
+
this.createRecords(store, type, array.slice());
|
2703
|
+
}, this);
|
2704
|
+
|
2705
|
+
commitDetails.deleted.eachType(function(type, array) {
|
2706
|
+
this.deleteRecords(store, type, array.slice());
|
2707
|
+
}, this);
|
2708
|
+
},
|
2709
|
+
|
2710
|
+
createRecords: function(store, type, models) {
|
2711
|
+
models.forEach(function(model) {
|
2712
|
+
this.createRecord(store, type, model);
|
2713
|
+
}, this);
|
2714
|
+
},
|
2715
|
+
|
2716
|
+
updateRecords: function(store, type, models) {
|
2717
|
+
models.forEach(function(model) {
|
2718
|
+
this.updateRecord(store, type, model);
|
2719
|
+
}, this);
|
2720
|
+
},
|
2721
|
+
|
2722
|
+
deleteRecords: function(store, type, models) {
|
2723
|
+
models.forEach(function(model) {
|
2724
|
+
this.deleteRecord(store, type, model);
|
2725
|
+
}, this);
|
2726
|
+
},
|
2727
|
+
|
2728
|
+
findMany: function(store, type, ids) {
|
2729
|
+
ids.forEach(function(id) {
|
2730
|
+
this.find(store, type, id);
|
2731
|
+
}, this);
|
2732
|
+
}
|
2733
|
+
});
|
2734
|
+
|
2735
|
+
})();
|
2736
|
+
|
2737
|
+
|
2738
|
+
|
2739
|
+
(function() {
|
2740
|
+
DS.fixtureAdapter = DS.Adapter.create({
|
2741
|
+
find: function(store, type, id) {
|
2742
|
+
var fixtures = type.FIXTURES;
|
2743
|
+
|
2744
|
+
if (fixtures.hasLoaded) { return; }
|
2745
|
+
|
2746
|
+
setTimeout(function() {
|
2747
|
+
store.loadMany(type, fixtures);
|
2748
|
+
fixtures.hasLoaded = true;
|
2749
|
+
}, 300);
|
2750
|
+
},
|
2751
|
+
|
2752
|
+
findMany: function() {
|
2753
|
+
this.find.apply(this, arguments);
|
2754
|
+
},
|
2755
|
+
|
2756
|
+
findAll: function(store, type) {
|
2757
|
+
var fixtures = type.FIXTURES;
|
2758
|
+
|
2759
|
+
|
2760
|
+
var ids = fixtures.map(function(item, index, self){ return item.id; });
|
2761
|
+
store.loadMany(type, ids, fixtures);
|
2762
|
+
}
|
2763
|
+
|
2764
|
+
});
|
2765
|
+
|
2766
|
+
})();
|
2767
|
+
|
2768
|
+
|
2769
|
+
|
2770
|
+
(function() {
|
2771
|
+
/*global jQuery*/
|
2772
|
+
|
2773
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
2774
|
+
|
2775
|
+
DS.RESTAdapter = DS.Adapter.extend({
|
2776
|
+
createRecord: function(store, type, model) {
|
2777
|
+
var root = this.rootForType(type);
|
2778
|
+
|
2779
|
+
var data = {};
|
2780
|
+
data[root] = model.toJSON();
|
2781
|
+
|
2782
|
+
this.ajax(this.buildURL(root), "POST", {
|
2783
|
+
data: data,
|
2784
|
+
success: function(json) {
|
2785
|
+
this.sideload(store, type, json, root);
|
2786
|
+
store.didCreateRecord(model, json[root]);
|
2787
|
+
}
|
2788
|
+
});
|
2789
|
+
},
|
2790
|
+
|
2791
|
+
createRecords: function(store, type, models) {
|
2792
|
+
if (get(this, 'bulkCommit') === false) {
|
2793
|
+
return this._super(store, type, models);
|
2794
|
+
}
|
2795
|
+
|
2796
|
+
var root = this.rootForType(type),
|
2797
|
+
plural = this.pluralize(root);
|
2798
|
+
|
2799
|
+
var data = {};
|
2800
|
+
data[plural] = models.map(function(model) {
|
2801
|
+
return model.toJSON();
|
2802
|
+
});
|
2803
|
+
|
2804
|
+
this.ajax(this.buildURL(root), "POST", {
|
2805
|
+
data: data,
|
2806
|
+
|
2807
|
+
success: function(json) {
|
2808
|
+
this.sideload(store, type, json, plural);
|
2809
|
+
store.didCreateRecords(type, models, json[plural]);
|
2810
|
+
}
|
2811
|
+
});
|
2812
|
+
},
|
2813
|
+
|
2814
|
+
updateRecord: function(store, type, model) {
|
2815
|
+
var id = get(model, 'id');
|
2816
|
+
var root = this.rootForType(type);
|
2817
|
+
|
2818
|
+
var data = {};
|
2819
|
+
data[root] = model.toJSON();
|
2820
|
+
|
2821
|
+
this.ajax(this.buildURL(root, id), "PUT", {
|
2822
|
+
data: data,
|
2823
|
+
success: function(json) {
|
2824
|
+
this.sideload(store, type, json, root);
|
2825
|
+
store.didUpdateRecord(model, json && json[root]);
|
2826
|
+
}
|
2827
|
+
});
|
2828
|
+
},
|
2829
|
+
|
2830
|
+
updateRecords: function(store, type, models) {
|
2831
|
+
if (get(this, 'bulkCommit') === false) {
|
2832
|
+
return this._super(store, type, models);
|
2833
|
+
}
|
2834
|
+
|
2835
|
+
var root = this.rootForType(type),
|
2836
|
+
plural = this.pluralize(root);
|
2837
|
+
|
2838
|
+
var data = {};
|
2839
|
+
data[plural] = models.map(function(model) {
|
2840
|
+
return model.toJSON();
|
2841
|
+
});
|
2842
|
+
|
2843
|
+
this.ajax(this.buildURL(root, "bulk"), "PUT", {
|
2844
|
+
data: data,
|
2845
|
+
success: function(json) {
|
2846
|
+
this.sideload(store, type, json, plural);
|
2847
|
+
store.didUpdateRecords(models, json[plural]);
|
2848
|
+
}
|
2849
|
+
});
|
2850
|
+
},
|
2851
|
+
|
2852
|
+
deleteRecord: function(store, type, model) {
|
2853
|
+
var id = get(model, 'id');
|
2854
|
+
var root = this.rootForType(type);
|
2855
|
+
|
2856
|
+
this.ajax(this.buildURL(root, id), "DELETE", {
|
2857
|
+
success: function(json) {
|
2858
|
+
if (json) { this.sideload(store, type, json); }
|
2859
|
+
store.didDeleteRecord(model);
|
2860
|
+
}
|
2861
|
+
});
|
2862
|
+
},
|
2863
|
+
|
2864
|
+
deleteRecords: function(store, type, models) {
|
2865
|
+
if (get(this, 'bulkCommit') === false) {
|
2866
|
+
return this._super(store, type, models);
|
2867
|
+
}
|
2868
|
+
|
2869
|
+
var root = this.rootForType(type),
|
2870
|
+
plural = this.pluralize(root);
|
2871
|
+
|
2872
|
+
var data = {};
|
2873
|
+
data[plural] = models.map(function(model) {
|
2874
|
+
return get(model, 'id');
|
2875
|
+
});
|
2876
|
+
|
2877
|
+
this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
|
2878
|
+
data: data,
|
2879
|
+
success: function(json) {
|
2880
|
+
if (json) { this.sideload(store, type, json); }
|
2881
|
+
store.didDeleteRecords(models);
|
2882
|
+
}
|
2883
|
+
});
|
2884
|
+
},
|
2885
|
+
|
2886
|
+
find: function(store, type, id) {
|
2887
|
+
var root = this.rootForType(type);
|
2888
|
+
|
2889
|
+
this.ajax(this.buildURL(root, id), "GET", {
|
2890
|
+
success: function(json) {
|
2891
|
+
store.load(type, json[root]);
|
2892
|
+
this.sideload(store, type, json, root);
|
2893
|
+
}
|
2894
|
+
});
|
2895
|
+
},
|
2896
|
+
|
2897
|
+
findMany: function(store, type, ids) {
|
2898
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
2899
|
+
|
2900
|
+
this.ajax(this.buildURL(root), "GET", {
|
2901
|
+
data: { ids: ids },
|
2902
|
+
success: function(json) {
|
2903
|
+
store.loadMany(type, ids, json[plural]);
|
2904
|
+
this.sideload(store, type, json, plural);
|
2905
|
+
}
|
2906
|
+
});
|
2907
|
+
},
|
2908
|
+
|
2909
|
+
findAll: function(store, type) {
|
2910
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
2911
|
+
|
2912
|
+
this.ajax(this.buildURL(root), "GET", {
|
2913
|
+
success: function(json) {
|
2914
|
+
store.loadMany(type, json[plural]);
|
2915
|
+
this.sideload(store, type, json, plural);
|
2916
|
+
}
|
2917
|
+
});
|
2918
|
+
},
|
2919
|
+
|
2920
|
+
findQuery: function(store, type, query, modelArray) {
|
2921
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
2922
|
+
|
2923
|
+
this.ajax(this.buildURL(root), "GET", {
|
2924
|
+
data: query,
|
2925
|
+
success: function(json) {
|
2926
|
+
modelArray.load(json[plural]);
|
2927
|
+
this.sideload(store, type, json, plural);
|
2928
|
+
}
|
2929
|
+
});
|
2930
|
+
},
|
2931
|
+
|
2932
|
+
// HELPERS
|
2933
|
+
|
2934
|
+
plurals: {},
|
2935
|
+
|
2936
|
+
// define a plurals hash in your subclass to define
|
2937
|
+
// special-case pluralization
|
2938
|
+
pluralize: function(name) {
|
2939
|
+
return this.plurals[name] || name + "s";
|
2940
|
+
},
|
2941
|
+
|
2942
|
+
rootForType: function(type) {
|
2943
|
+
if (type.url) { return type.url; }
|
2944
|
+
|
2945
|
+
// use the last part of the name as the URL
|
2946
|
+
var parts = type.toString().split(".");
|
2947
|
+
var name = parts[parts.length - 1];
|
2948
|
+
return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
|
2949
|
+
},
|
2950
|
+
|
2951
|
+
ajax: function(url, type, hash) {
|
2952
|
+
hash.url = url;
|
2953
|
+
hash.type = type;
|
2954
|
+
hash.dataType = 'json';
|
2955
|
+
hash.contentType = 'application/json';
|
2956
|
+
hash.context = this;
|
2957
|
+
|
2958
|
+
if (hash.data && type !== 'GET') {
|
2959
|
+
hash.data = JSON.stringify(hash.data);
|
2960
|
+
}
|
2961
|
+
|
2962
|
+
jQuery.ajax(hash);
|
2963
|
+
},
|
2964
|
+
|
2965
|
+
sideload: function(store, type, json, root) {
|
2966
|
+
var sideloadedType, mappings;
|
2967
|
+
|
2968
|
+
for (var prop in json) {
|
2969
|
+
if (!json.hasOwnProperty(prop)) { continue; }
|
2970
|
+
if (prop === root) { continue; }
|
2971
|
+
|
2972
|
+
sideloadedType = type.typeForAssociation(prop);
|
2973
|
+
|
2974
|
+
if (!sideloadedType) {
|
2975
|
+
mappings = get(this, 'mappings');
|
2976
|
+
|
2977
|
+
|
2978
|
+
sideloadedType = get(get(this, 'mappings'), prop);
|
2979
|
+
|
2980
|
+
}
|
2981
|
+
|
2982
|
+
this.loadValue(store, sideloadedType, json[prop]);
|
2983
|
+
}
|
2984
|
+
},
|
2985
|
+
|
2986
|
+
loadValue: function(store, type, value) {
|
2987
|
+
if (value instanceof Array) {
|
2988
|
+
store.loadMany(type, value);
|
2989
|
+
} else {
|
2990
|
+
store.load(type, value);
|
2991
|
+
}
|
2992
|
+
},
|
2993
|
+
|
2994
|
+
buildURL: function(model, suffix) {
|
2995
|
+
var url = [""];
|
2996
|
+
|
2997
|
+
if (this.namespace !== undefined) {
|
2998
|
+
url.push(this.namespace);
|
2999
|
+
}
|
3000
|
+
|
3001
|
+
url.push(this.pluralize(model));
|
3002
|
+
if (suffix !== undefined) {
|
3003
|
+
url.push(suffix);
|
3004
|
+
}
|
3005
|
+
|
3006
|
+
return url.join("/");
|
3007
|
+
}
|
3008
|
+
});
|
3009
|
+
|
3010
|
+
|
3011
|
+
})();
|
3012
|
+
|
3013
|
+
|
3014
|
+
|
3015
|
+
(function() {
|
3016
|
+
//Copyright (C) 2011 by Living Social, Inc.
|
3017
|
+
|
3018
|
+
//Permission is hereby granted, free of charge, to any person obtaining a copy of
|
3019
|
+
//this software and associated documentation files (the "Software"), to deal in
|
3020
|
+
//the Software without restriction, including without limitation the rights to
|
3021
|
+
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
3022
|
+
//of the Software, and to permit persons to whom the Software is furnished to do
|
3023
|
+
//so, subject to the following conditions:
|
3024
|
+
|
3025
|
+
//The above copyright notice and this permission notice shall be included in all
|
3026
|
+
//copies or substantial portions of the Software.
|
3027
|
+
|
3028
|
+
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
3029
|
+
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
3030
|
+
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
3031
|
+
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
3032
|
+
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
3033
|
+
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
3034
|
+
//SOFTWARE.
|
3035
|
+
|
3036
|
+
})();
|
1740
3037
|
|
1741
|
-
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1742
|
-
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
1743
|
-
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
1744
|
-
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
1745
|
-
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
1746
|
-
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
1747
|
-
//SOFTWARE.
|
1748
|
-
})({});
|