rightnow_oms 0.1.4 → 0.1.6
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/CHANGELOG +12 -0
- data/README.md +145 -66
- data/app/assets/javascripts/rightnow_oms/app/app.js.coffee +12 -4
- data/app/assets/javascripts/rightnow_oms/app/controllers/cart.js.coffee +17 -32
- data/app/assets/javascripts/rightnow_oms/app/models/cart.js.coffee +50 -28
- data/app/assets/javascripts/rightnow_oms/app/models/cart_item.js.coffee +28 -49
- data/app/assets/javascripts/rightnow_oms/app/templates/cart_items/show.handlebars +30 -0
- data/app/assets/javascripts/rightnow_oms/app/templates/cart_items/{show_in_detail.hjs → show_in_detail.handlebars} +3 -3
- data/app/assets/javascripts/rightnow_oms/app/templates/carts/{show.hjs → show.handlebars} +4 -3
- data/app/assets/javascripts/rightnow_oms/app/templates/carts/show_cartable_count.handlebars +2 -0
- data/app/assets/javascripts/rightnow_oms/app/templates/carts/show_in_detail.handlebars +30 -0
- data/app/assets/javascripts/rightnow_oms/app/views/cart_items/show_in_detail.js.coffee +4 -1
- data/app/assets/javascripts/rightnow_oms/application.js.coffee +10 -0
- data/app/assets/javascripts/rightnow_oms/cart_items.js +10 -0
- data/app/assets/javascripts/rightnow_oms/config/app.js.coffee +5 -0
- data/app/assets/javascripts/rightnow_oms/config/locales/en.js.coffee +24 -0
- data/app/assets/javascripts/rightnow_oms/config/locales/zh_CN.js.coffee +24 -0
- data/app/assets/javascripts/rightnow_oms/lib/ember/data/my_rest_adapter.js.coffee +2 -167
- data/app/assets/javascripts/rightnow_oms/vendor/cldr.js +240 -0
- data/app/assets/javascripts/rightnow_oms/vendor/ember-data.js +1530 -587
- data/app/assets/javascripts/rightnow_oms/vendor/ember-data.min.js +1 -1
- data/app/assets/javascripts/rightnow_oms/vendor/ember-i18n.js +123 -0
- data/app/assets/javascripts/rightnow_oms/vendor/{ember-0.9.5.js → ember.js} +1584 -928
- data/app/assets/javascripts/rightnow_oms/vendor/ember.min.js +5 -5
- data/app/assets/stylesheets/rightnow_oms/carts.css.scss +93 -85
- data/app/controllers/rightnow_oms/cart_items_controller.rb +2 -2
- data/app/controllers/rightnow_oms/orders_controller.rb +27 -5
- data/app/models/rightnow_oms/cart.rb +1 -0
- data/app/models/rightnow_oms/cart_item.rb +1 -0
- data/app/models/rightnow_oms/order.rb +26 -2
- data/app/views/rightnow_oms/cart_items/_list.html.haml +24 -0
- data/app/views/rightnow_oms/orders/show.html.haml +2 -0
- data/config/initializers/rightnow_oms.rb +4 -0
- data/config/initializers/validates_timeliness.rb +40 -0
- data/config/locales/validates_timeliness.en.yml +16 -0
- data/config/locales/validates_timeliness.zh-CN.yml +16 -0
- data/db/migrate/20120224051751_add_required_arrival_time_to_rightnow_oms_orders.rb +6 -0
- data/db/migrate/20120302085200_add_tastes_to_rightnow_oms_order_items.rb +6 -0
- data/lib/rightnow_oms.rb +5 -1
- data/lib/rightnow_oms/controller_extension.rb +33 -0
- data/lib/rightnow_oms/controller_helpers.rb +4 -21
- data/lib/rightnow_oms/version.rb +1 -1
- metadata +71 -35
- data/app/assets/javascripts/rightnow_oms/app/templates/cart_items/show.hjs +0 -23
- data/app/assets/javascripts/rightnow_oms/app/templates/carts/show_cartable_count.hjs +0 -1
- data/app/assets/javascripts/rightnow_oms/app/templates/carts/show_in_detail.hjs +0 -27
- data/app/assets/javascripts/rightnow_oms/application.js +0 -8
@@ -0,0 +1,240 @@
|
|
1
|
+
(function(globals) {
|
2
|
+
|
3
|
+
// CLDR Pluralization Data
|
4
|
+
// see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
|
5
|
+
|
6
|
+
// The six plural forms. Not all languages use all six forms.
|
7
|
+
var Zero = 'zero',
|
8
|
+
One = 'one',
|
9
|
+
Two = 'two',
|
10
|
+
Few = 'few',
|
11
|
+
Many = 'many',
|
12
|
+
Other = 'other',
|
13
|
+
Data = {};
|
14
|
+
|
15
|
+
function isInt(value) {
|
16
|
+
return value << 0 === value;
|
17
|
+
}
|
18
|
+
|
19
|
+
function isAmong(value, array) {
|
20
|
+
for ( var i = 0; i < array.length; ++i ) {
|
21
|
+
if (array[i] === value) { return true; }
|
22
|
+
}
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
|
26
|
+
function define(languages, rule) {
|
27
|
+
for ( var i = 0; i < languages.length; ++i ) {
|
28
|
+
Data[ languages[i] ] = rule;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
define([
|
33
|
+
'az', 'bm', 'my', 'zh', 'dz', 'ka', 'hu', 'ig', 'id', 'ja', 'jv', 'kea',
|
34
|
+
'kn', 'km', 'ko', 'ses', 'lo', 'kde', 'ms', 'fa', 'root', 'sah', 'sg',
|
35
|
+
'ii', 'th', 'bo', 'to', 'tr', 'vi', 'wo', 'yo'
|
36
|
+
], function(n) {
|
37
|
+
return Other;
|
38
|
+
});
|
39
|
+
|
40
|
+
define([ 'gv' ], function(n) {
|
41
|
+
if ( isAmong(n % 10, [ 1, 2 ]) || n % 20 === 0 ) { return One; }
|
42
|
+
return Other;
|
43
|
+
});
|
44
|
+
|
45
|
+
define([ 'tzm' ], function(n) {
|
46
|
+
if ( n === 0 || n === 1 ) { return One; }
|
47
|
+
if ( isInt(n) && n >= 11 && n <= 99 ) { return One; }
|
48
|
+
return Other;
|
49
|
+
});
|
50
|
+
|
51
|
+
define([ 'mk' ], function(n) {
|
52
|
+
return n % 10 === 1 && n !== 11 ? One : Other;
|
53
|
+
});
|
54
|
+
|
55
|
+
define([ 'fr', 'ff', 'kab' ], function(n) {
|
56
|
+
return n >= 0 && n < 2 ? One : Other;
|
57
|
+
});
|
58
|
+
|
59
|
+
define([
|
60
|
+
'ak', 'am', 'bh', 'fil', 'guw', 'hi', 'ln', 'mg', 'nso', 'tl', 'ti', 'wa'
|
61
|
+
], function(n) {
|
62
|
+
return n === 0 || n === 1 ? One : Other;
|
63
|
+
});
|
64
|
+
|
65
|
+
define([
|
66
|
+
'af', 'sq', 'eu', 'bem', 'bn', 'brx', 'bg', 'ca', 'chr', 'cgg', 'da', 'dv',
|
67
|
+
'nl', 'en', 'eo', 'et', 'ee', 'fo', 'fi', 'fur', 'gl', 'lg', 'de', 'el',
|
68
|
+
'gu', 'ha', 'haw', 'he', 'is', 'it', 'kl', 'kk', 'ku', 'lb', 'ml', 'mr',
|
69
|
+
'mas', 'mn', 'nah', 'ne', 'no', 'nb', 'nn', 'nyn', 'or', 'om', 'pap', 'ps',
|
70
|
+
'pt', 'pa', 'rm', 'ssy', 'saq', 'xog', 'so', 'es', 'sw', 'sv', 'gsw',
|
71
|
+
'syr', 'ta', 'te', 'tk', 'ur', 'wae', 'fy', 'zu'
|
72
|
+
], function(n) {
|
73
|
+
return n === 1 ? One : Other;
|
74
|
+
});
|
75
|
+
|
76
|
+
define([ 'lv' ], function(n) {
|
77
|
+
if (n === 0) { return Zero; }
|
78
|
+
if (n % 10 === 1 && n % 100 !== 11) { return One; }
|
79
|
+
return Other;
|
80
|
+
});
|
81
|
+
|
82
|
+
define([ 'ksh' ], function(n) {
|
83
|
+
if (n === 0) { return Zero; }
|
84
|
+
if (n === 1) { return One; }
|
85
|
+
return Other;
|
86
|
+
});
|
87
|
+
|
88
|
+
define([ 'lag' ], function(n) {
|
89
|
+
if (n === 0) { return Zero; }
|
90
|
+
if (n > 0 && n < 2) { return One; }
|
91
|
+
return Other;
|
92
|
+
});
|
93
|
+
|
94
|
+
define([
|
95
|
+
'kw', 'smn', 'iu', 'ga', 'smj', 'se', 'smi', 'sms', 'sma'
|
96
|
+
], function(n) {
|
97
|
+
if (n === 1) { return One; }
|
98
|
+
if (n === 2) { return Two; }
|
99
|
+
return Other;
|
100
|
+
});
|
101
|
+
|
102
|
+
define([
|
103
|
+
'be', 'bs', 'hr', 'ru', 'sr', 'sh', 'uk'
|
104
|
+
], function(n) {
|
105
|
+
var mod10 = n % 10,
|
106
|
+
mod100 = n % 100;
|
107
|
+
|
108
|
+
if ( mod10 === 1 && n % 100 !== 11 ) { return One; }
|
109
|
+
|
110
|
+
if ( isAmong(mod10, [ 2, 3, 4 ]) &&
|
111
|
+
!isAmong(mod100, [ 12, 13, 14 ]) ) { return Few; }
|
112
|
+
|
113
|
+
if ( isAmong(mod10, [ 0, 5, 6, 7, 8, 9 ]) ||
|
114
|
+
isAmong(mod100, [ 11, 12, 13, 14 ]) ) { return Many; }
|
115
|
+
|
116
|
+
return Other;
|
117
|
+
});
|
118
|
+
|
119
|
+
define([ 'pl' ], function(n) {
|
120
|
+
var mod10 = n % 10,
|
121
|
+
mod100 = n % 100;
|
122
|
+
|
123
|
+
if ( n === 1 ) { return One; }
|
124
|
+
|
125
|
+
if ( isAmong(mod10, [ 2, 3, 4 ]) &&
|
126
|
+
!isAmong(mod100, [ 12, 13, 14 ]) ) { return Few; }
|
127
|
+
|
128
|
+
if ( isAmong(mod10, [ 0, 1, 5, 6, 7, 8, 9 ]) ||
|
129
|
+
isAmong(mod100, [ 12, 13, 14 ]) ) { return Many; }
|
130
|
+
|
131
|
+
return Other;
|
132
|
+
});
|
133
|
+
|
134
|
+
define([ 'lt' ], function(n) {
|
135
|
+
var mod10 = n % 10,
|
136
|
+
mod100 = n % 100;
|
137
|
+
|
138
|
+
if ( mod10 === 1 && mod100 !== 11 ) { return One; }
|
139
|
+
|
140
|
+
if ( isInt(n) &&
|
141
|
+
mod10 >= 2 && mod10 <= 9 &&
|
142
|
+
mod100 >= 12 && mod100 <= 19 ) { return Few; }
|
143
|
+
|
144
|
+
return Other;
|
145
|
+
});
|
146
|
+
|
147
|
+
define([ 'shi' ], function(n) {
|
148
|
+
if ( n >= 0 && n <= 1 ) { return One; }
|
149
|
+
if ( isInt(n) && n >= 2 && n <= 9 ) { return Few; }
|
150
|
+
return Other;
|
151
|
+
});
|
152
|
+
|
153
|
+
define([ 'mo', 'ro' ], function(n) {
|
154
|
+
var mod100 = n % 100;
|
155
|
+
|
156
|
+
if ( n === 1 ) { return One; }
|
157
|
+
|
158
|
+
if ( n === 0 ||
|
159
|
+
(isInt(n) && mod100 >= 1 && mod100 <= 19) ) { return Few; }
|
160
|
+
|
161
|
+
return Other;
|
162
|
+
});
|
163
|
+
|
164
|
+
define([ 'cs', 'sk' ], function(n) {
|
165
|
+
if ( n === 1 ) { return One; }
|
166
|
+
if ( isAmong(n, [ 2, 3, 4 ]) ) { return Few; }
|
167
|
+
return Other;
|
168
|
+
});
|
169
|
+
|
170
|
+
define([ 'sl' ], function(n) {
|
171
|
+
var mod100 = n % 100;
|
172
|
+
if ( mod100 === 1 ) { return One; }
|
173
|
+
if ( mod100 === 2 ) { return Two; }
|
174
|
+
if ( mod100 === 3 || mod100 === 4 ) { return Few; }
|
175
|
+
return Other;
|
176
|
+
});
|
177
|
+
|
178
|
+
define([ 'mt' ], function(n) {
|
179
|
+
if ( n === 1 ) { return One; }
|
180
|
+
var mod100 = n % 100;
|
181
|
+
if ( isInt(mod100) && mod100 >= 2 && mod100 <= 10 ) { return Few; }
|
182
|
+
if ( isInt(mod100) && mod100 >= 11 && mod100 <= 19 ) { return Many; }
|
183
|
+
return Other;
|
184
|
+
});
|
185
|
+
|
186
|
+
define([ 'ar' ], function(n) {
|
187
|
+
if ( n === 0 ) { return Zero; }
|
188
|
+
if ( n === 1 ) { return One; }
|
189
|
+
if ( n === 2 ) { return Two; }
|
190
|
+
var mod100 = n % 100;
|
191
|
+
if ( isInt(mod100) && mod100 >= 3 && mod100 <= 10 ) { return Few; }
|
192
|
+
if ( isInt(mod100) && mod100 >= 11 && mod100 <= 99 ) { return Many; }
|
193
|
+
return Other;
|
194
|
+
});
|
195
|
+
|
196
|
+
define([ 'br', 'cy' ], function(n) {
|
197
|
+
switch ( n ) {
|
198
|
+
case 0: return Zero;
|
199
|
+
case 1: return One;
|
200
|
+
case 2: return Two;
|
201
|
+
case 3: return Few;
|
202
|
+
case 6: return Many;
|
203
|
+
default: return Other;
|
204
|
+
}
|
205
|
+
});
|
206
|
+
|
207
|
+
if ( globals.CLDR == null ) { globals.CLDR = {}; }
|
208
|
+
|
209
|
+
var CLDR = globals.CLDR;
|
210
|
+
|
211
|
+
// Look up the proper plural key for a count and language.
|
212
|
+
// If CLDR.defaultLanguage is set, language is optional.
|
213
|
+
//
|
214
|
+
// For example:
|
215
|
+
//
|
216
|
+
// CLDR.pluralForm(0, 'en'); // => 'other'
|
217
|
+
// CLDR.pluralForm(1, 'en-US'); // => 'one'
|
218
|
+
// CLDR.pluralForm(2.383, 'fr'); // => 'other'
|
219
|
+
// CLDR.pluralForm(1, 'zh'); // => 'other'
|
220
|
+
// CLDR.pluralForm(26, 'uk'); // => 'many'
|
221
|
+
//
|
222
|
+
// @return [String] the proper key (one of `CLDR.pluralForm.Zero`,
|
223
|
+
// `.One`, `.Two`, `.Few`, `.Many`, or `.Other`).
|
224
|
+
CLDR.pluralForm = function(count, language) {
|
225
|
+
if (count == null) { throw new Error("CLDR.pluralForm requires a count"); }
|
226
|
+
language = language || CLDR.defaultLanguage;
|
227
|
+
if (language == null) { throw new Error("CLDR.pluralForm requires a language"); }
|
228
|
+
language = language.replace(/^(\w\w\w?)-?.*/, "$1");
|
229
|
+
if (Data[language] == null) { throw new Error("No CLDR pluralization information for " + language); }
|
230
|
+
return Data[language].call(CLDR, +count);
|
231
|
+
};
|
232
|
+
|
233
|
+
CLDR.pluralForm.Zero = Zero;
|
234
|
+
CLDR.pluralForm.One = One;
|
235
|
+
CLDR.pluralForm.Two = Two;
|
236
|
+
CLDR.pluralForm.Few = Few;
|
237
|
+
CLDR.pluralForm.Many = Many;
|
238
|
+
CLDR.pluralForm.Other = Other;
|
239
|
+
|
240
|
+
}(this));
|
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
(function(exports) {
|
3
|
-
window.DS = Ember.Namespace.create(
|
3
|
+
window.DS = Ember.Namespace.create({
|
4
|
+
CURRENT_API_REVISION: 2
|
5
|
+
});
|
4
6
|
|
5
7
|
})({});
|
6
8
|
|
@@ -71,7 +73,7 @@ DS.fixtureAdapter = DS.Adapter.create({
|
|
71
73
|
|
72
74
|
ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
|
73
75
|
|
74
|
-
var ids = fixtures.map(function(item, index, self){ return item.id });
|
76
|
+
var ids = fixtures.map(function(item, index, self){ return item.id; });
|
75
77
|
store.loadMany(type, ids, fixtures);
|
76
78
|
}
|
77
79
|
|
@@ -81,6 +83,7 @@ DS.fixtureAdapter = DS.Adapter.create({
|
|
81
83
|
|
82
84
|
|
83
85
|
(function(exports) {
|
86
|
+
/*global jQuery*/
|
84
87
|
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
85
88
|
|
86
89
|
DS.RESTAdapter = DS.Adapter.extend({
|
@@ -88,11 +91,12 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
88
91
|
var root = this.rootForType(type);
|
89
92
|
|
90
93
|
var data = {};
|
91
|
-
data[root] =
|
94
|
+
data[root] = model.toJSON();
|
92
95
|
|
93
|
-
this.ajax(
|
96
|
+
this.ajax(this.buildURL(root), "POST", {
|
94
97
|
data: data,
|
95
98
|
success: function(json) {
|
99
|
+
this.sideload(store, type, json, root);
|
96
100
|
store.didCreateRecord(model, json[root]);
|
97
101
|
}
|
98
102
|
});
|
@@ -108,31 +112,31 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
108
112
|
|
109
113
|
var data = {};
|
110
114
|
data[plural] = models.map(function(model) {
|
111
|
-
return
|
115
|
+
return model.toJSON();
|
112
116
|
});
|
113
117
|
|
114
|
-
this.ajax(
|
118
|
+
this.ajax(this.buildURL(root), "POST", {
|
115
119
|
data: data,
|
120
|
+
|
116
121
|
success: function(json) {
|
122
|
+
this.sideload(store, type, json, plural);
|
117
123
|
store.didCreateRecords(type, models, json[plural]);
|
118
124
|
}
|
119
125
|
});
|
120
126
|
},
|
121
127
|
|
122
128
|
updateRecord: function(store, type, model) {
|
123
|
-
var
|
124
|
-
id = get(model, primaryKey);
|
129
|
+
var id = get(model, 'id');
|
125
130
|
var root = this.rootForType(type);
|
126
131
|
|
127
132
|
var data = {};
|
128
|
-
data[root] =
|
129
|
-
|
130
|
-
var url = ["", this.pluralize(root), id].join("/");
|
133
|
+
data[root] = model.toJSON();
|
131
134
|
|
132
|
-
this.ajax(
|
135
|
+
this.ajax(this.buildURL(root, id), "PUT", {
|
133
136
|
data: data,
|
134
137
|
success: function(json) {
|
135
|
-
|
138
|
+
this.sideload(store, type, json, root);
|
139
|
+
store.didUpdateRecord(model, json && json[root]);
|
136
140
|
}
|
137
141
|
});
|
138
142
|
},
|
@@ -147,26 +151,25 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
147
151
|
|
148
152
|
var data = {};
|
149
153
|
data[plural] = models.map(function(model) {
|
150
|
-
return
|
154
|
+
return model.toJSON();
|
151
155
|
});
|
152
156
|
|
153
|
-
this.ajax(
|
157
|
+
this.ajax(this.buildURL(root, "bulk"), "PUT", {
|
154
158
|
data: data,
|
155
159
|
success: function(json) {
|
160
|
+
this.sideload(store, type, json, plural);
|
156
161
|
store.didUpdateRecords(models, json[plural]);
|
157
162
|
}
|
158
163
|
});
|
159
164
|
},
|
160
165
|
|
161
166
|
deleteRecord: function(store, type, model) {
|
162
|
-
var
|
163
|
-
id = get(model, primaryKey);
|
167
|
+
var id = get(model, 'id');
|
164
168
|
var root = this.rootForType(type);
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
this.ajax(url, "DELETE", {
|
170
|
+
this.ajax(this.buildURL(root, id), "DELETE", {
|
169
171
|
success: function(json) {
|
172
|
+
if (json) { this.sideload(store, type, json); }
|
170
173
|
store.didDeleteRecord(model);
|
171
174
|
}
|
172
175
|
});
|
@@ -178,17 +181,17 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
178
181
|
}
|
179
182
|
|
180
183
|
var root = this.rootForType(type),
|
181
|
-
plural = this.pluralize(root)
|
182
|
-
primaryKey = getPath(type, 'proto.primaryKey');
|
184
|
+
plural = this.pluralize(root);
|
183
185
|
|
184
186
|
var data = {};
|
185
187
|
data[plural] = models.map(function(model) {
|
186
|
-
return get(model,
|
188
|
+
return get(model, 'id');
|
187
189
|
});
|
188
190
|
|
189
|
-
this.ajax(
|
191
|
+
this.ajax(this.buildURL(root, 'bulk'), "DELETE", {
|
190
192
|
data: data,
|
191
193
|
success: function(json) {
|
194
|
+
if (json) { this.sideload(store, type, json); }
|
192
195
|
store.didDeleteRecords(models);
|
193
196
|
}
|
194
197
|
});
|
@@ -197,11 +200,10 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
197
200
|
find: function(store, type, id) {
|
198
201
|
var root = this.rootForType(type);
|
199
202
|
|
200
|
-
|
201
|
-
|
202
|
-
this.ajax(url, "GET", {
|
203
|
+
this.ajax(this.buildURL(root, id), "GET", {
|
203
204
|
success: function(json) {
|
204
205
|
store.load(type, json[root]);
|
206
|
+
this.sideload(store, type, json, root);
|
205
207
|
}
|
206
208
|
});
|
207
209
|
},
|
@@ -209,21 +211,22 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
209
211
|
findMany: function(store, type, ids) {
|
210
212
|
var root = this.rootForType(type), plural = this.pluralize(root);
|
211
213
|
|
212
|
-
this.ajax(
|
214
|
+
this.ajax(this.buildURL(root), "GET", {
|
213
215
|
data: { ids: ids },
|
214
216
|
success: function(json) {
|
215
217
|
store.loadMany(type, ids, json[plural]);
|
218
|
+
this.sideload(store, type, json, plural);
|
216
219
|
}
|
217
220
|
});
|
218
|
-
var url = "/" + plural;
|
219
221
|
},
|
220
222
|
|
221
223
|
findAll: function(store, type) {
|
222
224
|
var root = this.rootForType(type), plural = this.pluralize(root);
|
223
225
|
|
224
|
-
this.ajax(
|
226
|
+
this.ajax(this.buildURL(root), "GET", {
|
225
227
|
success: function(json) {
|
226
228
|
store.loadMany(type, json[plural]);
|
229
|
+
this.sideload(store, type, json, plural);
|
227
230
|
}
|
228
231
|
});
|
229
232
|
},
|
@@ -231,10 +234,11 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
231
234
|
findQuery: function(store, type, query, modelArray) {
|
232
235
|
var root = this.rootForType(type), plural = this.pluralize(root);
|
233
236
|
|
234
|
-
this.ajax(
|
237
|
+
this.ajax(this.buildURL(root), "GET", {
|
235
238
|
data: query,
|
236
239
|
success: function(json) {
|
237
240
|
modelArray.load(json[plural]);
|
241
|
+
this.sideload(store, type, json, plural);
|
238
242
|
}
|
239
243
|
});
|
240
244
|
},
|
@@ -261,9 +265,61 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
261
265
|
ajax: function(url, type, hash) {
|
262
266
|
hash.url = url;
|
263
267
|
hash.type = type;
|
264
|
-
hash.dataType =
|
268
|
+
hash.dataType = 'json';
|
269
|
+
hash.contentType = 'application/json';
|
270
|
+
hash.context = this;
|
271
|
+
|
272
|
+
if (hash.data && type !== 'GET') {
|
273
|
+
hash.data = JSON.stringify(hash.data);
|
274
|
+
}
|
265
275
|
|
266
276
|
jQuery.ajax(hash);
|
277
|
+
},
|
278
|
+
|
279
|
+
sideload: function(store, type, json, root) {
|
280
|
+
var sideloadedType, mappings;
|
281
|
+
|
282
|
+
for (var prop in json) {
|
283
|
+
if (!json.hasOwnProperty(prop)) { continue; }
|
284
|
+
if (prop === root) { continue; }
|
285
|
+
|
286
|
+
sideloadedType = type.typeForAssociation(prop);
|
287
|
+
|
288
|
+
if (!sideloadedType) {
|
289
|
+
mappings = get(this, 'mappings');
|
290
|
+
|
291
|
+
ember_assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
|
292
|
+
|
293
|
+
sideloadedType = get(get(this, 'mappings'), prop);
|
294
|
+
|
295
|
+
ember_assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
|
296
|
+
}
|
297
|
+
|
298
|
+
this.loadValue(store, sideloadedType, json[prop]);
|
299
|
+
}
|
300
|
+
},
|
301
|
+
|
302
|
+
loadValue: function(store, type, value) {
|
303
|
+
if (value instanceof Array) {
|
304
|
+
store.loadMany(type, value);
|
305
|
+
} else {
|
306
|
+
store.load(type, value);
|
307
|
+
}
|
308
|
+
},
|
309
|
+
|
310
|
+
buildURL: function(model, suffix) {
|
311
|
+
var url = [""];
|
312
|
+
|
313
|
+
if (this.namespace !== undefined) {
|
314
|
+
url.push(this.namespace);
|
315
|
+
}
|
316
|
+
|
317
|
+
url.push(this.pluralize(model));
|
318
|
+
if (suffix !== undefined) {
|
319
|
+
url.push(suffix);
|
320
|
+
}
|
321
|
+
|
322
|
+
return url.join("/");
|
267
323
|
}
|
268
324
|
});
|
269
325
|
|
@@ -274,9 +330,30 @@ DS.RESTAdapter = DS.Adapter.extend({
|
|
274
330
|
(function(exports) {
|
275
331
|
var get = Ember.get, set = Ember.set;
|
276
332
|
|
333
|
+
/**
|
334
|
+
A model array is an array that contains records of a certain type. The model
|
335
|
+
array materializes records as needed when they are retrieved for the first
|
336
|
+
time. You should not create model arrays yourself. Instead, an instance of
|
337
|
+
DS.ModelArray or its subclasses will be returned by your application's store
|
338
|
+
in response to queries.
|
339
|
+
*/
|
340
|
+
|
277
341
|
DS.ModelArray = Ember.ArrayProxy.extend({
|
342
|
+
|
343
|
+
/**
|
344
|
+
The model type contained by this model array.
|
345
|
+
|
346
|
+
@type DS.Model
|
347
|
+
*/
|
278
348
|
type: null,
|
349
|
+
|
350
|
+
// The array of client ids backing the model array. When a
|
351
|
+
// record is requested from the model array, the record
|
352
|
+
// for the client id at the same index is materialized, if
|
353
|
+
// necessary, by the store.
|
279
354
|
content: null,
|
355
|
+
|
356
|
+
// The store that created this model array.
|
280
357
|
store: null,
|
281
358
|
|
282
359
|
init: function() {
|
@@ -286,7 +363,7 @@ DS.ModelArray = Ember.ArrayProxy.extend({
|
|
286
363
|
|
287
364
|
arrayDidChange: function(array, index, removed, added) {
|
288
365
|
var modelCache = get(this, 'modelCache');
|
289
|
-
modelCache.replace(index, 0, Array(added));
|
366
|
+
modelCache.replace(index, 0, new Array(added));
|
290
367
|
|
291
368
|
this._super(array, index, removed, added);
|
292
369
|
},
|
@@ -318,19 +395,41 @@ DS.ModelArray = Ember.ArrayProxy.extend({
|
|
318
395
|
}
|
319
396
|
});
|
320
397
|
|
398
|
+
})({});
|
399
|
+
|
400
|
+
|
401
|
+
(function(exports) {
|
402
|
+
var get = Ember.get;
|
403
|
+
|
321
404
|
DS.FilteredModelArray = DS.ModelArray.extend({
|
322
405
|
filterFunction: null,
|
323
406
|
|
407
|
+
replace: function() {
|
408
|
+
var type = get(this, 'type').toString();
|
409
|
+
throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
|
410
|
+
},
|
411
|
+
|
324
412
|
updateFilter: Ember.observer(function() {
|
325
413
|
var store = get(this, 'store');
|
326
414
|
store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
|
327
415
|
}, 'filterFunction')
|
328
416
|
});
|
329
417
|
|
418
|
+
})({});
|
419
|
+
|
420
|
+
|
421
|
+
(function(exports) {
|
422
|
+
var get = Ember.get, set = Ember.set;
|
423
|
+
|
330
424
|
DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
331
425
|
query: null,
|
332
426
|
isLoaded: false,
|
333
427
|
|
428
|
+
replace: function() {
|
429
|
+
var type = get(this, 'type').toString();
|
430
|
+
throw new Error("The result of a server query (on " + type + ") is immutable.");
|
431
|
+
},
|
432
|
+
|
334
433
|
load: function(array) {
|
335
434
|
var store = get(this, 'store'), type = get(this, 'type');
|
336
435
|
|
@@ -343,172 +442,181 @@ DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
|
343
442
|
}
|
344
443
|
});
|
345
444
|
|
445
|
+
|
346
446
|
})({});
|
347
447
|
|
348
448
|
|
349
449
|
(function(exports) {
|
350
|
-
var get = Ember.get, set = Ember.set
|
450
|
+
var get = Ember.get, set = Ember.set;
|
351
451
|
|
352
|
-
|
353
|
-
|
354
|
-
this.clear();
|
355
|
-
},
|
452
|
+
DS.ManyArray = DS.ModelArray.extend({
|
453
|
+
parentRecord: null,
|
356
454
|
|
357
|
-
|
358
|
-
|
359
|
-
this
|
360
|
-
|
455
|
+
// Overrides Ember.Array's replace method to implement
|
456
|
+
replace: function(index, removed, added) {
|
457
|
+
var parentRecord = get(this, 'parentRecord');
|
458
|
+
var pendingParent = parentRecord && !get(parentRecord, 'id');
|
361
459
|
|
362
|
-
|
363
|
-
|
364
|
-
presenceSet = get(this, 'presenceSet'),
|
365
|
-
list = get(this, 'list');
|
460
|
+
added = added.map(function(record) {
|
461
|
+
ember_assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this association.", !get(this, 'type') || (get(this, 'type') === record.constructor));
|
366
462
|
|
367
|
-
|
463
|
+
if (pendingParent) {
|
464
|
+
record.send('waitingOn', parentRecord);
|
465
|
+
}
|
368
466
|
|
369
|
-
|
370
|
-
list.pushObject(obj);
|
371
|
-
},
|
467
|
+
this.assignInverse(record, parentRecord);
|
372
468
|
|
373
|
-
|
374
|
-
|
375
|
-
presenceSet = get(this, 'presenceSet'),
|
376
|
-
list = get(this, 'list');
|
469
|
+
return record.get('clientId');
|
470
|
+
}, this);
|
377
471
|
|
378
|
-
|
379
|
-
list.removeObject(obj);
|
472
|
+
this._super(index, removed, added);
|
380
473
|
},
|
381
474
|
|
382
|
-
|
383
|
-
|
384
|
-
|
475
|
+
assignInverse: function(record, parentRecord) {
|
476
|
+
var associationMap = get(record.constructor, 'associations'),
|
477
|
+
possibleAssociations = associationMap.get(record.constructor),
|
478
|
+
possible, actual;
|
385
479
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
480
|
+
if (!possibleAssociations) { return; }
|
481
|
+
|
482
|
+
for (var i = 0, l = possibleAssociations.length; i < l; i++) {
|
483
|
+
possible = possibleAssociations[i];
|
484
|
+
|
485
|
+
if (possible.kind === 'belongsTo') {
|
486
|
+
actual = possible;
|
487
|
+
break;
|
488
|
+
}
|
489
|
+
}
|
391
490
|
|
392
|
-
|
393
|
-
|
491
|
+
if (actual) {
|
492
|
+
set(record, actual.name, parentRecord);
|
493
|
+
}
|
394
494
|
}
|
395
495
|
});
|
396
496
|
|
397
|
-
|
398
|
-
|
399
|
-
default Objects, the keys of a Hash can be any JavaScript
|
400
|
-
object.
|
497
|
+
})({});
|
498
|
+
|
401
499
|
|
402
|
-
|
500
|
+
(function(exports) {
|
501
|
+
})({});
|
403
502
|
|
404
|
-
`keys`: an OrderedSet of all of the existing keys
|
405
|
-
`values`: a JavaScript Object indexed by the
|
406
|
-
Ember.guidFor(key)
|
407
503
|
|
408
|
-
|
409
|
-
|
410
|
-
replace an entry in `values`. When an entry is deleted,
|
411
|
-
we delete its entry in `keys` and `values`.
|
412
|
-
*/
|
504
|
+
(function(exports) {
|
505
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
413
506
|
|
414
|
-
|
507
|
+
DS.Transaction = Ember.Object.extend({
|
415
508
|
init: function() {
|
416
|
-
set(this, '
|
417
|
-
|
509
|
+
set(this, 'buckets', {
|
510
|
+
clean: Ember.Map.create(),
|
511
|
+
created: Ember.Map.create(),
|
512
|
+
updated: Ember.Map.create(),
|
513
|
+
deleted: Ember.Map.create()
|
514
|
+
});
|
418
515
|
},
|
419
516
|
|
420
|
-
|
421
|
-
var
|
422
|
-
var guid = Ember.guidFor(key);
|
423
|
-
|
424
|
-
keys.add(key);
|
425
|
-
values[guid] = value;
|
517
|
+
createRecord: function(type, hash) {
|
518
|
+
var store = get(this, 'store');
|
426
519
|
|
427
|
-
return
|
520
|
+
return store.createRecord(type, hash, this);
|
428
521
|
},
|
429
522
|
|
430
|
-
|
431
|
-
|
432
|
-
|
523
|
+
add: function(record) {
|
524
|
+
// we could probably make this work if someone has a valid use case. Do you?
|
525
|
+
ember_assert("Once a record has changed, you cannot move it into a different transaction", !get(record, 'isDirty'));
|
433
526
|
|
434
|
-
|
527
|
+
var modelTransaction = get(record, 'transaction'),
|
528
|
+
defaultTransaction = getPath(this, 'store.defaultTransaction');
|
435
529
|
|
436
|
-
|
437
|
-
delete values[guid];
|
530
|
+
ember_assert("Models cannot belong to more than one transaction at a time.", modelTransaction === defaultTransaction);
|
438
531
|
|
439
|
-
|
532
|
+
this.adoptRecord(record);
|
440
533
|
},
|
441
534
|
|
442
|
-
|
443
|
-
var
|
444
|
-
var guid = Ember.guidFor(key);
|
535
|
+
remove: function(record) {
|
536
|
+
var defaultTransaction = getPath(this, 'store.defaultTransaction');
|
445
537
|
|
446
|
-
|
538
|
+
defaultTransaction.adoptRecord(record);
|
447
539
|
},
|
448
540
|
|
449
|
-
|
450
|
-
|
541
|
+
/**
|
542
|
+
@private
|
451
543
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
});
|
456
|
-
}
|
457
|
-
});
|
544
|
+
This method moves a record into a different transaction without the normal
|
545
|
+
checks that ensure that the user is not doing something weird, like moving
|
546
|
+
a dirty record into a new transaction.
|
458
547
|
|
459
|
-
|
460
|
-
|
461
|
-
set(this, 'dirty', {
|
462
|
-
created: Hash.create(),
|
463
|
-
updated: Hash.create(),
|
464
|
-
deleted: Hash.create()
|
465
|
-
});
|
466
|
-
},
|
548
|
+
It is designed for internal use, such as when we are moving a clean record
|
549
|
+
into a new transaction when the transaction is committed.
|
467
550
|
|
468
|
-
|
469
|
-
|
551
|
+
This method must not be called unless the record is clean.
|
552
|
+
*/
|
553
|
+
adoptRecord: function(record) {
|
554
|
+
var oldTransaction = get(record, 'transaction');
|
470
555
|
|
471
|
-
|
472
|
-
|
556
|
+
if (oldTransaction) {
|
557
|
+
oldTransaction.removeFromBucket('clean', record);
|
558
|
+
}
|
473
559
|
|
474
|
-
|
475
|
-
|
476
|
-
|
560
|
+
this.addToBucket('clean', record);
|
561
|
+
set(record, 'transaction', this);
|
562
|
+
},
|
477
563
|
|
478
|
-
|
564
|
+
modelBecameDirty: function(kind, record) {
|
565
|
+
this.removeFromBucket('clean', record);
|
566
|
+
this.addToBucket(kind, record);
|
479
567
|
},
|
480
568
|
|
481
|
-
|
482
|
-
|
483
|
-
|
569
|
+
/** @private */
|
570
|
+
addToBucket: function(kind, record) {
|
571
|
+
var bucket = get(get(this, 'buckets'), kind),
|
572
|
+
type = record.constructor;
|
484
573
|
|
485
|
-
var
|
574
|
+
var records = bucket.get(type);
|
486
575
|
|
487
|
-
|
488
|
-
|
576
|
+
if (!records) {
|
577
|
+
records = Ember.OrderedSet.create();
|
578
|
+
bucket.set(type, records);
|
579
|
+
}
|
580
|
+
|
581
|
+
records.add(record);
|
489
582
|
},
|
490
583
|
|
491
|
-
|
492
|
-
|
493
|
-
|
584
|
+
/** @private */
|
585
|
+
removeFromBucket: function(kind, record) {
|
586
|
+
var bucket = get(get(this, 'buckets'), kind),
|
587
|
+
type = record.constructor;
|
588
|
+
|
589
|
+
var records = bucket.get(type);
|
590
|
+
records.remove(record);
|
591
|
+
},
|
494
592
|
|
495
|
-
|
496
|
-
|
593
|
+
modelBecameClean: function(kind, record) {
|
594
|
+
this.removeFromBucket(kind, record);
|
497
595
|
|
498
|
-
|
596
|
+
var defaultTransaction = getPath(this, 'store.defaultTransaction');
|
597
|
+
defaultTransaction.adoptRecord(record);
|
499
598
|
},
|
500
599
|
|
501
600
|
commit: function() {
|
502
|
-
var
|
601
|
+
var buckets = get(this, 'buckets');
|
503
602
|
|
504
603
|
var iterate = function(kind, fn, binding) {
|
505
|
-
var dirty = get(
|
604
|
+
var dirty = get(buckets, kind);
|
506
605
|
|
507
606
|
dirty.forEach(function(type, models) {
|
508
607
|
if (models.isEmpty()) { return; }
|
509
608
|
|
510
|
-
|
511
|
-
|
609
|
+
var array = [];
|
610
|
+
|
611
|
+
models.forEach(function(model) {
|
612
|
+
model.send('willCommit');
|
613
|
+
|
614
|
+
if (get(model, 'isPending') === false) {
|
615
|
+
array.push(model);
|
616
|
+
}
|
617
|
+
});
|
618
|
+
|
619
|
+
fn.call(binding, type, array);
|
512
620
|
});
|
513
621
|
};
|
514
622
|
|
@@ -528,6 +636,16 @@ DS.Transaction = Ember.Object.extend({
|
|
528
636
|
|
529
637
|
var store = get(this, 'store');
|
530
638
|
var adapter = get(store, '_adapter');
|
639
|
+
|
640
|
+
var clean = get(buckets, 'clean');
|
641
|
+
var defaultTransaction = get(store, 'defaultTransaction');
|
642
|
+
|
643
|
+
clean.forEach(function(type, records) {
|
644
|
+
records.forEach(function(record) {
|
645
|
+
this.remove(record);
|
646
|
+
}, this);
|
647
|
+
}, this);
|
648
|
+
|
531
649
|
if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
|
532
650
|
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
|
533
651
|
}
|
@@ -539,46 +657,12 @@ DS.Transaction = Ember.Object.extend({
|
|
539
657
|
(function(exports) {
|
540
658
|
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
541
659
|
|
542
|
-
var
|
543
|
-
|
544
|
-
this.
|
545
|
-
},
|
546
|
-
|
547
|
-
clear: function() {
|
548
|
-
this.set('presenceSet', {});
|
549
|
-
this.set('list', Ember.NativeArray.apply([]));
|
550
|
-
},
|
551
|
-
|
552
|
-
add: function(obj) {
|
553
|
-
var guid = Ember.guidFor(obj),
|
554
|
-
presenceSet = get(this, 'presenceSet'),
|
555
|
-
list = get(this, 'list');
|
556
|
-
|
557
|
-
if (guid in presenceSet) { return; }
|
558
|
-
|
559
|
-
presenceSet[guid] = true;
|
560
|
-
list.pushObject(obj);
|
561
|
-
},
|
562
|
-
|
563
|
-
remove: function(obj) {
|
564
|
-
var guid = Ember.guidFor(obj),
|
565
|
-
presenceSet = get(this, 'presenceSet'),
|
566
|
-
list = get(this, 'list');
|
567
|
-
|
568
|
-
delete presenceSet[guid];
|
569
|
-
list.removeObject(obj);
|
570
|
-
},
|
571
|
-
|
572
|
-
isEmpty: function() {
|
573
|
-
return getPath(this, 'list.length') === 0;
|
574
|
-
},
|
575
|
-
|
576
|
-
forEach: function(fn, self) {
|
577
|
-
get(this, 'list').forEach(function(item) {
|
578
|
-
fn.call(self, item);
|
579
|
-
});
|
660
|
+
var DATA_PROXY = {
|
661
|
+
get: function(name) {
|
662
|
+
return this.savedData[name];
|
580
663
|
}
|
581
|
-
}
|
664
|
+
};
|
665
|
+
|
582
666
|
|
583
667
|
// Implementors Note:
|
584
668
|
//
|
@@ -627,33 +711,49 @@ DS.Store = Ember.Object.extend({
|
|
627
711
|
The init method registers this store as the default if none is specified.
|
628
712
|
*/
|
629
713
|
init: function() {
|
714
|
+
// Enforce API revisioning. See BREAKING_CHANGES.md for more.
|
715
|
+
var revision = get(this, 'revision');
|
716
|
+
|
717
|
+
if (revision !== DS.CURRENT_API_REVISION && !Ember.ENV.TESTING) {
|
718
|
+
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);
|
719
|
+
}
|
720
|
+
|
630
721
|
if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
|
631
722
|
set(DS, 'defaultStore', this);
|
632
723
|
}
|
633
724
|
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
725
|
+
// internal bookkeeping; not observable
|
726
|
+
this.typeMaps = {};
|
727
|
+
this.recordCache = [];
|
728
|
+
this.clientIdToId = {};
|
729
|
+
this.modelArraysByClientId = {};
|
730
|
+
|
731
|
+
set(this, 'defaultTransaction', this.transaction());
|
640
732
|
|
641
733
|
return this._super();
|
642
734
|
},
|
643
735
|
|
736
|
+
/**
|
737
|
+
Returns a new transaction scoped to this store.
|
738
|
+
|
739
|
+
@see {DS.Transaction}
|
740
|
+
@returns DS.Transaction
|
741
|
+
*/
|
644
742
|
transaction: function() {
|
645
743
|
return DS.Transaction.create({ store: this });
|
646
744
|
},
|
647
745
|
|
648
|
-
|
649
|
-
|
650
|
-
var ret = modelArrays[clientId];
|
746
|
+
/**
|
747
|
+
@private
|
651
748
|
|
652
|
-
|
653
|
-
|
654
|
-
|
749
|
+
This is used only by the model's DataProxy. Do not use this directly.
|
750
|
+
*/
|
751
|
+
dataForRecord: function(record) {
|
752
|
+
var type = record.constructor,
|
753
|
+
clientId = get(record, 'clientId'),
|
754
|
+
typeMap = this.typeMapFor(type);
|
655
755
|
|
656
|
-
return
|
756
|
+
return typeMap.cidToHash[clientId];
|
657
757
|
},
|
658
758
|
|
659
759
|
/**
|
@@ -666,6 +766,13 @@ DS.Store = Ember.Object.extend({
|
|
666
766
|
*/
|
667
767
|
adapter: null,
|
668
768
|
|
769
|
+
/**
|
770
|
+
@private
|
771
|
+
|
772
|
+
This property returns the adapter, after resolving a possible String.
|
773
|
+
|
774
|
+
@returns DS.Adapter
|
775
|
+
*/
|
669
776
|
_adapter: Ember.computed(function() {
|
670
777
|
var adapter = get(this, 'adapter');
|
671
778
|
if (typeof adapter === 'string') {
|
@@ -674,45 +781,80 @@ DS.Store = Ember.Object.extend({
|
|
674
781
|
return adapter;
|
675
782
|
}).property('adapter').cacheable(),
|
676
783
|
|
784
|
+
// A monotonically increasing number to be used to uniquely identify
|
785
|
+
// data hashes and records.
|
677
786
|
clientIdCounter: -1,
|
678
787
|
|
679
788
|
// ....................
|
680
789
|
// . CREATE NEW MODEL .
|
681
790
|
// ....................
|
682
791
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
var id = hash[getPath(type, 'proto.primaryKey')] || null;
|
792
|
+
/**
|
793
|
+
Create a new record in the current store. The properties passed
|
794
|
+
to this method are set on the newly created record.
|
687
795
|
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
796
|
+
@param {subclass of DS.Model} type
|
797
|
+
@param {Object} properties a hash of properties to set on the
|
798
|
+
newly created record.
|
799
|
+
@returns DS.Model
|
800
|
+
*/
|
801
|
+
createRecord: function(type, properties, transaction) {
|
802
|
+
properties = properties || {};
|
803
|
+
|
804
|
+
// Create a new instance of the model `type` and put it
|
805
|
+
// into the specified `transaction`. If no transaction is
|
806
|
+
// specified, the default transaction will be used.
|
807
|
+
//
|
808
|
+
// NOTE: A `transaction` is specified when the
|
809
|
+
// `transaction.createRecord` API is used.
|
810
|
+
var record = type._create({
|
811
|
+
store: this
|
692
812
|
});
|
693
813
|
|
694
|
-
|
814
|
+
transaction = transaction || get(this, 'defaultTransaction');
|
815
|
+
transaction.adoptRecord(record);
|
695
816
|
|
696
|
-
|
697
|
-
|
817
|
+
// Extract the primary key from the `properties` hash,
|
818
|
+
// based on the `primaryKey` for the model type.
|
819
|
+
var id = properties[get(record, 'primaryKey')] || null;
|
698
820
|
|
699
|
-
var
|
821
|
+
var hash = {}, clientId;
|
700
822
|
|
701
|
-
|
823
|
+
// Push the hash into the store. If present, associate the
|
824
|
+
// extracted `id` with the hash.
|
825
|
+
clientId = this.pushHash(hash, id, type);
|
702
826
|
|
703
|
-
|
827
|
+
record.send('didChangeData');
|
704
828
|
|
705
|
-
this
|
829
|
+
var recordCache = get(this, 'recordCache');
|
706
830
|
|
707
|
-
|
831
|
+
// Now that we have a clientId, attach it to the record we
|
832
|
+
// just created.
|
833
|
+
set(record, 'clientId', clientId);
|
834
|
+
|
835
|
+
// Store the record we just created in the record cache for
|
836
|
+
// this clientId.
|
837
|
+
recordCache[clientId] = record;
|
838
|
+
|
839
|
+
// Set the properties specified on the record.
|
840
|
+
record.setProperties(properties);
|
841
|
+
|
842
|
+
this.updateModelArrays(type, clientId, get(record, 'data'));
|
843
|
+
|
844
|
+
return record;
|
708
845
|
},
|
709
846
|
|
710
847
|
// ................
|
711
848
|
// . DELETE MODEL .
|
712
849
|
// ................
|
713
850
|
|
714
|
-
|
715
|
-
|
851
|
+
/**
|
852
|
+
For symmetry, a record can be deleted via the store.
|
853
|
+
|
854
|
+
@param {DS.Model} record
|
855
|
+
*/
|
856
|
+
deleteRecord: function(record) {
|
857
|
+
record.send('deleteRecord');
|
716
858
|
},
|
717
859
|
|
718
860
|
// ...............
|
@@ -720,26 +862,64 @@ DS.Store = Ember.Object.extend({
|
|
720
862
|
// ...............
|
721
863
|
|
722
864
|
/**
|
723
|
-
|
724
|
-
|
725
|
-
immediately. Otherwise, an empty DS.Model instance will be returned in
|
726
|
-
the loading state. As soon as the requested data is available, the model
|
727
|
-
will be moved into the loaded state and all of the information will be
|
728
|
-
available.
|
865
|
+
This is the main entry point into finding records. The first
|
866
|
+
parameter to this method is always a subclass of `DS.Model`.
|
729
867
|
|
730
|
-
|
731
|
-
|
868
|
+
You can use the `find` method on a subclass of `DS.Model`
|
869
|
+
directly if your application only has one store. For
|
870
|
+
example, instead of `store.find(App.Person, 1)`, you could
|
871
|
+
say `App.Person.find(1)`.
|
732
872
|
|
733
|
-
|
873
|
+
---
|
734
874
|
|
735
|
-
|
875
|
+
To find a record by ID, pass the `id` as the second parameter:
|
736
876
|
|
737
|
-
|
738
|
-
|
877
|
+
store.find(App.Person, 1);
|
878
|
+
App.Person.find(1);
|
879
|
+
|
880
|
+
If the record with that `id` had not previously been loaded,
|
881
|
+
the store will return an empty record immediately and ask
|
882
|
+
the adapter to find the data by calling its `find` method.
|
883
|
+
|
884
|
+
The `find` method will always return the same object for a
|
885
|
+
given type and `id`. To check whether the adapter has populated
|
886
|
+
a record, you can check its `isLoaded` property.
|
887
|
+
|
888
|
+
---
|
889
|
+
|
890
|
+
To find all records for a type, call `find` with no additional
|
891
|
+
parameters:
|
892
|
+
|
893
|
+
store.find(App.Person);
|
894
|
+
App.Person.find();
|
895
|
+
|
896
|
+
This will return a `ModelArray` representing all known records
|
897
|
+
for the given type and kick off a request to the adapter's
|
898
|
+
`findAll` method to load any additional records for the type.
|
899
|
+
|
900
|
+
The `ModelArray` returned by `find()` is live. If any more
|
901
|
+
records for the type are added at a later time through any
|
902
|
+
mechanism, it will automatically update to reflect the change.
|
903
|
+
|
904
|
+
---
|
905
|
+
|
906
|
+
To find a record by a query, call `find` with a hash as the
|
907
|
+
second parameter:
|
908
|
+
|
909
|
+
store.find(App.Person, { page: 1 });
|
910
|
+
App.Person.find({ page: 1 });
|
911
|
+
|
912
|
+
This will return a `ModelArray` immediately, but it will always
|
913
|
+
be an empty `ModelArray` at first. It will call the adapter's
|
914
|
+
`findQuery` method, which will populate the `ModelArray` once
|
915
|
+
the server has returned results.
|
916
|
+
|
917
|
+
You can check whether a query results `ModelArray` has loaded
|
918
|
+
by checking its `isLoaded` property.
|
739
919
|
*/
|
740
920
|
find: function(type, id, query) {
|
741
921
|
if (id === undefined) {
|
742
|
-
return this.
|
922
|
+
return this.findAll(type);
|
743
923
|
}
|
744
924
|
|
745
925
|
if (query !== undefined) {
|
@@ -758,10 +938,9 @@ DS.Store = Ember.Object.extend({
|
|
758
938
|
},
|
759
939
|
|
760
940
|
findByClientId: function(type, clientId, id) {
|
761
|
-
var
|
762
|
-
|
763
|
-
|
764
|
-
var data = this.clientIdToHashMap(type);
|
941
|
+
var recordCache = get(this, 'recordCache'),
|
942
|
+
dataCache = this.typeMapFor(type).cidToHash,
|
943
|
+
model;
|
765
944
|
|
766
945
|
// If there is already a clientId assigned for this
|
767
946
|
// type/id combination, try to find an existing
|
@@ -769,27 +948,28 @@ DS.Store = Ember.Object.extend({
|
|
769
948
|
// materialize a new model and set its data to the
|
770
949
|
// value we already have.
|
771
950
|
if (clientId !== undefined) {
|
772
|
-
model =
|
951
|
+
model = recordCache[clientId];
|
773
952
|
|
774
953
|
if (!model) {
|
775
954
|
// create a new instance of the model in the
|
776
955
|
// 'isLoading' state
|
777
|
-
model = this.
|
956
|
+
model = this.materializeRecord(type, clientId);
|
778
957
|
|
779
|
-
|
780
|
-
|
958
|
+
if (dataCache[clientId]) {
|
959
|
+
model.send('didChangeData');
|
960
|
+
}
|
781
961
|
}
|
782
962
|
} else {
|
783
963
|
clientId = this.pushHash(null, id, type);
|
784
964
|
|
785
965
|
// create a new instance of the model in the
|
786
966
|
// 'isLoading' state
|
787
|
-
model = this.
|
967
|
+
model = this.materializeRecord(type, clientId);
|
788
968
|
|
789
969
|
// let the adapter set the data, possibly async
|
790
970
|
var adapter = get(this, '_adapter');
|
791
971
|
if (adapter && adapter.find) { adapter.find(this, type, id); }
|
792
|
-
else { throw fmt("Adapter is either null or
|
972
|
+
else { throw fmt("Adapter is either null or does not implement `find` method", this); }
|
793
973
|
}
|
794
974
|
|
795
975
|
return model;
|
@@ -798,8 +978,10 @@ DS.Store = Ember.Object.extend({
|
|
798
978
|
/** @private
|
799
979
|
*/
|
800
980
|
findMany: function(type, ids, query) {
|
801
|
-
var
|
802
|
-
|
981
|
+
var typeMap = this.typeMapFor(type),
|
982
|
+
idToClientIdMap = typeMap.idToCid,
|
983
|
+
data = typeMap.cidToHash,
|
984
|
+
needed;
|
803
985
|
|
804
986
|
var clientIds = Ember.A([]);
|
805
987
|
|
@@ -822,17 +1004,17 @@ DS.Store = Ember.Object.extend({
|
|
822
1004
|
if ((needed && get(needed, 'length') > 0) || query) {
|
823
1005
|
var adapter = get(this, '_adapter');
|
824
1006
|
if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
|
825
|
-
else { throw fmt("Adapter is either null or
|
1007
|
+
else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
|
826
1008
|
}
|
827
1009
|
|
828
|
-
return this.
|
1010
|
+
return this.createManyArray(type, clientIds);
|
829
1011
|
},
|
830
1012
|
|
831
1013
|
findQuery: function(type, query) {
|
832
1014
|
var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
|
833
1015
|
var adapter = get(this, '_adapter');
|
834
1016
|
if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
|
835
|
-
else { throw fmt("Adapter is either null or
|
1017
|
+
else { throw fmt("Adapter is either null or does not implement `findQuery` method", this); }
|
836
1018
|
return array;
|
837
1019
|
},
|
838
1020
|
|
@@ -853,7 +1035,14 @@ DS.Store = Ember.Object.extend({
|
|
853
1035
|
return array;
|
854
1036
|
},
|
855
1037
|
|
856
|
-
filter: function(type, filter) {
|
1038
|
+
filter: function(type, query, filter) {
|
1039
|
+
// allow an optional server query
|
1040
|
+
if (arguments.length === 3) {
|
1041
|
+
this.findQuery(type, query);
|
1042
|
+
} else if (arguments.length === 2) {
|
1043
|
+
filter = query;
|
1044
|
+
}
|
1045
|
+
|
857
1046
|
var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
|
858
1047
|
|
859
1048
|
this.registerModelArray(array, type, filter);
|
@@ -865,11 +1054,8 @@ DS.Store = Ember.Object.extend({
|
|
865
1054
|
// . UPDATING .
|
866
1055
|
// ............
|
867
1056
|
|
868
|
-
hashWasUpdated: function(type, clientId) {
|
869
|
-
|
870
|
-
var hash = clientIdToHashMap[clientId];
|
871
|
-
|
872
|
-
this.updateModelArrays(type, clientId, hash);
|
1057
|
+
hashWasUpdated: function(type, clientId, record) {
|
1058
|
+
this.updateModelArrays(type, clientId, get(record, 'data'));
|
873
1059
|
},
|
874
1060
|
|
875
1061
|
// ..............
|
@@ -877,11 +1063,14 @@ DS.Store = Ember.Object.extend({
|
|
877
1063
|
// ..............
|
878
1064
|
|
879
1065
|
commit: function() {
|
880
|
-
get(this, 'defaultTransaction')
|
1066
|
+
var defaultTransaction = get(this, 'defaultTransaction');
|
1067
|
+
set(this, 'defaultTransaction', this.transaction());
|
1068
|
+
|
1069
|
+
defaultTransaction.commit();
|
881
1070
|
},
|
882
1071
|
|
883
1072
|
didUpdateRecords: function(array, hashes) {
|
884
|
-
if (
|
1073
|
+
if (hashes) {
|
885
1074
|
array.forEach(function(model, idx) {
|
886
1075
|
this.didUpdateRecord(model, hashes[idx]);
|
887
1076
|
}, this);
|
@@ -893,72 +1082,89 @@ DS.Store = Ember.Object.extend({
|
|
893
1082
|
},
|
894
1083
|
|
895
1084
|
didUpdateRecord: function(model, hash) {
|
896
|
-
if (
|
897
|
-
var clientId = get(model, 'clientId')
|
898
|
-
|
1085
|
+
if (hash) {
|
1086
|
+
var clientId = get(model, 'clientId'),
|
1087
|
+
dataCache = this.typeMapFor(model.constructor).cidToHash;
|
899
1088
|
|
900
|
-
|
901
|
-
model.
|
1089
|
+
dataCache[clientId] = hash;
|
1090
|
+
model.send('didChangeData');
|
902
1091
|
}
|
903
1092
|
|
904
|
-
model.
|
1093
|
+
model.send('didCommit');
|
905
1094
|
},
|
906
1095
|
|
907
1096
|
didDeleteRecords: function(array) {
|
908
1097
|
array.forEach(function(model) {
|
909
|
-
model.
|
1098
|
+
model.send('didCommit');
|
910
1099
|
});
|
911
1100
|
},
|
912
1101
|
|
913
1102
|
didDeleteRecord: function(model) {
|
914
|
-
model.
|
1103
|
+
model.send('didCommit');
|
915
1104
|
},
|
916
1105
|
|
917
|
-
|
918
|
-
var
|
1106
|
+
_didCreateRecord: function(record, hash, typeMap, clientId, primaryKey) {
|
1107
|
+
var recordData = get(record, 'data'), id, changes;
|
919
1108
|
|
920
|
-
|
921
|
-
|
922
|
-
|
1109
|
+
if (hash) {
|
1110
|
+
typeMap.cidToHash[clientId] = hash;
|
1111
|
+
|
1112
|
+
// If the server returns a hash, we assume that the server's version
|
1113
|
+
// of the data supercedes the local changes.
|
1114
|
+
record.beginPropertyChanges();
|
1115
|
+
record.send('didChangeData');
|
1116
|
+
recordData.adapterDidUpdate(hash);
|
1117
|
+
record.endPropertyChanges();
|
923
1118
|
|
924
|
-
for (var i=0, l=get(array, 'length'); i<l; i++) {
|
925
|
-
var model = array[i], hash = hashes[i];
|
926
1119
|
id = hash[primaryKey];
|
927
|
-
clientId = get(model, 'clientId');
|
928
1120
|
|
929
|
-
|
930
|
-
|
1121
|
+
typeMap.idToCid[id] = clientId;
|
1122
|
+
this.clientIdToId[clientId] = id;
|
1123
|
+
} else {
|
1124
|
+
recordData.commit();
|
1125
|
+
}
|
1126
|
+
|
1127
|
+
record.send('didCommit');
|
1128
|
+
},
|
931
1129
|
|
932
|
-
idToClientIdMap[id] = clientId;
|
933
|
-
idList.push(id);
|
934
1130
|
|
935
|
-
|
1131
|
+
didCreateRecords: function(type, array, hashes) {
|
1132
|
+
var primaryKey = getPath(type, 'proto.primaryKey'),
|
1133
|
+
typeMap = this.typeMapFor(type),
|
1134
|
+
id, clientId;
|
1135
|
+
|
1136
|
+
for (var i=0, l=get(array, 'length'); i<l; i++) {
|
1137
|
+
var model = array[i], hash = hashes[i];
|
1138
|
+
clientId = get(model, 'clientId');
|
1139
|
+
|
1140
|
+
this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
|
936
1141
|
}
|
937
1142
|
},
|
938
1143
|
|
939
1144
|
didCreateRecord: function(model, hash) {
|
940
|
-
var type = model.constructor
|
1145
|
+
var type = model.constructor,
|
1146
|
+
typeMap = this.typeMapFor(type),
|
1147
|
+
id, clientId, primaryKey;
|
941
1148
|
|
942
|
-
|
1149
|
+
// The hash is optional, but if it is not provided, the client must have
|
1150
|
+
// provided a primary key.
|
943
1151
|
|
944
|
-
|
945
|
-
var data = this.clientIdToHashMap(type);
|
946
|
-
var idList = this.idList(type);
|
1152
|
+
primaryKey = getPath(type, 'proto.primaryKey');
|
947
1153
|
|
948
|
-
|
1154
|
+
// TODO: Make ember_assert more flexible and convert this into an ember_assert
|
1155
|
+
if (hash) {
|
1156
|
+
ember_assert("The server must provide a primary key: " + primaryKey, get(hash, primaryKey));
|
1157
|
+
} else {
|
1158
|
+
ember_assert("The server did not return data, and you did not create a primary key (" + primaryKey + ") on the client", get(get(model, 'data'), primaryKey));
|
1159
|
+
}
|
949
1160
|
|
950
1161
|
clientId = get(model, 'clientId');
|
951
|
-
data[clientId] = hash;
|
952
|
-
set(model, 'data', hash);
|
953
|
-
|
954
|
-
idToClientIdMap[id] = clientId;
|
955
|
-
idList.push(id);
|
956
1162
|
|
957
|
-
|
1163
|
+
this._didCreateRecord(model, hash, typeMap, clientId, primaryKey);
|
958
1164
|
},
|
959
1165
|
|
960
1166
|
recordWasInvalid: function(record, errors) {
|
961
|
-
record.
|
1167
|
+
record.send('becameInvalid', errors);
|
962
1168
|
},
|
963
1169
|
|
964
1170
|
// ................
|
@@ -966,16 +1172,15 @@ DS.Store = Ember.Object.extend({
|
|
966
1172
|
// ................
|
967
1173
|
|
968
1174
|
registerModelArray: function(array, type, filter) {
|
969
|
-
var modelArrays =
|
970
|
-
var idToClientIdMap = this.idToClientIdMap(type);
|
1175
|
+
var modelArrays = this.typeMapFor(type).modelArrays;
|
971
1176
|
|
972
1177
|
modelArrays.push(array);
|
973
1178
|
|
974
1179
|
this.updateModelArrayFilter(array, type, filter);
|
975
1180
|
},
|
976
1181
|
|
977
|
-
|
978
|
-
var array = DS.
|
1182
|
+
createManyArray: function(type, clientIds) {
|
1183
|
+
var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
|
979
1184
|
|
980
1185
|
clientIds.forEach(function(clientId) {
|
981
1186
|
var modelArrays = this.modelArraysForClientId(clientId);
|
@@ -986,40 +1191,46 @@ DS.Store = Ember.Object.extend({
|
|
986
1191
|
},
|
987
1192
|
|
988
1193
|
updateModelArrayFilter: function(array, type, filter) {
|
989
|
-
var
|
990
|
-
|
1194
|
+
var typeMap = this.typeMapFor(type),
|
1195
|
+
dataCache = typeMap.cidToHash,
|
1196
|
+
clientIds = typeMap.clientIds,
|
1197
|
+
clientId, hash, proxy;
|
1198
|
+
|
1199
|
+
var recordCache = get(this, 'recordCache'), record;
|
991
1200
|
|
992
|
-
for (var i=0, l=
|
993
|
-
clientId =
|
1201
|
+
for (var i=0, l=clientIds.length; i<l; i++) {
|
1202
|
+
clientId = clientIds[i];
|
994
1203
|
|
995
|
-
hash =
|
1204
|
+
if (hash = dataCache[clientId]) {
|
1205
|
+
if (record = recordCache[clientId]) {
|
1206
|
+
proxy = get(record, 'data');
|
1207
|
+
} else {
|
1208
|
+
DATA_PROXY.savedData = hash;
|
1209
|
+
proxy = DATA_PROXY;
|
1210
|
+
}
|
996
1211
|
|
997
|
-
|
998
|
-
this.updateModelArray(array, filter, type, clientId, hash);
|
1212
|
+
this.updateModelArray(array, filter, type, clientId, proxy);
|
999
1213
|
}
|
1000
1214
|
}
|
1001
1215
|
},
|
1002
1216
|
|
1003
|
-
updateModelArrays: function(type, clientId,
|
1004
|
-
var modelArrays =
|
1217
|
+
updateModelArrays: function(type, clientId, dataProxy) {
|
1218
|
+
var modelArrays = this.typeMapFor(type).modelArrays,
|
1219
|
+
modelArrayType, filter;
|
1005
1220
|
|
1006
1221
|
modelArrays.forEach(function(array) {
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
if (type !== modelArrayType) { return; }
|
1011
|
-
|
1012
|
-
this.updateModelArray(array, filter, type, clientId, hash);
|
1222
|
+
filter = get(array, 'filterFunction');
|
1223
|
+
this.updateModelArray(array, filter, type, clientId, dataProxy);
|
1013
1224
|
}, this);
|
1014
1225
|
},
|
1015
1226
|
|
1016
|
-
updateModelArray: function(array, filter, type, clientId,
|
1227
|
+
updateModelArray: function(array, filter, type, clientId, dataProxy) {
|
1017
1228
|
var shouldBeInArray;
|
1018
1229
|
|
1019
1230
|
if (!filter) {
|
1020
1231
|
shouldBeInArray = true;
|
1021
1232
|
} else {
|
1022
|
-
shouldBeInArray = filter(
|
1233
|
+
shouldBeInArray = filter(dataProxy);
|
1023
1234
|
}
|
1024
1235
|
|
1025
1236
|
var content = get(array, 'content');
|
@@ -1047,44 +1258,39 @@ DS.Store = Ember.Object.extend({
|
|
1047
1258
|
},
|
1048
1259
|
|
1049
1260
|
// ............
|
1050
|
-
// .
|
1261
|
+
// . INDEXING .
|
1051
1262
|
// ............
|
1052
1263
|
|
1264
|
+
modelArraysForClientId: function(clientId) {
|
1265
|
+
var modelArrays = get(this, 'modelArraysByClientId');
|
1266
|
+
var ret = modelArrays[clientId];
|
1267
|
+
|
1268
|
+
if (!ret) {
|
1269
|
+
ret = modelArrays[clientId] = Ember.OrderedSet.create();
|
1270
|
+
}
|
1271
|
+
|
1272
|
+
return ret;
|
1273
|
+
},
|
1274
|
+
|
1053
1275
|
typeMapFor: function(type) {
|
1054
|
-
var
|
1276
|
+
var typeMaps = get(this, 'typeMaps');
|
1055
1277
|
var guidForType = Ember.guidFor(type);
|
1056
1278
|
|
1057
|
-
var typeMap =
|
1279
|
+
var typeMap = typeMaps[guidForType];
|
1058
1280
|
|
1059
1281
|
if (typeMap) {
|
1060
1282
|
return typeMap;
|
1061
1283
|
} else {
|
1062
|
-
return (
|
1284
|
+
return (typeMaps[guidForType] =
|
1063
1285
|
{
|
1064
1286
|
idToCid: {},
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1287
|
+
clientIds: [],
|
1288
|
+
cidToHash: {},
|
1289
|
+
modelArrays: []
|
1068
1290
|
});
|
1069
1291
|
}
|
1070
1292
|
},
|
1071
1293
|
|
1072
|
-
idToClientIdMap: function(type) {
|
1073
|
-
return this.typeMapFor(type).idToCid;
|
1074
|
-
},
|
1075
|
-
|
1076
|
-
idList: function(type) {
|
1077
|
-
return this.typeMapFor(type).idList;
|
1078
|
-
},
|
1079
|
-
|
1080
|
-
clientIdList: function(type) {
|
1081
|
-
return this.typeMapFor(type).cidList;
|
1082
|
-
},
|
1083
|
-
|
1084
|
-
clientIdToHashMap: function(type) {
|
1085
|
-
return this.typeMapFor(type).cidToHash;
|
1086
|
-
},
|
1087
|
-
|
1088
1294
|
/** @private
|
1089
1295
|
|
1090
1296
|
For a given type and id combination, returns the client id used by the store.
|
@@ -1097,13 +1303,6 @@ DS.Store = Ember.Object.extend({
|
|
1097
1303
|
return this.typeMapFor(type).idToCid[id];
|
1098
1304
|
},
|
1099
1305
|
|
1100
|
-
idForHash: function(type, hash) {
|
1101
|
-
var primaryKey = getPath(type, 'proto.primaryKey');
|
1102
|
-
|
1103
|
-
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
1104
|
-
return hash[primaryKey];
|
1105
|
-
},
|
1106
|
-
|
1107
1306
|
// ................
|
1108
1307
|
// . LOADING DATA .
|
1109
1308
|
// ................
|
@@ -1124,28 +1323,28 @@ DS.Store = Ember.Object.extend({
|
|
1124
1323
|
if (hash === undefined) {
|
1125
1324
|
hash = id;
|
1126
1325
|
var primaryKey = getPath(type, 'proto.primaryKey');
|
1127
|
-
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.",
|
1326
|
+
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", primaryKey in hash);
|
1128
1327
|
id = hash[primaryKey];
|
1129
1328
|
}
|
1130
1329
|
|
1131
|
-
var
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1330
|
+
var typeMap = this.typeMapFor(type),
|
1331
|
+
dataCache = typeMap.cidToHash,
|
1332
|
+
clientId = typeMap.idToCid[id],
|
1333
|
+
recordCache = get(this, 'recordCache');
|
1135
1334
|
|
1136
1335
|
if (clientId !== undefined) {
|
1137
|
-
|
1336
|
+
dataCache[clientId] = hash;
|
1138
1337
|
|
1139
|
-
var model =
|
1338
|
+
var model = recordCache[clientId];
|
1140
1339
|
if (model) {
|
1141
|
-
model.
|
1142
|
-
model.setData(hash);
|
1340
|
+
model.send('didChangeData');
|
1143
1341
|
}
|
1144
1342
|
} else {
|
1145
1343
|
clientId = this.pushHash(hash, id, type);
|
1146
1344
|
}
|
1147
1345
|
|
1148
|
-
|
1346
|
+
DATA_PROXY.savedData = hash;
|
1347
|
+
this.updateModelArrays(type, clientId, DATA_PROXY);
|
1149
1348
|
|
1150
1349
|
return { id: id, clientId: clientId };
|
1151
1350
|
},
|
@@ -1158,8 +1357,7 @@ DS.Store = Ember.Object.extend({
|
|
1158
1357
|
ids = [];
|
1159
1358
|
var primaryKey = getPath(type, 'proto.primaryKey');
|
1160
1359
|
|
1161
|
-
ids =
|
1162
|
-
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
1360
|
+
ids = Ember.ArrayUtils.map(hashes, function(hash) {
|
1163
1361
|
return hash[primaryKey];
|
1164
1362
|
});
|
1165
1363
|
}
|
@@ -1183,23 +1381,25 @@ DS.Store = Ember.Object.extend({
|
|
1183
1381
|
@returns {Number}
|
1184
1382
|
*/
|
1185
1383
|
pushHash: function(hash, id, type) {
|
1186
|
-
var
|
1187
|
-
|
1188
|
-
var
|
1189
|
-
|
1384
|
+
var typeMap = this.typeMapFor(type);
|
1385
|
+
|
1386
|
+
var idToClientIdMap = typeMap.idToCid,
|
1387
|
+
clientIdToIdMap = this.clientIdToId,
|
1388
|
+
clientIds = typeMap.clientIds,
|
1389
|
+
dataCache = typeMap.cidToHash;
|
1190
1390
|
|
1191
|
-
var clientId = this.
|
1391
|
+
var clientId = ++this.clientIdCounter;
|
1192
1392
|
|
1193
|
-
|
1393
|
+
dataCache[clientId] = hash;
|
1194
1394
|
|
1195
1395
|
// if we're creating an item, this process will be done
|
1196
1396
|
// later, once the object has been persisted.
|
1197
1397
|
if (id) {
|
1198
1398
|
idToClientIdMap[id] = clientId;
|
1199
|
-
|
1399
|
+
clientIdToIdMap[clientId] = id;
|
1200
1400
|
}
|
1201
1401
|
|
1202
|
-
|
1402
|
+
clientIds.push(clientId);
|
1203
1403
|
|
1204
1404
|
return clientId;
|
1205
1405
|
},
|
@@ -1208,22 +1408,34 @@ DS.Store = Ember.Object.extend({
|
|
1208
1408
|
// . MODEL MATERIALIZATION .
|
1209
1409
|
// .........................
|
1210
1410
|
|
1211
|
-
|
1411
|
+
materializeRecord: function(type, clientId) {
|
1212
1412
|
var model;
|
1213
1413
|
|
1214
|
-
get(this, '
|
1215
|
-
|
1216
|
-
|
1414
|
+
get(this, 'recordCache')[clientId] = model = type._create({
|
1415
|
+
store: this,
|
1416
|
+
clientId: clientId
|
1417
|
+
});
|
1418
|
+
|
1419
|
+
get(this, 'defaultTransaction').adoptRecord(model);
|
1420
|
+
|
1421
|
+
model.send('loadingData');
|
1217
1422
|
return model;
|
1423
|
+
},
|
1424
|
+
|
1425
|
+
destroy: function() {
|
1426
|
+
if (get(DS, 'defaultStore') === this) {
|
1427
|
+
set(DS, 'defaultStore', null);
|
1428
|
+
}
|
1429
|
+
|
1430
|
+
return this._super();
|
1218
1431
|
}
|
1219
1432
|
});
|
1220
1433
|
|
1221
|
-
|
1222
1434
|
})({});
|
1223
1435
|
|
1224
1436
|
|
1225
1437
|
(function(exports) {
|
1226
|
-
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
1438
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, guidFor = Ember.guidFor;
|
1227
1439
|
|
1228
1440
|
var stateProperty = Ember.computed(function(key) {
|
1229
1441
|
var parent = get(this, 'parentState');
|
@@ -1232,116 +1444,401 @@ var stateProperty = Ember.computed(function(key) {
|
|
1232
1444
|
}
|
1233
1445
|
}).property();
|
1234
1446
|
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
isDeleted: stateProperty,
|
1240
|
-
isError: stateProperty,
|
1241
|
-
isNew: stateProperty,
|
1242
|
-
isValid: stateProperty
|
1243
|
-
});
|
1447
|
+
var isEmptyObject = function(object) {
|
1448
|
+
for (var name in object) {
|
1449
|
+
if (object.hasOwnProperty(name)) { return false; }
|
1450
|
+
}
|
1244
1451
|
|
1245
|
-
|
1246
|
-
// TODO: get the current state name
|
1247
|
-
throw "You cannot load data into the store when its associated model is in its current state";
|
1452
|
+
return true;
|
1248
1453
|
};
|
1249
1454
|
|
1250
|
-
var
|
1251
|
-
for (var
|
1252
|
-
if (
|
1253
|
-
return false;
|
1455
|
+
var hasDefinedProperties = function(object) {
|
1456
|
+
for (var name in object) {
|
1457
|
+
if (object.hasOwnProperty(name) && object[name]) { return true; }
|
1254
1458
|
}
|
1255
1459
|
|
1256
|
-
return
|
1460
|
+
return false;
|
1257
1461
|
};
|
1258
1462
|
|
1463
|
+
DS.State = Ember.State.extend({
|
1464
|
+
isLoaded: stateProperty,
|
1465
|
+
isDirty: stateProperty,
|
1466
|
+
isSaving: stateProperty,
|
1467
|
+
isDeleted: stateProperty,
|
1468
|
+
isError: stateProperty,
|
1469
|
+
isNew: stateProperty,
|
1470
|
+
isValid: stateProperty,
|
1471
|
+
isPending: stateProperty,
|
1472
|
+
|
1473
|
+
// For states that are substates of a
|
1474
|
+
// DirtyState (updated or created), it is
|
1475
|
+
// useful to be able to determine which
|
1476
|
+
// type of dirty state it is.
|
1477
|
+
dirtyType: stateProperty
|
1478
|
+
});
|
1479
|
+
|
1259
1480
|
var setProperty = function(manager, context) {
|
1260
1481
|
var key = context.key, value = context.value;
|
1261
1482
|
|
1262
|
-
var model = get(manager, 'model'),
|
1263
|
-
|
1264
|
-
|
1483
|
+
var model = get(manager, 'model'),
|
1484
|
+
data = get(model, 'data');
|
1485
|
+
|
1486
|
+
set(data, key, value);
|
1487
|
+
};
|
1265
1488
|
|
1266
|
-
|
1489
|
+
var didChangeData = function(manager) {
|
1490
|
+
var model = get(manager, 'model'),
|
1491
|
+
data = get(model, 'data');
|
1267
1492
|
|
1268
|
-
|
1493
|
+
data._savedData = null;
|
1494
|
+
model.notifyPropertyChange('data');
|
1269
1495
|
};
|
1270
1496
|
|
1271
|
-
//
|
1272
|
-
//
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1497
|
+
// The waitingOn event shares common functionality
|
1498
|
+
// between the different dirty states, but each is
|
1499
|
+
// treated slightly differently. This method is exposed
|
1500
|
+
// so that each implementation can invoke the common
|
1501
|
+
// behavior, and then implement the behavior specific
|
1502
|
+
// to the state.
|
1503
|
+
var waitingOn = function(manager, object) {
|
1504
|
+
var model = get(manager, 'model'),
|
1505
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1506
|
+
objectGuid = guidFor(object);
|
1507
|
+
|
1508
|
+
var observer = function() {
|
1509
|
+
if (get(object, 'id')) {
|
1510
|
+
manager.send('doneWaitingOn', object);
|
1511
|
+
Ember.removeObserver(object, 'id', observer);
|
1512
|
+
}
|
1513
|
+
};
|
1514
|
+
|
1515
|
+
pendingQueue[objectGuid] = [object, observer];
|
1516
|
+
Ember.addObserver(object, 'id', observer);
|
1517
|
+
};
|
1518
|
+
|
1519
|
+
// Implementation notes:
|
1520
|
+
//
|
1521
|
+
// Each state has a boolean value for all of the following flags:
|
1522
|
+
//
|
1523
|
+
// * isLoaded: The record has a populated `data` property. When a
|
1524
|
+
// record is loaded via `store.find`, `isLoaded` is false
|
1525
|
+
// until the adapter sets it. When a record is created locally,
|
1526
|
+
// its `isLoaded` property is always true.
|
1527
|
+
// * isDirty: The record has local changes that have not yet been
|
1528
|
+
// saved by the adapter. This includes records that have been
|
1529
|
+
// created (but not yet saved) or deleted.
|
1530
|
+
// * isSaving: The record's transaction has been committed, but
|
1531
|
+
// the adapter has not yet acknowledged that the changes have
|
1532
|
+
// been persisted to the backend.
|
1533
|
+
// * isDeleted: The record was marked for deletion. When `isDeleted`
|
1534
|
+
// is true and `isDirty` is true, the record is deleted locally
|
1535
|
+
// but the deletion was not yet persisted. When `isSaving` is
|
1536
|
+
// true, the change is in-flight. When both `isDirty` and
|
1537
|
+
// `isSaving` are false, the change has persisted.
|
1538
|
+
// * isError: The adapter reported that it was unable to save
|
1539
|
+
// local changes to the backend. This may also result in the
|
1540
|
+
// record having its `isValid` property become false if the
|
1541
|
+
// adapter reported that server-side validations failed.
|
1542
|
+
// * isNew: The record was created on the client and the adapter
|
1543
|
+
// did not yet report that it was successfully saved.
|
1544
|
+
// * isValid: No client-side validations have failed and the
|
1545
|
+
// adapter did not report any server-side validation failures.
|
1546
|
+
// * isPending: A record `isPending` when it belongs to an
|
1547
|
+
// association on another record and that record has not been
|
1548
|
+
// saved. A record in this state cannot be saved because it
|
1549
|
+
// lacks a "foreign key" that will be supplied by its parent
|
1550
|
+
// association when the parent record has been created. When
|
1551
|
+
// the adapter reports that the parent has saved, the
|
1552
|
+
// `isPending` property on all children will become `false`
|
1553
|
+
// and the transaction will try to commit the records.
|
1554
|
+
|
1555
|
+
// This mixin is mixed into various uncommitted states. Make
|
1556
|
+
// sure to mix it in *after* the class definition, so its
|
1557
|
+
// super points to the class definition.
|
1558
|
+
var Uncommitted = Ember.Mixin.create({
|
1559
|
+
setProperty: setProperty,
|
1281
1560
|
|
1282
|
-
|
1283
|
-
|
1284
|
-
model = get(manager, 'model');
|
1561
|
+
deleteRecord: function(manager) {
|
1562
|
+
this._super(manager);
|
1285
1563
|
|
1286
|
-
model
|
1287
|
-
|
1564
|
+
var model = get(manager, 'model'),
|
1565
|
+
dirtyType = get(this, 'dirtyType');
|
1566
|
+
|
1567
|
+
model.withTransaction(function(t) {
|
1568
|
+
t.modelBecameClean(dirtyType, model);
|
1288
1569
|
});
|
1289
|
-
}
|
1570
|
+
}
|
1571
|
+
});
|
1572
|
+
|
1573
|
+
// These mixins are mixed into substates of the concrete
|
1574
|
+
// subclasses of DirtyState.
|
1575
|
+
|
1576
|
+
var CreatedUncommitted = Ember.Mixin.create({
|
1577
|
+
deleteRecord: function(manager) {
|
1578
|
+
this._super(manager);
|
1579
|
+
|
1580
|
+
manager.goToState('deleted.saved');
|
1581
|
+
}
|
1582
|
+
});
|
1290
1583
|
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1584
|
+
var UpdatedUncommitted = Ember.Mixin.create({
|
1585
|
+
deleteRecord: function(manager) {
|
1586
|
+
this._super(manager);
|
1294
1587
|
|
1295
|
-
|
1588
|
+
var model = get(manager, 'model');
|
1296
1589
|
|
1297
|
-
model.withTransaction(function
|
1298
|
-
t.modelBecameClean(
|
1590
|
+
model.withTransaction(function(t) {
|
1591
|
+
t.modelBecameClean('created', model);
|
1299
1592
|
});
|
1300
|
-
},
|
1301
1593
|
|
1302
|
-
|
1594
|
+
manager.goToState('deleted');
|
1595
|
+
}
|
1596
|
+
});
|
1303
1597
|
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1598
|
+
// The dirty state is a abstract state whose functionality is
|
1599
|
+
// shared between the `created` and `updated` states.
|
1600
|
+
//
|
1601
|
+
// The deleted state shares the `isDirty` flag with the
|
1602
|
+
// subclasses of `DirtyState`, but with a very different
|
1603
|
+
// implementation.
|
1604
|
+
var DirtyState = DS.State.extend({
|
1605
|
+
initialState: 'uncommitted',
|
1606
|
+
|
1607
|
+
// FLAGS
|
1608
|
+
isDirty: true,
|
1609
|
+
|
1610
|
+
// SUBSTATES
|
1611
|
+
|
1612
|
+
// When a record first becomes dirty, it is `uncommitted`.
|
1613
|
+
// This means that there are local pending changes,
|
1614
|
+
// but they have not yet begun to be saved.
|
1615
|
+
uncommitted: DS.State.extend({
|
1616
|
+
// TRANSITIONS
|
1617
|
+
enter: function(manager) {
|
1618
|
+
var dirtyType = get(this, 'dirtyType'),
|
1619
|
+
model = get(manager, 'model');
|
1620
|
+
|
1621
|
+
model.withTransaction(function (t) {
|
1622
|
+
t.modelBecameDirty(dirtyType, model);
|
1623
|
+
});
|
1624
|
+
},
|
1625
|
+
|
1626
|
+
exit: function(manager) {
|
1627
|
+
var model = get(manager, 'model');
|
1628
|
+
manager.send('invokeLifecycleCallbacks', model);
|
1629
|
+
},
|
1630
|
+
|
1631
|
+
// EVENTS
|
1632
|
+
deleteRecord: Ember.K,
|
1307
1633
|
|
1308
|
-
|
1634
|
+
waitingOn: function(manager, object) {
|
1635
|
+
waitingOn(manager, object);
|
1636
|
+
manager.goToState('pending');
|
1637
|
+
},
|
1638
|
+
|
1639
|
+
willCommit: function(manager) {
|
1640
|
+
manager.goToState('inFlight');
|
1641
|
+
}
|
1642
|
+
}, Uncommitted),
|
1643
|
+
|
1644
|
+
// Once a record has been handed off to the adapter to be
|
1645
|
+
// saved, it is in the 'in flight' state. Changes to the
|
1646
|
+
// record cannot be made during this window.
|
1647
|
+
inFlight: DS.State.extend({
|
1648
|
+
// FLAGS
|
1309
1649
|
isSaving: true,
|
1310
1650
|
|
1311
|
-
|
1651
|
+
// TRANSITIONS
|
1652
|
+
enter: function(manager) {
|
1653
|
+
var dirtyType = get(this, 'dirtyType'),
|
1654
|
+
model = get(manager, 'model');
|
1655
|
+
|
1656
|
+
model.withTransaction(function (t) {
|
1657
|
+
t.modelBecameClean(dirtyType, model);
|
1658
|
+
});
|
1659
|
+
},
|
1660
|
+
|
1661
|
+
// EVENTS
|
1662
|
+
didCommit: function(manager) {
|
1312
1663
|
manager.goToState('loaded');
|
1313
1664
|
},
|
1314
1665
|
|
1315
|
-
|
1666
|
+
becameInvalid: function(manager, errors) {
|
1316
1667
|
var model = get(manager, 'model');
|
1317
1668
|
|
1318
1669
|
set(model, 'errors', errors);
|
1319
1670
|
manager.goToState('invalid');
|
1320
|
-
}
|
1671
|
+
},
|
1672
|
+
|
1673
|
+
didChangeData: didChangeData
|
1674
|
+
}),
|
1675
|
+
|
1676
|
+
// If a record becomes associated with a newly created
|
1677
|
+
// parent record, it will be `pending` until the parent
|
1678
|
+
// record has successfully persisted. Once this happens,
|
1679
|
+
// this record can use the parent's primary key as its
|
1680
|
+
// foreign key.
|
1681
|
+
//
|
1682
|
+
// If the record's transaction had already started to
|
1683
|
+
// commit, the record will transition to the `inFlight`
|
1684
|
+
// state. If it had not, the record will transition to
|
1685
|
+
// the `uncommitted` state.
|
1686
|
+
pending: DS.State.extend({
|
1687
|
+
initialState: 'uncommitted',
|
1688
|
+
|
1689
|
+
// FLAGS
|
1690
|
+
isPending: true,
|
1691
|
+
|
1692
|
+
// SUBSTATES
|
1693
|
+
|
1694
|
+
// A pending record whose transaction has not yet
|
1695
|
+
// started to commit is in this state.
|
1696
|
+
uncommitted: DS.State.extend({
|
1697
|
+
// EVENTS
|
1698
|
+
deleteRecord: function(manager) {
|
1699
|
+
var model = get(manager, 'model'),
|
1700
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1701
|
+
tuple;
|
1702
|
+
|
1703
|
+
// since we are leaving the pending state, remove any
|
1704
|
+
// observers we have registered on other records.
|
1705
|
+
for (var prop in pendingQueue) {
|
1706
|
+
if (!pendingQueue.hasOwnProperty(prop)) { continue; }
|
1707
|
+
|
1708
|
+
tuple = pendingQueue[prop];
|
1709
|
+
Ember.removeObserver(tuple[0], 'id', tuple[1]);
|
1710
|
+
}
|
1711
|
+
},
|
1712
|
+
|
1713
|
+
willCommit: function(manager) {
|
1714
|
+
manager.goToState('committing');
|
1715
|
+
},
|
1716
|
+
|
1717
|
+
doneWaitingOn: function(manager, object) {
|
1718
|
+
var model = get(manager, 'model'),
|
1719
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1720
|
+
objectGuid = guidFor(object);
|
1721
|
+
|
1722
|
+
delete pendingQueue[objectGuid];
|
1723
|
+
|
1724
|
+
if (isEmptyObject(pendingQueue)) {
|
1725
|
+
manager.send('doneWaiting');
|
1726
|
+
}
|
1727
|
+
},
|
1728
|
+
|
1729
|
+
doneWaiting: function(manager) {
|
1730
|
+
var dirtyType = get(this, 'dirtyType');
|
1731
|
+
manager.goToState(dirtyType + '.uncommitted');
|
1732
|
+
}
|
1733
|
+
}, Uncommitted),
|
1734
|
+
|
1735
|
+
// A pending record whose transaction has started
|
1736
|
+
// to commit is in this state. Since it has not yet
|
1737
|
+
// been sent to the adapter, it is not `inFlight`
|
1738
|
+
// until all of its dependencies have been committed.
|
1739
|
+
committing: DS.State.extend({
|
1740
|
+
// FLAGS
|
1741
|
+
isSaving: true,
|
1742
|
+
|
1743
|
+
// EVENTS
|
1744
|
+
doneWaitingOn: function(manager, object) {
|
1745
|
+
var model = get(manager, 'model'),
|
1746
|
+
pendingQueue = get(model, 'pendingQueue'),
|
1747
|
+
objectGuid = guidFor(object);
|
1748
|
+
|
1749
|
+
delete pendingQueue[objectGuid];
|
1750
|
+
|
1751
|
+
if (isEmptyObject(pendingQueue)) {
|
1752
|
+
manager.send('doneWaiting');
|
1753
|
+
}
|
1754
|
+
},
|
1755
|
+
|
1756
|
+
doneWaiting: function(manager) {
|
1757
|
+
var model = get(manager, 'model'),
|
1758
|
+
transaction = get(model, 'transaction');
|
1759
|
+
|
1760
|
+
// Now that the model is no longer pending, schedule
|
1761
|
+
// the transaction to commit.
|
1762
|
+
Ember.run.once(transaction, transaction.commit);
|
1763
|
+
},
|
1764
|
+
|
1765
|
+
willCommit: function(manager) {
|
1766
|
+
var dirtyType = get(this, 'dirtyType');
|
1767
|
+
manager.goToState(dirtyType + '.inFlight');
|
1768
|
+
}
|
1769
|
+
})
|
1321
1770
|
}),
|
1322
1771
|
|
1772
|
+
// A record is in the `invalid` state when its client-side
|
1773
|
+
// invalidations have failed, or if the adapter has indicated
|
1774
|
+
// the the record failed server-side invalidations.
|
1323
1775
|
invalid: DS.State.extend({
|
1776
|
+
// FLAGS
|
1324
1777
|
isValid: false,
|
1325
1778
|
|
1779
|
+
// EVENTS
|
1780
|
+
deleteRecord: function(manager) {
|
1781
|
+
manager.goToState('deleted');
|
1782
|
+
},
|
1783
|
+
|
1326
1784
|
setProperty: function(manager, context) {
|
1327
1785
|
setProperty(manager, context);
|
1328
1786
|
|
1329
|
-
var
|
1330
|
-
model = get(manager, 'model'),
|
1787
|
+
var model = get(manager, 'model'),
|
1331
1788
|
errors = get(model, 'errors'),
|
1332
1789
|
key = context.key;
|
1333
1790
|
|
1334
1791
|
delete errors[key];
|
1335
1792
|
|
1336
|
-
if (
|
1337
|
-
manager.
|
1793
|
+
if (!hasDefinedProperties(errors)) {
|
1794
|
+
manager.send('becameValid');
|
1338
1795
|
}
|
1796
|
+
},
|
1797
|
+
|
1798
|
+
becameValid: function(manager) {
|
1799
|
+
manager.goToState('uncommitted');
|
1339
1800
|
}
|
1340
1801
|
})
|
1341
1802
|
});
|
1342
1803
|
|
1804
|
+
// The created and updated states are created outside the state
|
1805
|
+
// chart so we can reopen their substates and add mixins as
|
1806
|
+
// necessary.
|
1807
|
+
|
1808
|
+
var createdState = DirtyState.create({
|
1809
|
+
dirtyType: 'created',
|
1810
|
+
|
1811
|
+
// FLAGS
|
1812
|
+
isNew: true,
|
1813
|
+
|
1814
|
+
// EVENTS
|
1815
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
1816
|
+
model.didCreate();
|
1817
|
+
}
|
1818
|
+
});
|
1819
|
+
|
1820
|
+
var updatedState = DirtyState.create({
|
1821
|
+
dirtyType: 'updated',
|
1822
|
+
|
1823
|
+
// EVENTS
|
1824
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
1825
|
+
model.didUpdate();
|
1826
|
+
}
|
1827
|
+
});
|
1828
|
+
|
1829
|
+
// The created.uncommitted state and created.pending.uncommitted share
|
1830
|
+
// some logic defined in CreatedUncommitted.
|
1831
|
+
createdState.states.uncommitted.reopen(CreatedUncommitted);
|
1832
|
+
createdState.states.pending.states.uncommitted.reopen(CreatedUncommitted);
|
1833
|
+
|
1834
|
+
// The updated.uncommitted state and updated.pending.uncommitted share
|
1835
|
+
// some logic defined in UpdatedUncommitted.
|
1836
|
+
updatedState.states.uncommitted.reopen(UpdatedUncommitted);
|
1837
|
+
updatedState.states.pending.states.uncommitted.reopen(UpdatedUncommitted);
|
1838
|
+
|
1343
1839
|
var states = {
|
1344
1840
|
rootState: Ember.State.create({
|
1841
|
+
// FLAGS
|
1345
1842
|
isLoaded: false,
|
1346
1843
|
isDirty: false,
|
1347
1844
|
isSaving: false,
|
@@ -1349,118 +1846,163 @@ var states = {
|
|
1349
1846
|
isError: false,
|
1350
1847
|
isNew: false,
|
1351
1848
|
isValid: true,
|
1849
|
+
isPending: false,
|
1352
1850
|
|
1353
|
-
|
1354
|
-
|
1355
|
-
didCreate: function(manager) {
|
1356
|
-
manager.goToState('loaded.created');
|
1357
|
-
},
|
1851
|
+
// SUBSTATES
|
1358
1852
|
|
1853
|
+
// A record begins its lifecycle in the `empty` state.
|
1854
|
+
// If its data will come from the adapter, it will
|
1855
|
+
// transition into the `loading` state. Otherwise, if
|
1856
|
+
// the record is being created on the client, it will
|
1857
|
+
// transition into the `created` state.
|
1359
1858
|
empty: DS.State.create({
|
1859
|
+
// EVENTS
|
1360
1860
|
loadingData: function(manager) {
|
1361
1861
|
manager.goToState('loading');
|
1862
|
+
},
|
1863
|
+
|
1864
|
+
didChangeData: function(manager) {
|
1865
|
+
didChangeData(manager);
|
1866
|
+
|
1867
|
+
manager.goToState('loaded.created');
|
1362
1868
|
}
|
1363
1869
|
}),
|
1364
1870
|
|
1871
|
+
// A record enters this state when the store askes
|
1872
|
+
// the adapter for its data. It remains in this state
|
1873
|
+
// until the adapter provides the requested data.
|
1874
|
+
//
|
1875
|
+
// Usually, this process is asynchronous, using an
|
1876
|
+
// XHR to retrieve the data.
|
1365
1877
|
loading: DS.State.create({
|
1366
|
-
|
1367
|
-
|
1878
|
+
// TRANSITIONS
|
1368
1879
|
exit: function(manager) {
|
1369
1880
|
var model = get(manager, 'model');
|
1370
1881
|
model.didLoad();
|
1371
1882
|
},
|
1372
1883
|
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
if (data !== null) {
|
1380
|
-
manager.goToState('loaded');
|
1381
|
-
}
|
1884
|
+
// EVENTS
|
1885
|
+
didChangeData: function(manager, data) {
|
1886
|
+
didChangeData(manager);
|
1887
|
+
manager.send('loadedData');
|
1888
|
+
},
|
1382
1889
|
|
1383
|
-
|
1890
|
+
loadedData: function(manager) {
|
1891
|
+
manager.goToState('loaded');
|
1384
1892
|
}
|
1385
1893
|
}),
|
1386
1894
|
|
1895
|
+
// A record enters this state when its data is populated.
|
1896
|
+
// Most of a record's lifecycle is spent inside substates
|
1897
|
+
// of the `loaded` state.
|
1387
1898
|
loaded: DS.State.create({
|
1899
|
+
initialState: 'saved',
|
1900
|
+
|
1901
|
+
// FLAGS
|
1388
1902
|
isLoaded: true,
|
1389
1903
|
|
1390
|
-
|
1904
|
+
// SUBSTATES
|
1391
1905
|
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1906
|
+
// If there are no local changes to a record, it remains
|
1907
|
+
// in the `saved` state.
|
1908
|
+
saved: DS.State.create({
|
1909
|
+
// EVENTS
|
1910
|
+
setProperty: function(manager, context) {
|
1911
|
+
setProperty(manager, context);
|
1912
|
+
manager.goToState('updated');
|
1913
|
+
},
|
1396
1914
|
|
1397
|
-
|
1398
|
-
manager.goToState('deleted');
|
1399
|
-
},
|
1915
|
+
didChangeData: didChangeData,
|
1400
1916
|
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1917
|
+
deleteRecord: function(manager) {
|
1918
|
+
manager.goToState('deleted');
|
1919
|
+
},
|
1404
1920
|
|
1405
|
-
|
1406
|
-
|
1921
|
+
waitingOn: function(manager, object) {
|
1922
|
+
waitingOn(manager, object);
|
1923
|
+
manager.goToState('updated.pending');
|
1407
1924
|
}
|
1408
1925
|
}),
|
1409
1926
|
|
1410
|
-
|
1411
|
-
|
1927
|
+
// A record is in this state after it has been locally
|
1928
|
+
// created but before the adapter has indicated that
|
1929
|
+
// it has been saved.
|
1930
|
+
created: createdState,
|
1412
1931
|
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1932
|
+
// A record is in this state if it has already been
|
1933
|
+
// saved to the server, but there are new local changes
|
1934
|
+
// that have not yet been saved.
|
1935
|
+
updated: updatedState,
|
1417
1936
|
}),
|
1418
1937
|
|
1938
|
+
// A record is in this state if it was deleted from the store.
|
1419
1939
|
deleted: DS.State.create({
|
1940
|
+
// FLAGS
|
1420
1941
|
isDeleted: true,
|
1421
1942
|
isLoaded: true,
|
1422
1943
|
isDirty: true,
|
1423
1944
|
|
1424
|
-
|
1945
|
+
// SUBSTATES
|
1425
1946
|
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1947
|
+
// When a record is deleted, it enters the `start`
|
1948
|
+
// state. It will exit this state when the record's
|
1949
|
+
// transaction starts to commit.
|
1950
|
+
start: DS.State.create({
|
1951
|
+
// TRANSITIONS
|
1952
|
+
enter: function(manager) {
|
1953
|
+
var model = get(manager, 'model');
|
1954
|
+
var store = get(model, 'store');
|
1429
1955
|
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1956
|
+
if (store) {
|
1957
|
+
store.removeFromModelArrays(model);
|
1958
|
+
}
|
1433
1959
|
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1960
|
+
model.withTransaction(function(t) {
|
1961
|
+
t.modelBecameDirty('deleted', model);
|
1962
|
+
});
|
1963
|
+
},
|
1438
1964
|
|
1439
|
-
|
1440
|
-
manager
|
1441
|
-
|
1965
|
+
// EVENTS
|
1966
|
+
willCommit: function(manager) {
|
1967
|
+
manager.goToState('inFlight');
|
1968
|
+
}
|
1969
|
+
}),
|
1442
1970
|
|
1443
|
-
|
1971
|
+
// After a record's transaction is committing, but
|
1972
|
+
// before the adapter indicates that the deletion
|
1973
|
+
// has saved to the server, a record is in the
|
1974
|
+
// `inFlight` substate of `deleted`.
|
1975
|
+
inFlight: DS.State.create({
|
1976
|
+
// FLAGS
|
1444
1977
|
isSaving: true,
|
1445
1978
|
|
1446
|
-
|
1447
|
-
manager.goToState('saved');
|
1448
|
-
},
|
1449
|
-
|
1979
|
+
// TRANSITIONS
|
1450
1980
|
exit: function(stateManager) {
|
1451
1981
|
var model = get(stateManager, 'model');
|
1452
1982
|
|
1453
1983
|
model.withTransaction(function(t) {
|
1454
1984
|
t.modelBecameClean('deleted', model);
|
1455
1985
|
});
|
1986
|
+
},
|
1987
|
+
|
1988
|
+
// EVENTS
|
1989
|
+
didCommit: function(manager) {
|
1990
|
+
manager.goToState('saved');
|
1456
1991
|
}
|
1457
1992
|
}),
|
1458
1993
|
|
1994
|
+
// Once the adapter indicates that the deletion has
|
1995
|
+
// been saved, the record enters the `saved` substate
|
1996
|
+
// of `deleted`.
|
1459
1997
|
saved: DS.State.create({
|
1998
|
+
// FLAGS
|
1460
1999
|
isDirty: false
|
1461
2000
|
})
|
1462
2001
|
}),
|
1463
2002
|
|
2003
|
+
// If the adapter indicates that there was an unknown
|
2004
|
+
// error saving a record, the record enters the `error`
|
2005
|
+
// state.
|
1464
2006
|
error: DS.State.create({
|
1465
2007
|
isError: true
|
1466
2008
|
})
|
@@ -1473,10 +2015,110 @@ DS.StateManager = Ember.StateManager.extend({
|
|
1473
2015
|
states: states
|
1474
2016
|
});
|
1475
2017
|
|
2018
|
+
})({});
|
2019
|
+
|
2020
|
+
|
2021
|
+
(function(exports) {
|
2022
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
2023
|
+
|
1476
2024
|
var retrieveFromCurrentState = Ember.computed(function(key) {
|
1477
2025
|
return get(getPath(this, 'stateManager.currentState'), key);
|
1478
2026
|
}).property('stateManager.currentState').cacheable();
|
1479
2027
|
|
2028
|
+
// This object is a regular JS object for performance. It is only
|
2029
|
+
// used internally for bookkeeping purposes.
|
2030
|
+
var DataProxy = function(record) {
|
2031
|
+
this.record = record;
|
2032
|
+
this.unsavedData = {};
|
2033
|
+
this.associations = {};
|
2034
|
+
};
|
2035
|
+
|
2036
|
+
DataProxy.prototype = {
|
2037
|
+
get: function(key) { return Ember.get(this, key); },
|
2038
|
+
set: function(key, value) { return Ember.set(this, key, value); },
|
2039
|
+
|
2040
|
+
setAssociation: function(key, value) {
|
2041
|
+
this.associations[key] = value;
|
2042
|
+
},
|
2043
|
+
|
2044
|
+
savedData: function() {
|
2045
|
+
var savedData = this._savedData;
|
2046
|
+
if (savedData) { return savedData; }
|
2047
|
+
|
2048
|
+
var record = this.record,
|
2049
|
+
clientId = get(record, 'clientId'),
|
2050
|
+
store = get(record, 'store');
|
2051
|
+
|
2052
|
+
if (store) {
|
2053
|
+
savedData = store.dataForRecord(record);
|
2054
|
+
this._savedData = savedData;
|
2055
|
+
return savedData;
|
2056
|
+
}
|
2057
|
+
},
|
2058
|
+
|
2059
|
+
unknownProperty: function(key) {
|
2060
|
+
var unsavedData = this.unsavedData,
|
2061
|
+
associations = this.associations,
|
2062
|
+
savedData = this.savedData(),
|
2063
|
+
store;
|
2064
|
+
|
2065
|
+
var value = unsavedData[key], association;
|
2066
|
+
|
2067
|
+
// if this is a belongsTo association, this will
|
2068
|
+
// be a clientId.
|
2069
|
+
association = associations[key];
|
2070
|
+
|
2071
|
+
if (association !== undefined) {
|
2072
|
+
store = get(this.record, 'store');
|
2073
|
+
return store.clientIdToId[association];
|
2074
|
+
}
|
2075
|
+
|
2076
|
+
if (savedData && value === undefined) {
|
2077
|
+
value = savedData[key];
|
2078
|
+
}
|
2079
|
+
|
2080
|
+
return value;
|
2081
|
+
},
|
2082
|
+
|
2083
|
+
setUnknownProperty: function(key, value) {
|
2084
|
+
var record = this.record,
|
2085
|
+
unsavedData = this.unsavedData;
|
2086
|
+
|
2087
|
+
unsavedData[key] = value;
|
2088
|
+
|
2089
|
+
// At the end of the run loop, notify model arrays that
|
2090
|
+
// this record has changed so they can re-evaluate its contents
|
2091
|
+
// to determine membership.
|
2092
|
+
Ember.run.once(record, record.notifyHashWasUpdated);
|
2093
|
+
|
2094
|
+
return value;
|
2095
|
+
},
|
2096
|
+
|
2097
|
+
commit: function() {
|
2098
|
+
var record = this.record;
|
2099
|
+
|
2100
|
+
var unsavedData = this.unsavedData;
|
2101
|
+
var savedData = this.savedData();
|
2102
|
+
|
2103
|
+
for (var prop in unsavedData) {
|
2104
|
+
if (unsavedData.hasOwnProperty(prop)) {
|
2105
|
+
savedData[prop] = unsavedData[prop];
|
2106
|
+
delete unsavedData[prop];
|
2107
|
+
}
|
2108
|
+
}
|
2109
|
+
|
2110
|
+
record.notifyPropertyChange('data');
|
2111
|
+
},
|
2112
|
+
|
2113
|
+
rollback: function() {
|
2114
|
+
this.unsavedData = {};
|
2115
|
+
},
|
2116
|
+
|
2117
|
+
adapterDidUpdate: function(data) {
|
2118
|
+
this.unsavedData = {};
|
2119
|
+
}
|
2120
|
+
};
|
2121
|
+
|
1480
2122
|
DS.Model = Ember.Object.extend({
|
1481
2123
|
isLoaded: retrieveFromCurrentState,
|
1482
2124
|
isDirty: retrieveFromCurrentState,
|
@@ -1484,207 +2126,379 @@ DS.Model = Ember.Object.extend({
|
|
1484
2126
|
isDeleted: retrieveFromCurrentState,
|
1485
2127
|
isError: retrieveFromCurrentState,
|
1486
2128
|
isNew: retrieveFromCurrentState,
|
2129
|
+
isPending: retrieveFromCurrentState,
|
1487
2130
|
isValid: retrieveFromCurrentState,
|
1488
2131
|
|
1489
2132
|
clientId: null,
|
2133
|
+
transaction: null,
|
2134
|
+
stateManager: null,
|
2135
|
+
pendingQueue: null,
|
2136
|
+
errors: null,
|
1490
2137
|
|
1491
2138
|
// because unknownProperty is used, any internal property
|
1492
2139
|
// must be initialized here.
|
1493
2140
|
primaryKey: 'id',
|
1494
|
-
|
1495
|
-
|
2141
|
+
id: Ember.computed(function(key, value) {
|
2142
|
+
var primaryKey = get(this, 'primaryKey'),
|
2143
|
+
data = get(this, 'data');
|
1496
2144
|
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
2145
|
+
if (arguments.length === 2) {
|
2146
|
+
set(data, primaryKey, value);
|
2147
|
+
return value;
|
2148
|
+
}
|
1500
2149
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
model: this
|
1504
|
-
});
|
2150
|
+
return data && get(data, primaryKey);
|
2151
|
+
}).property('primaryKey', 'data'),
|
1505
2152
|
|
1506
|
-
|
1507
|
-
|
2153
|
+
// The following methods are callbacks invoked by `getJSON`. You
|
2154
|
+
// can override one of the callbacks to override specific behavior,
|
2155
|
+
// or getJSON itself.
|
2156
|
+
//
|
2157
|
+
// If you override getJSON, you can invoke these callbacks manually
|
2158
|
+
// to get the default behavior.
|
2159
|
+
|
2160
|
+
/**
|
2161
|
+
Add the record's primary key to the JSON hash.
|
2162
|
+
|
2163
|
+
The default implementation uses the record's specified `primaryKey`
|
2164
|
+
and the `id` computed property, which are passed in as parameters.
|
2165
|
+
|
2166
|
+
@param {Object} json the JSON hash being built
|
2167
|
+
@param {Number|String} id the record's id
|
2168
|
+
@param {String} key the primaryKey for the record
|
2169
|
+
*/
|
2170
|
+
addIdToJSON: function(json, id, key) {
|
2171
|
+
if (id) { json[key] = id; }
|
1508
2172
|
},
|
1509
2173
|
|
1510
|
-
|
1511
|
-
|
2174
|
+
/**
|
2175
|
+
Add the attributes' current values to the JSON hash.
|
1512
2176
|
|
1513
|
-
|
2177
|
+
The default implementation gets the current value of each
|
2178
|
+
attribute from the `data`, and uses a `defaultValue` if
|
2179
|
+
specified in the `DS.attr` definition.
|
2180
|
+
|
2181
|
+
@param {Object} json the JSON hash being build
|
2182
|
+
@param {Ember.Map} attributes a Map of attributes
|
2183
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2184
|
+
*/
|
2185
|
+
addAttributesToJSON: function(json, attributes, data) {
|
2186
|
+
attributes.forEach(function(name, meta) {
|
2187
|
+
var key = meta.key(this.constructor),
|
2188
|
+
value = get(data, key);
|
2189
|
+
|
2190
|
+
if (value === undefined) {
|
2191
|
+
value = meta.options.defaultValue;
|
2192
|
+
}
|
2193
|
+
|
2194
|
+
json[key] = value;
|
2195
|
+
}, this);
|
1514
2196
|
},
|
1515
2197
|
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
2198
|
+
/**
|
2199
|
+
Add the value of a `hasMany` association to the JSON hash.
|
2200
|
+
|
2201
|
+
The default implementation honors the `embedded` option
|
2202
|
+
passed to `DS.hasMany`. If embedded, `toJSON` is recursively
|
2203
|
+
called on the child records. If not, the `id` of each
|
2204
|
+
record is added.
|
2205
|
+
|
2206
|
+
Note that if a record is not embedded and does not
|
2207
|
+
yet have an `id` (usually provided by the server), it
|
2208
|
+
will not be included in the output.
|
2209
|
+
|
2210
|
+
@param {Object} json the JSON hash being built
|
2211
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2212
|
+
@param {Object} meta information about the association
|
2213
|
+
@param {Object} options options passed to `toJSON`
|
2214
|
+
*/
|
2215
|
+
addHasManyToJSON: function(json, data, meta, options) {
|
2216
|
+
var key = meta.key,
|
2217
|
+
manyArray = get(this, key),
|
2218
|
+
records = [],
|
2219
|
+
clientId, id;
|
2220
|
+
|
2221
|
+
if (meta.options.embedded) {
|
2222
|
+
// TODO: Avoid materializing embedded hashes if possible
|
2223
|
+
manyArray.forEach(function(record) {
|
2224
|
+
records.push(record.toJSON(options));
|
2225
|
+
});
|
2226
|
+
} else {
|
2227
|
+
var clientIds = get(manyArray, 'content');
|
2228
|
+
|
2229
|
+
for (var i=0, l=clientIds.length; i<l; i++) {
|
2230
|
+
clientId = clientIds[i];
|
2231
|
+
id = get(this, 'store').clientIdToId[clientId];
|
2232
|
+
|
2233
|
+
if (id !== undefined) {
|
2234
|
+
records.push(id);
|
2235
|
+
}
|
2236
|
+
}
|
2237
|
+
}
|
2238
|
+
|
2239
|
+
json[key] = records;
|
1519
2240
|
},
|
1520
2241
|
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
2242
|
+
/**
|
2243
|
+
Add the value of a `belongsTo` association to the JSON hash.
|
2244
|
+
|
2245
|
+
The default implementation always includes the `id`.
|
2246
|
+
|
2247
|
+
@param {Object} json the JSON hash being built
|
2248
|
+
@param {DataProxy} data the record's data, accessed with `get` and `set`.
|
2249
|
+
@param {Object} meta information about the association
|
2250
|
+
@param {Object} options options passed to `toJSON`
|
2251
|
+
*/
|
2252
|
+
addBelongsToToJSON: function(json, data, meta, options) {
|
2253
|
+
var key = meta.key, id;
|
2254
|
+
|
2255
|
+
if (id = data.get(key)) {
|
2256
|
+
json[key] = id;
|
2257
|
+
}
|
1524
2258
|
},
|
1525
2259
|
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
2260
|
+
/**
|
2261
|
+
Create a JSON representation of the record, including its `id`,
|
2262
|
+
attributes and associations. Honor any settings defined on the
|
2263
|
+
attributes or associations (such as `embedded` or `key`).
|
2264
|
+
*/
|
2265
|
+
toJSON: function(options) {
|
2266
|
+
var data = get(this, 'data'),
|
2267
|
+
result = {},
|
2268
|
+
type = this.constructor,
|
2269
|
+
attributes = get(type, 'attributes'),
|
2270
|
+
primaryKey = get(this, 'primaryKey'),
|
2271
|
+
id = get(this, 'id'),
|
2272
|
+
store = get(this, 'store'),
|
2273
|
+
associations;
|
2274
|
+
|
2275
|
+
options = options || {};
|
2276
|
+
|
2277
|
+
// delegate to `addIdToJSON` callback
|
2278
|
+
this.addIdToJSON(result, id, primaryKey);
|
2279
|
+
|
2280
|
+
// delegate to `addAttributesToJSON` callback
|
2281
|
+
this.addAttributesToJSON(result, attributes, data);
|
2282
|
+
|
2283
|
+
associations = get(type, 'associationsByName');
|
2284
|
+
|
2285
|
+
// add associations, delegating to `addHasManyToJSON` and
|
2286
|
+
// `addBelongsToToJSON`.
|
2287
|
+
associations.forEach(function(key, meta) {
|
2288
|
+
if (options.associations && meta.kind === 'hasMany') {
|
2289
|
+
this.addHasManyToJSON(result, data, meta, options);
|
2290
|
+
} else if (meta.kind === 'belongsTo') {
|
2291
|
+
this.addBelongsToToJSON(result, data, meta, options);
|
2292
|
+
}
|
2293
|
+
}, this);
|
2294
|
+
|
2295
|
+
return result;
|
1529
2296
|
},
|
1530
2297
|
|
1531
|
-
|
1532
|
-
this
|
1533
|
-
|
2298
|
+
data: Ember.computed(function() {
|
2299
|
+
return new DataProxy(this);
|
2300
|
+
}).cacheable(),
|
2301
|
+
|
2302
|
+
didLoad: Ember.K,
|
2303
|
+
didUpdate: Ember.K,
|
2304
|
+
didCreate: Ember.K,
|
2305
|
+
|
2306
|
+
init: function() {
|
2307
|
+
var stateManager = DS.StateManager.create({
|
2308
|
+
model: this
|
2309
|
+
});
|
2310
|
+
|
2311
|
+
set(this, 'pendingQueue', {});
|
2312
|
+
|
2313
|
+
set(this, 'stateManager', stateManager);
|
2314
|
+
stateManager.goToState('empty');
|
1534
2315
|
},
|
1535
2316
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
2317
|
+
destroy: function() {
|
2318
|
+
if (!get(this, 'isDeleted')) {
|
2319
|
+
this.deleteRecord();
|
2320
|
+
}
|
2321
|
+
this._super();
|
1539
2322
|
},
|
1540
2323
|
|
1541
|
-
|
1542
|
-
|
1543
|
-
stateManager.send('willLoadData');
|
2324
|
+
send: function(name, context) {
|
2325
|
+
return get(this, 'stateManager').send(name, context);
|
1544
2326
|
},
|
1545
2327
|
|
1546
|
-
|
1547
|
-
var
|
1548
|
-
|
2328
|
+
withTransaction: function(fn) {
|
2329
|
+
var transaction = get(this, 'transaction');
|
2330
|
+
if (transaction) { fn(transaction); }
|
1549
2331
|
},
|
1550
2332
|
|
1551
|
-
|
1552
|
-
|
1553
|
-
stateManager.send('didUpdate');
|
2333
|
+
setProperty: function(key, value) {
|
2334
|
+
this.send('setProperty', { key: key, value: value });
|
1554
2335
|
},
|
1555
2336
|
|
1556
|
-
|
1557
|
-
|
1558
|
-
stateManager.send('didCreate');
|
2337
|
+
deleteRecord: function() {
|
2338
|
+
this.send('deleteRecord');
|
1559
2339
|
},
|
1560
2340
|
|
1561
|
-
|
1562
|
-
|
1563
|
-
stateManager.send('didDelete');
|
2341
|
+
waitingOn: function(record) {
|
2342
|
+
this.send('waitingOn', record);
|
1564
2343
|
},
|
1565
2344
|
|
1566
|
-
|
1567
|
-
var
|
1568
|
-
|
2345
|
+
notifyHashWasUpdated: function() {
|
2346
|
+
var store = get(this, 'store');
|
2347
|
+
if (store) {
|
2348
|
+
store.hashWasUpdated(this.constructor, get(this, 'clientId'), this);
|
2349
|
+
}
|
1569
2350
|
},
|
1570
2351
|
|
1571
2352
|
unknownProperty: function(key) {
|
1572
2353
|
var data = get(this, 'data');
|
1573
2354
|
|
1574
|
-
if (data) {
|
1575
|
-
|
2355
|
+
if (data && key in data) {
|
2356
|
+
ember_assert("You attempted to access the " + key + " property on a model without defining an attribute.", false);
|
1576
2357
|
}
|
1577
2358
|
},
|
1578
2359
|
|
1579
2360
|
setUnknownProperty: function(key, value) {
|
1580
2361
|
var data = get(this, 'data');
|
1581
|
-
ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
|
1582
2362
|
|
1583
|
-
|
1584
|
-
|
2363
|
+
if (data && key in data) {
|
2364
|
+
ember_assert("You attempted to set the " + key + " property on a model without defining an attribute.", false);
|
2365
|
+
} else {
|
2366
|
+
return this._super(key, value);
|
2367
|
+
}
|
2368
|
+
},
|
2369
|
+
|
2370
|
+
namingConvention: {
|
2371
|
+
keyToJSONKey: function(key) {
|
2372
|
+
// TODO: Strip off `is` from the front. Example: `isHipster` becomes `hipster`
|
2373
|
+
return Ember.String.decamelize(key);
|
2374
|
+
},
|
2375
|
+
|
2376
|
+
foreignKey: function(key) {
|
2377
|
+
return key + '_id';
|
2378
|
+
}
|
1585
2379
|
}
|
1586
2380
|
});
|
1587
2381
|
|
2382
|
+
// Helper function to generate store aliases.
|
2383
|
+
// This returns a function that invokes the named alias
|
2384
|
+
// on the default store, but injects the class as the
|
2385
|
+
// first parameter.
|
2386
|
+
var storeAlias = function(methodName) {
|
2387
|
+
return function() {
|
2388
|
+
var store = get(DS, 'defaultStore'),
|
2389
|
+
args = [].slice.call(arguments);
|
2390
|
+
|
2391
|
+
args.unshift(this);
|
2392
|
+
return store[methodName].apply(store, args);
|
2393
|
+
};
|
2394
|
+
};
|
2395
|
+
|
1588
2396
|
DS.Model.reopenClass({
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
2397
|
+
find: storeAlias('find'),
|
2398
|
+
filter: storeAlias('filter'),
|
2399
|
+
|
2400
|
+
_create: DS.Model.create,
|
2401
|
+
|
2402
|
+
create: function() {
|
2403
|
+
throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.");
|
2404
|
+
},
|
2405
|
+
|
2406
|
+
createRecord: storeAlias('createRecord')
|
1596
2407
|
});
|
1597
2408
|
|
1598
|
-
|
1599
|
-
var transform = DS.attr.transforms[type];
|
1600
|
-
var transformFrom = transform.from;
|
1601
|
-
var transformTo = transform.to;
|
2409
|
+
})({});
|
1602
2410
|
|
1603
|
-
return Ember.computed(function(key, value) {
|
1604
|
-
var data = get(this, 'data');
|
1605
2411
|
|
1606
|
-
|
2412
|
+
(function(exports) {
|
2413
|
+
var get = Ember.get, getPath = Ember.getPath;
|
2414
|
+
DS.Model.reopenClass({
|
2415
|
+
attributes: Ember.computed(function() {
|
2416
|
+
var map = Ember.Map.create();
|
1607
2417
|
|
1608
|
-
|
1609
|
-
if (
|
2418
|
+
this.eachComputedProperty(function(name, meta) {
|
2419
|
+
if (meta.isAttribute) { map.set(name, meta); }
|
2420
|
+
});
|
1610
2421
|
|
1611
|
-
|
1612
|
-
|
1613
|
-
ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
|
2422
|
+
return map;
|
2423
|
+
}).cacheable(),
|
1614
2424
|
|
1615
|
-
|
1616
|
-
|
1617
|
-
return value;
|
1618
|
-
}
|
1619
|
-
}).property('data');
|
1620
|
-
};
|
2425
|
+
processAttributeKeys: function() {
|
2426
|
+
if (this.processedAttributeKeys) { return; }
|
1621
2427
|
|
1622
|
-
var
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
2428
|
+
var namingConvention = getPath(this, 'proto.namingConvention');
|
2429
|
+
|
2430
|
+
this.eachComputedProperty(function(name, meta) {
|
2431
|
+
if (meta.isAttribute && !meta.options.key) {
|
2432
|
+
meta.options.key = namingConvention.keyToJSONKey(name, this);
|
2433
|
+
}
|
2434
|
+
}, this);
|
1628
2435
|
}
|
1629
|
-
};
|
2436
|
+
});
|
1630
2437
|
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
2438
|
+
DS.attr = function(type, options) {
|
2439
|
+
var transform = DS.attr.transforms[type];
|
2440
|
+
ember_assert("Could not find model attribute of type " + type, !!transform);
|
1634
2441
|
|
1635
|
-
var
|
1636
|
-
var
|
1637
|
-
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
2442
|
+
var transformFrom = transform.from;
|
2443
|
+
var transformTo = transform.to;
|
1638
2444
|
|
1639
|
-
|
1640
|
-
var data = get(this, 'data'), ids, id, association,
|
1641
|
-
store = get(this, 'store');
|
2445
|
+
options = options || {};
|
1642
2446
|
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
2447
|
+
var meta = {
|
2448
|
+
type: type,
|
2449
|
+
isAttribute: true,
|
2450
|
+
options: options,
|
1646
2451
|
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
ids = findRecord(store, type, data, key);
|
1653
|
-
association = store.findMany(type, ids);
|
2452
|
+
// this will ensure that the key always takes naming
|
2453
|
+
// conventions into consideration.
|
2454
|
+
key: function(recordType) {
|
2455
|
+
recordType.processAttributeKeys();
|
2456
|
+
return options.key;
|
1654
2457
|
}
|
2458
|
+
};
|
1655
2459
|
|
1656
|
-
|
1657
|
-
|
1658
|
-
};
|
2460
|
+
return Ember.computed(function(key, value) {
|
2461
|
+
var data;
|
1659
2462
|
|
1660
|
-
|
1661
|
-
ember_assert("The type passed to DS.hasMany must be defined", !!type);
|
1662
|
-
return hasAssociation(type, options);
|
1663
|
-
};
|
2463
|
+
key = meta.key(this.constructor);
|
1664
2464
|
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
2465
|
+
if (arguments.length === 2) {
|
2466
|
+
value = transformTo(value);
|
2467
|
+
this.setProperty(key, value);
|
2468
|
+
} else {
|
2469
|
+
data = get(this, 'data');
|
2470
|
+
value = get(data, key);
|
2471
|
+
|
2472
|
+
if (value === undefined) {
|
2473
|
+
value = options.defaultValue;
|
2474
|
+
}
|
2475
|
+
}
|
2476
|
+
|
2477
|
+
return transformFrom(value);
|
2478
|
+
// `data` is never set directly. However, it may be
|
2479
|
+
// invalidated from the state manager's setData
|
2480
|
+
// event.
|
2481
|
+
}).property('data').cacheable().meta(meta);
|
1668
2482
|
};
|
1669
2483
|
|
1670
2484
|
DS.attr.transforms = {
|
1671
2485
|
string: {
|
1672
2486
|
from: function(serialized) {
|
1673
|
-
return
|
2487
|
+
return Ember.none(serialized) ? null : String(serialized);
|
1674
2488
|
},
|
1675
2489
|
|
1676
2490
|
to: function(deserialized) {
|
1677
|
-
return
|
2491
|
+
return Ember.none(deserialized) ? null : String(deserialized);
|
1678
2492
|
}
|
1679
2493
|
},
|
1680
2494
|
|
1681
|
-
|
2495
|
+
number: {
|
1682
2496
|
from: function(serialized) {
|
1683
|
-
return
|
2497
|
+
return Ember.none(serialized) ? null : Number(serialized);
|
1684
2498
|
},
|
1685
2499
|
|
1686
2500
|
to: function(deserialized) {
|
1687
|
-
return
|
2501
|
+
return Ember.none(deserialized) ? null : Number(deserialized);
|
1688
2502
|
}
|
1689
2503
|
},
|
1690
2504
|
|
@@ -1746,6 +2560,135 @@ DS.attr.transforms = {
|
|
1746
2560
|
}
|
1747
2561
|
};
|
1748
2562
|
|
2563
|
+
|
2564
|
+
})({});
|
2565
|
+
|
2566
|
+
|
2567
|
+
(function(exports) {
|
2568
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
2569
|
+
DS.Model.reopenClass({
|
2570
|
+
typeForAssociation: function(name) {
|
2571
|
+
var association = get(this, 'associationsByName').get(name);
|
2572
|
+
return association && association.type;
|
2573
|
+
},
|
2574
|
+
|
2575
|
+
associations: Ember.computed(function() {
|
2576
|
+
var map = Ember.Map.create();
|
2577
|
+
|
2578
|
+
this.eachComputedProperty(function(name, meta) {
|
2579
|
+
if (meta.isAssociation) {
|
2580
|
+
var type = meta.type,
|
2581
|
+
typeList = map.get(type);
|
2582
|
+
|
2583
|
+
if (typeof type === 'string') {
|
2584
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2585
|
+
meta.type = type;
|
2586
|
+
}
|
2587
|
+
|
2588
|
+
if (!typeList) {
|
2589
|
+
typeList = [];
|
2590
|
+
map.set(type, typeList);
|
2591
|
+
}
|
2592
|
+
|
2593
|
+
typeList.push({ name: name, kind: meta.kind });
|
2594
|
+
}
|
2595
|
+
});
|
2596
|
+
|
2597
|
+
return map;
|
2598
|
+
}).cacheable(),
|
2599
|
+
|
2600
|
+
associationsByName: Ember.computed(function() {
|
2601
|
+
var map = Ember.Map.create(), type;
|
2602
|
+
|
2603
|
+
this.eachComputedProperty(function(name, meta) {
|
2604
|
+
if (meta.isAssociation) {
|
2605
|
+
meta.key = name;
|
2606
|
+
type = meta.type;
|
2607
|
+
|
2608
|
+
if (typeof type === 'string') {
|
2609
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2610
|
+
meta.type = type;
|
2611
|
+
}
|
2612
|
+
|
2613
|
+
map.set(name, meta);
|
2614
|
+
}
|
2615
|
+
});
|
2616
|
+
|
2617
|
+
return map;
|
2618
|
+
}).cacheable()
|
2619
|
+
});
|
2620
|
+
|
2621
|
+
|
2622
|
+
var embeddedFindRecord = function(store, type, data, key, one) {
|
2623
|
+
var association = data ? get(data, key) : one ? null : [];
|
2624
|
+
if (one) {
|
2625
|
+
return association ? store.load(type, association).id : null;
|
2626
|
+
} else {
|
2627
|
+
return association ? store.loadMany(type, association).ids : [];
|
2628
|
+
}
|
2629
|
+
};
|
2630
|
+
|
2631
|
+
var referencedFindRecord = function(store, type, data, key, one) {
|
2632
|
+
return data ? get(data, key) : one ? null : [];
|
2633
|
+
};
|
2634
|
+
|
2635
|
+
var hasAssociation = function(type, options, one) {
|
2636
|
+
var embedded = options && options.embedded,
|
2637
|
+
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
2638
|
+
|
2639
|
+
var meta = { type: type, isAssociation: true, options: options || {} };
|
2640
|
+
if (one) {
|
2641
|
+
meta.kind = 'belongsTo';
|
2642
|
+
} else {
|
2643
|
+
meta.kind = 'hasMany';
|
2644
|
+
}
|
2645
|
+
|
2646
|
+
return Ember.computed(function(key, value) {
|
2647
|
+
var data = get(this, 'data'), ids, id, association,
|
2648
|
+
store = get(this, 'store');
|
2649
|
+
|
2650
|
+
if (typeof type === 'string') {
|
2651
|
+
type = getPath(this, type, false) || getPath(window, type);
|
2652
|
+
}
|
2653
|
+
|
2654
|
+
key = (options && options.key) ? options.key : key;
|
2655
|
+
if (one) {
|
2656
|
+
if (arguments.length === 2) {
|
2657
|
+
data.setAssociation(key, get(value, 'clientId'));
|
2658
|
+
// put the client id in `key` in the data hash
|
2659
|
+
return value;
|
2660
|
+
} else {
|
2661
|
+
id = findRecord(store, type, data, key, true);
|
2662
|
+
association = id ? store.find(type, id) : null;
|
2663
|
+
|
2664
|
+
// if we have an association, store its client id in `key` in the data hash
|
2665
|
+
}
|
2666
|
+
} else {
|
2667
|
+
ids = findRecord(store, type, data, key);
|
2668
|
+
association = store.findMany(type, ids);
|
2669
|
+
set(association, 'parentRecord', this);
|
2670
|
+
}
|
2671
|
+
|
2672
|
+
return association;
|
2673
|
+
}).property('data').cacheable().meta(meta);
|
2674
|
+
};
|
2675
|
+
|
2676
|
+
DS.hasMany = function(type, options) {
|
2677
|
+
ember_assert("The type passed to DS.hasMany must be defined", !!type);
|
2678
|
+
return hasAssociation(type, options);
|
2679
|
+
};
|
2680
|
+
|
2681
|
+
DS.hasOne = function(type, options) {
|
2682
|
+
ember_assert("The type passed to DS.belongsTo must be defined", !!type);
|
2683
|
+
return hasAssociation(type, options, true);
|
2684
|
+
};
|
2685
|
+
|
2686
|
+
DS.belongsTo = DS.hasOne;
|
2687
|
+
|
2688
|
+
})({});
|
2689
|
+
|
2690
|
+
|
2691
|
+
(function(exports) {
|
1749
2692
|
})({});
|
1750
2693
|
|
1751
2694
|
|