rasputin 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +4 -4
- data/README.md +33 -13
- data/lib/rasputin.rb +7 -1
- data/lib/rasputin/require_preprocessor.rb +188 -0
- data/lib/rasputin/version.rb +1 -1
- data/vendor/assets/javascripts/ember-data.js +366 -218
- metadata +11 -14
- data/vendor/assets/javascripts/TransformJS.js +0 -432
- data/vendor/assets/javascripts/ember-i18n.js +0 -21
- data/vendor/assets/javascripts/ember-routing.js +0 -568
- data/vendor/assets/javascripts/ember-touch.js +0 -1303
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rasputin (0.
|
4
|
+
rasputin (0.12.2)
|
5
5
|
actionpack (~> 3.1.0)
|
6
6
|
jquery-rails (~> 1.0)
|
7
7
|
railties (~> 3.1.0)
|
@@ -34,9 +34,9 @@ GEM
|
|
34
34
|
jquery-rails (1.0.19)
|
35
35
|
railties (~> 3.0)
|
36
36
|
thor (~> 0.14)
|
37
|
-
json (1.6.
|
37
|
+
json (1.6.4)
|
38
38
|
multi_json (1.0.4)
|
39
|
-
rack (1.3.
|
39
|
+
rack (1.3.6)
|
40
40
|
rack-cache (1.1)
|
41
41
|
rack (>= 0.4)
|
42
42
|
rack-mount (0.8.3)
|
@@ -53,7 +53,7 @@ GEM
|
|
53
53
|
rdoc (~> 3.4)
|
54
54
|
thor (~> 0.14.6)
|
55
55
|
rake (0.9.2.2)
|
56
|
-
rdoc (3.
|
56
|
+
rdoc (3.12)
|
57
57
|
json (~> 1.4)
|
58
58
|
sprockets (2.0.3)
|
59
59
|
hike (~> 1.2)
|
data/README.md
CHANGED
@@ -3,20 +3,13 @@ Rasputin
|
|
3
3
|
|
4
4
|
This is a gem for integration of Ember.js with Rails 3.1 assets pipeline.
|
5
5
|
|
6
|
-
It provide direct requires for
|
6
|
+
It provide direct requires for following ember packages :
|
7
7
|
|
8
8
|
* ember
|
9
|
-
* ember-datetime
|
10
9
|
* ember-data
|
11
|
-
* ember-touch
|
12
|
-
* ember-routing
|
13
|
-
|
14
|
-
And it also provides one unnoficial package :
|
15
|
-
|
16
|
-
* ember-i18n (integration with i18n-js gem)
|
17
10
|
|
18
11
|
Rasputin also provide sprockets engine for handlebars templates. Any template in your
|
19
|
-
javascript assets folder with extention handlebars will be availabel in
|
12
|
+
javascript assets folder with extention `handlebars` or `hbs` will be availabel in ember.
|
20
13
|
|
21
14
|
Examples :
|
22
15
|
|
@@ -33,6 +26,7 @@ The new default is '/'
|
|
33
26
|
Precompilation :
|
34
27
|
|
35
28
|
Starting with 0.9.0 release, Rasputin will precompile your handlebars templates.
|
29
|
+
Starting with 0.12.1 release, default behavior is to precompile templates only in production environment.
|
36
30
|
If you do not want this behavior you can tourn it off in your rails configuration block :
|
37
31
|
|
38
32
|
config.rasputin.precompile_handlebars = false
|
@@ -48,6 +42,20 @@ It will be translated as :
|
|
48
42
|
{{view Ember.Button}}OK{{/view}}
|
49
43
|
</script>
|
50
44
|
|
45
|
+
Preprocessor :
|
46
|
+
|
47
|
+
If you chouse to use "javascript native require" your application.js file will look like this :
|
48
|
+
|
49
|
+
require('jquery');
|
50
|
+
require('ember');
|
51
|
+
require('ember-data');
|
52
|
+
require('app/**/*');
|
53
|
+
|
54
|
+
Ther is two new settings :
|
55
|
+
|
56
|
+
config.rasputin.use_javascript_require = true
|
57
|
+
config.rasputin.strip_javascript_require = true
|
58
|
+
|
51
59
|
Install
|
52
60
|
-------
|
53
61
|
|
@@ -62,11 +70,7 @@ In your javascript asset manifest (app/assets/javascripts/application.js) add th
|
|
62
70
|
|
63
71
|
And any of the following you want to include:
|
64
72
|
|
65
|
-
//= require ember-datetime
|
66
73
|
//= require ember-data
|
67
|
-
//= require ember-touch
|
68
|
-
//= require ember-routing
|
69
|
-
//= require ember-i18n
|
70
74
|
|
71
75
|
In your stylesheet asset manifest (app/assets/stylesheets/application.css) add the following:
|
72
76
|
|
@@ -77,6 +81,22 @@ In your stylesheet asset manifest (app/assets/stylesheets/application.css) add t
|
|
77
81
|
ChangeLog
|
78
82
|
----------
|
79
83
|
|
84
|
+
0.13.1
|
85
|
+
|
86
|
+
* fix to ensure rasputin is initialized in all groups (thanks @chrisconley)
|
87
|
+
* update ember-data
|
88
|
+
|
89
|
+
0.13.0
|
90
|
+
|
91
|
+
* new preprocessor for "javascript native require" (WIP)
|
92
|
+
* remove legacy packages
|
93
|
+
|
94
|
+
0.12.1
|
95
|
+
|
96
|
+
* new precompiler (borrowed from @keithpitt)
|
97
|
+
* default behavior is to precompil only in production environment
|
98
|
+
* haml filter (thanks @ootoovak)
|
99
|
+
|
80
100
|
0.12.0
|
81
101
|
|
82
102
|
* replace ember-datastore with ember-data
|
data/lib/rasputin.rb
CHANGED
@@ -8,13 +8,19 @@ require "rasputin/handlebars/template"
|
|
8
8
|
require "rasputin/slim" if defined? Slim
|
9
9
|
require "rasputin/haml" if defined? Haml
|
10
10
|
|
11
|
+
require "rasputin/require_preprocessor"
|
12
|
+
|
11
13
|
module Rasputin
|
12
14
|
class Engine < ::Rails::Engine
|
13
15
|
config.rasputin = ActiveSupport::OrderedOptions.new
|
14
16
|
config.rasputin.precompile_handlebars = Rails.env.production?
|
15
17
|
config.rasputin.template_name_separator = '/'
|
16
18
|
|
17
|
-
|
19
|
+
config.rasputin.use_javascript_require = true
|
20
|
+
config.rasputin.strip_javascript_require = true
|
21
|
+
|
22
|
+
initializer :setup_rasputin, :group => :all do |app|
|
23
|
+
app.assets.register_preprocessor 'application/javascript', Rasputin::RequirePreprocessor
|
18
24
|
app.assets.register_engine '.handlebars', Rasputin::HandlebarsTemplate
|
19
25
|
app.assets.register_engine '.hbs', Rasputin::HandlebarsTemplate
|
20
26
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
|
2
|
+
module Rasputin
|
3
|
+
class RequirePreprocessor < Tilt::Template
|
4
|
+
|
5
|
+
REQUIRE_PATTERN = /require\(\s*['"]([^\)]+)['"]\s*\)\s*;?\s*/
|
6
|
+
HEADER_PATTERN = /
|
7
|
+
\A (
|
8
|
+
(?m:\s*) (
|
9
|
+
(\/\* (?m:.*?) \*\/) |
|
10
|
+
(\#\#\# (?m:.*?) \#\#\#) |
|
11
|
+
(\/\/ .* \n?)+ |
|
12
|
+
(\# .* \n?)+ |
|
13
|
+
(#{REQUIRE_PATTERN} \n?)+
|
14
|
+
)
|
15
|
+
)+
|
16
|
+
/x
|
17
|
+
DIRECTIVE_PATTERN = /^\s*#{REQUIRE_PATTERN}$/
|
18
|
+
TREE_PATTERN = /\*\*\/\*$/
|
19
|
+
DIRECTORY_PATTERN = /\*$/
|
20
|
+
|
21
|
+
attr_reader :pathname
|
22
|
+
attr_reader :header, :body
|
23
|
+
|
24
|
+
def prepare
|
25
|
+
@pathname = Pathname.new(file)
|
26
|
+
|
27
|
+
@use_javascript_require = Rails.configuration.rasputin.use_javascript_require
|
28
|
+
@strip_javascript_require = Rails.configuration.rasputin.strip_javascript_require
|
29
|
+
|
30
|
+
if @use_javascript_require
|
31
|
+
@header = data[HEADER_PATTERN, 0] || ""
|
32
|
+
@body = $' || data
|
33
|
+
# Ensure body ends in a new line
|
34
|
+
@body += "\n" if @body != "" && @body !~ /\n\Z/m
|
35
|
+
else
|
36
|
+
@body = data
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def evaluate(context, locals, &block)
|
41
|
+
if @use_javascript_require
|
42
|
+
@context = context
|
43
|
+
process_directives
|
44
|
+
|
45
|
+
processed_source
|
46
|
+
else
|
47
|
+
body
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def processed_header
|
52
|
+
if @use_javascript_require && @strip_javascript_require
|
53
|
+
lineno = 0
|
54
|
+
@processed_header ||= header.lines.map { |line|
|
55
|
+
lineno += 1
|
56
|
+
# Replace directive line with a clean break
|
57
|
+
directives.assoc(lineno) ? "\n" : line
|
58
|
+
}.join.chomp
|
59
|
+
else
|
60
|
+
@processed_header ||= header.chomp
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def processed_source
|
65
|
+
@processed_source ||= processed_header + "\n" + body
|
66
|
+
end
|
67
|
+
|
68
|
+
def directives
|
69
|
+
@directives ||= header.lines.each_with_index.map { |line, index|
|
70
|
+
if line =~ DIRECTIVE_PATTERN
|
71
|
+
name, path = detect_directive($1)
|
72
|
+
if respond_to?("process_#{name}_directive")
|
73
|
+
[index + 1, name, path]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
}.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
attr_reader :context
|
82
|
+
|
83
|
+
def process_directives
|
84
|
+
directives.each do |line_number, name, path|
|
85
|
+
context.__LINE__ = line_number
|
86
|
+
send("process_#{name}_directive", path)
|
87
|
+
context.__LINE__ = nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def detect_directive(path)
|
92
|
+
if path =~ TREE_PATTERN
|
93
|
+
return :require_tree, absolute_path_to_directory(path, TREE_PATTERN)
|
94
|
+
elsif path =~ DIRECTORY_PATTERN
|
95
|
+
return :require_directory, absolute_path_to_directory(path, DIRECTORY_PATTERN)
|
96
|
+
else
|
97
|
+
return :require_file, path
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
###
|
102
|
+
# Directives implementation
|
103
|
+
###
|
104
|
+
|
105
|
+
# `path`
|
106
|
+
def process_require_file_directive(path)
|
107
|
+
if relative?(path)
|
108
|
+
# The path must be absolute.
|
109
|
+
raise ArgumentError, "require argument must be absolute path"
|
110
|
+
else
|
111
|
+
context.require_asset(path)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# `path/*`
|
116
|
+
def process_require_directory_directive(path)
|
117
|
+
if relative?(path)
|
118
|
+
# The path must be absolute.
|
119
|
+
raise ArgumentError, "require_directory argument must be absolute path"
|
120
|
+
else
|
121
|
+
root = Pathname.new(path)
|
122
|
+
|
123
|
+
unless (stats = stat(root)) && stats.directory?
|
124
|
+
raise ArgumentError, "require_directory argument must be a directory"
|
125
|
+
end
|
126
|
+
|
127
|
+
context.depend_on(root)
|
128
|
+
|
129
|
+
entries(root).each do |pathname|
|
130
|
+
pathname = root.join(pathname)
|
131
|
+
if pathname.to_s == self.file
|
132
|
+
next
|
133
|
+
elsif context.asset_requirable?(pathname)
|
134
|
+
context.require_asset(pathname)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# `path/**/*`
|
141
|
+
def process_require_tree_directive(path)
|
142
|
+
if relative?(path)
|
143
|
+
# The path must be absolute.
|
144
|
+
raise ArgumentError, "require_tree argument must be absolute path"
|
145
|
+
else
|
146
|
+
root = Pathname.new(path)
|
147
|
+
|
148
|
+
unless (stats = stat(root)) && stats.directory?
|
149
|
+
raise ArgumentError, "require_tree argument must be a directory"
|
150
|
+
end
|
151
|
+
|
152
|
+
context.depend_on(root)
|
153
|
+
|
154
|
+
each_entry(root) do |pathname|
|
155
|
+
if pathname.to_s == self.file
|
156
|
+
next
|
157
|
+
elsif stat(pathname).directory?
|
158
|
+
context.depend_on(pathname)
|
159
|
+
elsif context.asset_requirable?(pathname)
|
160
|
+
context.require_asset(pathname)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def absolute_path_to_directory(path, pattern)
|
169
|
+
File.join(context.root_path, path.gsub(pattern, ''))
|
170
|
+
end
|
171
|
+
|
172
|
+
def relative?(path)
|
173
|
+
path =~ /^\.($|\.?\/)/
|
174
|
+
end
|
175
|
+
|
176
|
+
def stat(path)
|
177
|
+
context.environment.stat(path)
|
178
|
+
end
|
179
|
+
|
180
|
+
def entries(path)
|
181
|
+
context.environment.entries(path)
|
182
|
+
end
|
183
|
+
|
184
|
+
def each_entry(root, &block)
|
185
|
+
context.environment.each_entry(root, &block)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/lib/rasputin/version.rb
CHANGED
@@ -9,33 +9,33 @@ window.DS = SC.Namespace.create();
|
|
9
9
|
DS.Adapter = SC.Object.extend({
|
10
10
|
commit: function(store, commitDetails) {
|
11
11
|
commitDetails.updated.eachType(function(type, array) {
|
12
|
-
this.
|
12
|
+
this.updateRecords(store, type, array.slice());
|
13
13
|
}, this);
|
14
14
|
|
15
15
|
commitDetails.created.eachType(function(type, array) {
|
16
|
-
this.
|
16
|
+
this.createRecords(store, type, array.slice());
|
17
17
|
}, this);
|
18
18
|
|
19
19
|
commitDetails.deleted.eachType(function(type, array) {
|
20
|
-
this.
|
20
|
+
this.deleteRecords(store, type, array.slice());
|
21
21
|
}, this);
|
22
22
|
},
|
23
23
|
|
24
|
-
|
24
|
+
createRecords: function(store, type, models) {
|
25
25
|
models.forEach(function(model) {
|
26
|
-
this.
|
26
|
+
this.createRecord(store, type, model);
|
27
27
|
}, this);
|
28
28
|
},
|
29
29
|
|
30
|
-
|
30
|
+
updateRecords: function(store, type, models) {
|
31
31
|
models.forEach(function(model) {
|
32
|
-
this.
|
32
|
+
this.updateRecord(store, type, model);
|
33
33
|
}, this);
|
34
34
|
},
|
35
35
|
|
36
|
-
|
36
|
+
deleteRecords: function(store, type, models) {
|
37
37
|
models.forEach(function(model) {
|
38
|
-
this.
|
38
|
+
this.deleteRecord(store, type, model);
|
39
39
|
}, this);
|
40
40
|
},
|
41
41
|
|
@@ -84,10 +84,10 @@ DS.ModelArray = SC.ArrayProxy.extend({
|
|
84
84
|
},
|
85
85
|
|
86
86
|
arrayDidChange: function(array, index, removed, added) {
|
87
|
-
this._super(array, index, removed, added);
|
88
|
-
|
89
87
|
var modelCache = get(this, 'modelCache');
|
90
88
|
modelCache.replace(index, 0, Array(added));
|
89
|
+
|
90
|
+
this._super(array, index, removed, added);
|
91
91
|
},
|
92
92
|
|
93
93
|
arrayWillChange: function(array, index, removed, added) {
|
@@ -145,6 +145,196 @@ DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
|
145
145
|
})({});
|
146
146
|
|
147
147
|
|
148
|
+
(function(exports) {
|
149
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
150
|
+
|
151
|
+
var OrderedSet = SC.Object.extend({
|
152
|
+
init: function() {
|
153
|
+
this.clear();
|
154
|
+
},
|
155
|
+
|
156
|
+
clear: function() {
|
157
|
+
this.set('presenceSet', {});
|
158
|
+
this.set('list', SC.NativeArray.apply([]));
|
159
|
+
},
|
160
|
+
|
161
|
+
add: function(obj) {
|
162
|
+
var guid = SC.guidFor(obj),
|
163
|
+
presenceSet = get(this, 'presenceSet'),
|
164
|
+
list = get(this, 'list');
|
165
|
+
|
166
|
+
if (guid in presenceSet) { return; }
|
167
|
+
|
168
|
+
presenceSet[guid] = true;
|
169
|
+
list.pushObject(obj);
|
170
|
+
},
|
171
|
+
|
172
|
+
remove: function(obj) {
|
173
|
+
var guid = SC.guidFor(obj),
|
174
|
+
presenceSet = get(this, 'presenceSet'),
|
175
|
+
list = get(this, 'list');
|
176
|
+
|
177
|
+
delete presenceSet[guid];
|
178
|
+
list.removeObject(obj);
|
179
|
+
},
|
180
|
+
|
181
|
+
isEmpty: function() {
|
182
|
+
return getPath(this, 'list.length') === 0;
|
183
|
+
},
|
184
|
+
|
185
|
+
forEach: function(fn, self) {
|
186
|
+
get(this, 'list').forEach(function(item) {
|
187
|
+
fn.call(self, item);
|
188
|
+
});
|
189
|
+
},
|
190
|
+
|
191
|
+
toArray: function() {
|
192
|
+
return get(this, 'list').slice();
|
193
|
+
}
|
194
|
+
});
|
195
|
+
|
196
|
+
/**
|
197
|
+
A Hash stores values indexed by keys. Unlike JavaScript's
|
198
|
+
default Objects, the keys of a Hash can be any JavaScript
|
199
|
+
object.
|
200
|
+
|
201
|
+
Internally, a Hash has two data structures:
|
202
|
+
|
203
|
+
`keys`: an OrderedSet of all of the existing keys
|
204
|
+
`values`: a JavaScript Object indexed by the
|
205
|
+
Ember.guidFor(key)
|
206
|
+
|
207
|
+
When a key/value pair is added for the first time, we
|
208
|
+
add the key to the `keys` OrderedSet, and create or
|
209
|
+
replace an entry in `values`. When an entry is deleted,
|
210
|
+
we delete its entry in `keys` and `values`.
|
211
|
+
*/
|
212
|
+
|
213
|
+
var Hash = SC.Object.extend({
|
214
|
+
init: function() {
|
215
|
+
set(this, 'keys', OrderedSet.create());
|
216
|
+
set(this, 'values', {});
|
217
|
+
},
|
218
|
+
|
219
|
+
add: function(key, value) {
|
220
|
+
var keys = get(this, 'keys'), values = get(this, 'values');
|
221
|
+
var guid = Ember.guidFor(key);
|
222
|
+
|
223
|
+
keys.add(key);
|
224
|
+
values[guid] = value;
|
225
|
+
|
226
|
+
return value;
|
227
|
+
},
|
228
|
+
|
229
|
+
remove: function(key) {
|
230
|
+
var keys = get(this, 'keys'), values = get(this, 'values');
|
231
|
+
var guid = Ember.guidFor(key), value;
|
232
|
+
|
233
|
+
keys.remove(key);
|
234
|
+
|
235
|
+
value = values[guid];
|
236
|
+
delete values[guid];
|
237
|
+
|
238
|
+
return value;
|
239
|
+
},
|
240
|
+
|
241
|
+
fetch: function(key) {
|
242
|
+
var values = get(this, 'values');
|
243
|
+
var guid = Ember.guidFor(key);
|
244
|
+
|
245
|
+
return values[guid];
|
246
|
+
},
|
247
|
+
|
248
|
+
forEach: function(fn, binding) {
|
249
|
+
var keys = get(this, 'keys'), values = get(this, 'values');
|
250
|
+
|
251
|
+
keys.forEach(function(key) {
|
252
|
+
var guid = Ember.guidFor(key);
|
253
|
+
fn.call(binding, key, values[guid]);
|
254
|
+
});
|
255
|
+
}
|
256
|
+
});
|
257
|
+
|
258
|
+
DS.Transaction = Ember.Object.extend({
|
259
|
+
init: function() {
|
260
|
+
set(this, 'dirty', {
|
261
|
+
created: Hash.create(),
|
262
|
+
updated: Hash.create(),
|
263
|
+
deleted: Hash.create()
|
264
|
+
});
|
265
|
+
},
|
266
|
+
|
267
|
+
createRecord: function(type, hash) {
|
268
|
+
var store = get(this, 'store');
|
269
|
+
|
270
|
+
return store.createRecord(type, hash, this);
|
271
|
+
},
|
272
|
+
|
273
|
+
add: function(model) {
|
274
|
+
var modelTransaction = get(model, 'transaction');
|
275
|
+
ember_assert("Models cannot belong to more than one transaction at a time.", !modelTransaction);
|
276
|
+
|
277
|
+
set(model, 'transaction', this);
|
278
|
+
},
|
279
|
+
|
280
|
+
modelBecameDirty: function(kind, model) {
|
281
|
+
var dirty = get(get(this, 'dirty'), kind),
|
282
|
+
type = model.constructor;
|
283
|
+
|
284
|
+
var models = dirty.fetch(type);
|
285
|
+
|
286
|
+
models = models || dirty.add(type, OrderedSet.create());
|
287
|
+
models.add(model);
|
288
|
+
},
|
289
|
+
|
290
|
+
modelBecameClean: function(kind, model) {
|
291
|
+
var dirty = get(get(this, 'dirty'), kind),
|
292
|
+
type = model.constructor;
|
293
|
+
|
294
|
+
var models = dirty.fetch(type);
|
295
|
+
models.remove(model);
|
296
|
+
|
297
|
+
set(model, 'transaction', null);
|
298
|
+
},
|
299
|
+
|
300
|
+
commit: function() {
|
301
|
+
var dirtyMap = get(this, 'dirty');
|
302
|
+
|
303
|
+
var iterate = function(kind, fn, binding) {
|
304
|
+
var dirty = get(dirtyMap, kind);
|
305
|
+
|
306
|
+
dirty.forEach(function(type, models) {
|
307
|
+
if (models.isEmpty()) { return; }
|
308
|
+
|
309
|
+
models.forEach(function(model) { model.willCommit(); });
|
310
|
+
fn.call(binding, type, models.toArray());
|
311
|
+
});
|
312
|
+
};
|
313
|
+
|
314
|
+
var commitDetails = {
|
315
|
+
updated: {
|
316
|
+
eachType: function(fn, binding) { iterate('updated', fn, binding); }
|
317
|
+
},
|
318
|
+
|
319
|
+
created: {
|
320
|
+
eachType: function(fn, binding) { iterate('created', fn, binding); }
|
321
|
+
},
|
322
|
+
|
323
|
+
deleted: {
|
324
|
+
eachType: function(fn, binding) { iterate('deleted', fn, binding); }
|
325
|
+
}
|
326
|
+
};
|
327
|
+
|
328
|
+
var store = get(this, 'store');
|
329
|
+
var adapter = get(store, '_adapter');
|
330
|
+
if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
|
331
|
+
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
|
332
|
+
}
|
333
|
+
});
|
334
|
+
|
335
|
+
})({});
|
336
|
+
|
337
|
+
|
148
338
|
(function(exports) {
|
149
339
|
var get = SC.get, set = SC.set, getPath = SC.getPath, fmt = SC.String.fmt;
|
150
340
|
|
@@ -245,13 +435,15 @@ DS.Store = SC.Object.extend({
|
|
245
435
|
set(this, 'models', []);
|
246
436
|
set(this, 'modelArrays', []);
|
247
437
|
set(this, 'modelArraysByClientId', {});
|
248
|
-
set(this, '
|
249
|
-
set(this, 'createdTypes', OrderedSet.create());
|
250
|
-
set(this, 'deletedTypes', OrderedSet.create());
|
438
|
+
set(this, 'defaultTransaction', DS.Transaction.create({ store: this }));
|
251
439
|
|
252
440
|
return this._super();
|
253
441
|
},
|
254
442
|
|
443
|
+
transaction: function() {
|
444
|
+
return DS.Transaction.create({ store: this });
|
445
|
+
},
|
446
|
+
|
255
447
|
modelArraysForClientId: function(clientId) {
|
256
448
|
var modelArrays = get(this, 'modelArraysByClientId');
|
257
449
|
var ret = modelArrays[clientId];
|
@@ -287,23 +479,29 @@ DS.Store = SC.Object.extend({
|
|
287
479
|
// . CREATE NEW MODEL .
|
288
480
|
// ....................
|
289
481
|
|
290
|
-
|
482
|
+
createRecord: function(type, hash, transaction) {
|
291
483
|
hash = hash || {};
|
292
484
|
|
293
485
|
var id = hash[getPath(type, 'proto.primaryKey')] || null;
|
294
486
|
|
295
|
-
var model = type.create({
|
487
|
+
var model = type.create({
|
488
|
+
data: hash || {},
|
489
|
+
store: this,
|
490
|
+
transaction: transaction
|
491
|
+
});
|
492
|
+
|
296
493
|
model.adapterDidCreate();
|
297
494
|
|
298
495
|
var data = this.clientIdToHashMap(type);
|
299
496
|
var models = get(this, 'models');
|
300
497
|
|
301
498
|
var clientId = this.pushHash(hash, id, type);
|
302
|
-
this.updateModelArrays(type, clientId, hash);
|
303
499
|
|
304
500
|
set(model, 'clientId', clientId);
|
305
501
|
|
306
|
-
|
502
|
+
models[clientId] = model;
|
503
|
+
|
504
|
+
this.updateModelArrays(type, clientId, hash);
|
307
505
|
|
308
506
|
return model;
|
309
507
|
},
|
@@ -312,8 +510,8 @@ DS.Store = SC.Object.extend({
|
|
312
510
|
// . DELETE MODEL .
|
313
511
|
// ................
|
314
512
|
|
315
|
-
|
316
|
-
model.
|
513
|
+
deleteRecord: function(model) {
|
514
|
+
model.deleteRecord();
|
317
515
|
},
|
318
516
|
|
319
517
|
// ...............
|
@@ -466,126 +664,27 @@ DS.Store = SC.Object.extend({
|
|
466
664
|
this.updateModelArrays(type, clientId, hash);
|
467
665
|
},
|
468
666
|
|
469
|
-
|
470
|
-
// Internally, the store keeps two data structures representing
|
471
|
-
// the dirty models.
|
472
|
-
//
|
473
|
-
// It holds an OrderedSet of all of the dirty types and a Hash
|
474
|
-
// keyed off of the guid of each type.
|
475
|
-
//
|
476
|
-
// Assuming that Ember.guidFor(Person) is 'sc1', guidFor(Place)
|
477
|
-
// is 'sc2', and guidFor(Thing) is 'sc3', the structure will look
|
478
|
-
// like:
|
479
|
-
//
|
480
|
-
// store: {
|
481
|
-
// updatedTypes: [ Person, Place, Thing ],
|
482
|
-
// updatedModels: {
|
483
|
-
// sc1: [ person1, person2, person3 ],
|
484
|
-
// sc2: [ place1 ],
|
485
|
-
// sc3: [ thing1, thing2 ]
|
486
|
-
// }
|
487
|
-
// }
|
488
|
-
//
|
489
|
-
// Adapters receive an iterator that they can use to retrieve the
|
490
|
-
// type and array at the same time:
|
491
|
-
//
|
492
|
-
// adapter: {
|
493
|
-
// commit: function(store, commitDetails) {
|
494
|
-
// commitDetails.updated.eachType(function(type, array) {
|
495
|
-
// // this callback will be invoked three times:
|
496
|
-
// //
|
497
|
-
// // 1. Person, [ person1, person2, person3 ]
|
498
|
-
// // 2. Place, [ place1 ]
|
499
|
-
// // 3. Thing, [ thing1, thing2 ]
|
500
|
-
// }
|
501
|
-
// }
|
502
|
-
// }
|
503
|
-
//
|
504
|
-
// This encapsulates the internal structure and presents it to the
|
505
|
-
// adapter as if it was a regular Hash with types as keys and dirty
|
506
|
-
// models as values.
|
507
|
-
//
|
508
|
-
// Note that there is a pair of *Types and *Models for each of
|
509
|
-
// `created`, `updated` and `deleted`. These correspond with the
|
510
|
-
// commitDetails passed into the adapter's commit method.
|
511
|
-
|
512
|
-
modelBecameDirty: function(kind, model) {
|
513
|
-
var dirtyTypes = get(this, kind + 'Types'), type = model.constructor;
|
514
|
-
dirtyTypes.add(type);
|
515
|
-
|
516
|
-
var dirtyModels = this.typeMap(type)[kind + 'Models'];
|
517
|
-
dirtyModels.add(model);
|
518
|
-
},
|
519
|
-
|
520
|
-
modelBecameClean: function(kind, model) {
|
521
|
-
var dirtyTypes = get(this, kind + 'Types'), type = model.constructor;
|
522
|
-
|
523
|
-
var dirtyModels = this.typeMap(type)[kind + 'Models'];
|
524
|
-
dirtyModels.remove(model);
|
525
|
-
|
526
|
-
if (dirtyModels.isEmpty()) {
|
527
|
-
dirtyTypes.remove(type);
|
528
|
-
}
|
529
|
-
},
|
530
|
-
|
531
|
-
eachDirtyType: function(kind, fn, self) {
|
532
|
-
var types = get(this, kind + 'Types'), dirtyModels;
|
533
|
-
|
534
|
-
types.forEach(function(type) {
|
535
|
-
dirtyModels = this.typeMap(type)[kind + 'Models'];
|
536
|
-
fn.call(self, type, get(dirtyModels, 'list'));
|
537
|
-
}, this);
|
538
|
-
},
|
539
|
-
|
540
667
|
// ..............
|
541
668
|
// . PERSISTING .
|
542
669
|
// ..............
|
543
670
|
|
544
671
|
commit: function() {
|
545
|
-
|
546
|
-
|
547
|
-
var iterate = function(kind, fn, binding) {
|
548
|
-
self.eachDirtyType(kind, function(type, models) {
|
549
|
-
models.forEach(function(model) {
|
550
|
-
model.willCommit();
|
551
|
-
});
|
552
|
-
|
553
|
-
fn.call(binding, type, models);
|
554
|
-
});
|
555
|
-
};
|
556
|
-
|
557
|
-
var commitDetails = {
|
558
|
-
updated: {
|
559
|
-
eachType: function(fn, binding) { iterate('updated', fn, binding); }
|
560
|
-
},
|
561
|
-
|
562
|
-
created: {
|
563
|
-
eachType: function(fn, binding) { iterate('created', fn, binding); }
|
564
|
-
},
|
565
|
-
|
566
|
-
deleted: {
|
567
|
-
eachType: function(fn, binding) { iterate('deleted', fn, binding); }
|
568
|
-
}
|
569
|
-
};
|
570
|
-
|
571
|
-
var adapter = get(this, '_adapter');
|
572
|
-
if (adapter && adapter.commit) { adapter.commit(this, commitDetails); }
|
573
|
-
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
|
672
|
+
get(this, 'defaultTransaction').commit();
|
574
673
|
},
|
575
674
|
|
576
|
-
|
675
|
+
didUpdateRecords: function(array, hashes) {
|
577
676
|
if (arguments.length === 2) {
|
578
677
|
array.forEach(function(model, idx) {
|
579
|
-
this.
|
678
|
+
this.didUpdateRecord(model, hashes[idx]);
|
580
679
|
}, this);
|
581
680
|
} else {
|
582
681
|
array.forEach(function(model) {
|
583
|
-
this.
|
682
|
+
this.didUpdateRecord(model);
|
584
683
|
}, this);
|
585
684
|
}
|
586
685
|
},
|
587
686
|
|
588
|
-
|
687
|
+
didUpdateRecord: function(model, hash) {
|
589
688
|
if (arguments.length === 2) {
|
590
689
|
var clientId = get(model, 'clientId');
|
591
690
|
var data = this.clientIdToHashMap(model.constructor);
|
@@ -597,17 +696,17 @@ DS.Store = SC.Object.extend({
|
|
597
696
|
model.adapterDidUpdate();
|
598
697
|
},
|
599
698
|
|
600
|
-
|
699
|
+
didDeleteRecords: function(array) {
|
601
700
|
array.forEach(function(model) {
|
602
701
|
model.adapterDidDelete();
|
603
702
|
});
|
604
703
|
},
|
605
704
|
|
606
|
-
|
705
|
+
didDeleteRecord: function(model) {
|
607
706
|
model.adapterDidDelete();
|
608
707
|
},
|
609
708
|
|
610
|
-
|
709
|
+
didCreateRecords: function(type, array, hashes) {
|
611
710
|
var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
|
612
711
|
|
613
712
|
var idToClientIdMap = this.idToClientIdMap(type);
|
@@ -629,7 +728,7 @@ DS.Store = SC.Object.extend({
|
|
629
728
|
}
|
630
729
|
},
|
631
730
|
|
632
|
-
|
731
|
+
didCreateRecord: function(model, hash) {
|
633
732
|
var type = model.constructor;
|
634
733
|
|
635
734
|
var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
|
@@ -650,6 +749,10 @@ DS.Store = SC.Object.extend({
|
|
650
749
|
model.adapterDidUpdate();
|
651
750
|
},
|
652
751
|
|
752
|
+
recordWasInvalid: function(record, errors) {
|
753
|
+
record.wasInvalid(errors);
|
754
|
+
},
|
755
|
+
|
653
756
|
// ................
|
654
757
|
// . MODEL ARRAYS .
|
655
758
|
// ................
|
@@ -753,10 +856,7 @@ DS.Store = SC.Object.extend({
|
|
753
856
|
idToCid: {},
|
754
857
|
idList: [],
|
755
858
|
cidList: [],
|
756
|
-
cidToHash: {}
|
757
|
-
updatedModels: OrderedSet.create(),
|
758
|
-
createdModels: OrderedSet.create(),
|
759
|
-
deletedModels: OrderedSet.create()
|
859
|
+
cidToHash: {}
|
760
860
|
});
|
761
861
|
}
|
762
862
|
},
|
@@ -931,7 +1031,8 @@ DS.State = SC.State.extend({
|
|
931
1031
|
isSaving: stateProperty,
|
932
1032
|
isDeleted: stateProperty,
|
933
1033
|
isError: stateProperty,
|
934
|
-
isNew: stateProperty
|
1034
|
+
isNew: stateProperty,
|
1035
|
+
isValid: stateProperty
|
935
1036
|
});
|
936
1037
|
|
937
1038
|
var cantLoadData = function() {
|
@@ -939,6 +1040,99 @@ var cantLoadData = function() {
|
|
939
1040
|
throw "You cannot load data into the store when its associated model is in its current state";
|
940
1041
|
};
|
941
1042
|
|
1043
|
+
var isEmptyObject = function(obj) {
|
1044
|
+
for (var prop in obj) {
|
1045
|
+
if (!obj.hasOwnProperty(prop)) { continue; }
|
1046
|
+
return false;
|
1047
|
+
}
|
1048
|
+
|
1049
|
+
return true;
|
1050
|
+
};
|
1051
|
+
|
1052
|
+
var setProperty = function(manager, context) {
|
1053
|
+
var key = context.key, value = context.value;
|
1054
|
+
|
1055
|
+
var model = get(manager, 'model'), type = model.constructor;
|
1056
|
+
var store = get(model, 'store');
|
1057
|
+
var data = get(model, 'data');
|
1058
|
+
|
1059
|
+
data[key] = value;
|
1060
|
+
|
1061
|
+
if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
|
1062
|
+
};
|
1063
|
+
|
1064
|
+
// several states share extremely common functionality, so we are factoring
|
1065
|
+
// them out into a common class.
|
1066
|
+
var DirtyState = DS.State.extend({
|
1067
|
+
// these states are virtually identical except that
|
1068
|
+
// they (thrice) use their states name explicitly.
|
1069
|
+
//
|
1070
|
+
// child classes implement stateName.
|
1071
|
+
stateName: null,
|
1072
|
+
isDirty: true,
|
1073
|
+
willLoadData: cantLoadData,
|
1074
|
+
|
1075
|
+
enter: function(manager) {
|
1076
|
+
var stateName = get(this, 'stateName'),
|
1077
|
+
model = get(manager, 'model');
|
1078
|
+
|
1079
|
+
model.withTransaction(function (t) {
|
1080
|
+
t.modelBecameDirty(stateName, model);
|
1081
|
+
});
|
1082
|
+
},
|
1083
|
+
|
1084
|
+
exit: function(manager) {
|
1085
|
+
var stateName = get(this, 'stateName'),
|
1086
|
+
model = get(manager, 'model');
|
1087
|
+
|
1088
|
+
this.notifyModel(model);
|
1089
|
+
|
1090
|
+
model.withTransaction(function (t) {
|
1091
|
+
t.modelBecameClean(stateName, model);
|
1092
|
+
});
|
1093
|
+
},
|
1094
|
+
|
1095
|
+
setProperty: setProperty,
|
1096
|
+
|
1097
|
+
willCommit: function(manager) {
|
1098
|
+
manager.goToState('saving');
|
1099
|
+
},
|
1100
|
+
|
1101
|
+
saving: DS.State.extend({
|
1102
|
+
isSaving: true,
|
1103
|
+
|
1104
|
+
didUpdate: function(manager) {
|
1105
|
+
manager.goToState('loaded');
|
1106
|
+
},
|
1107
|
+
|
1108
|
+
wasInvalid: function(manager, errors) {
|
1109
|
+
var model = get(manager, 'model');
|
1110
|
+
|
1111
|
+
set(model, 'errors', errors);
|
1112
|
+
manager.goToState('invalid');
|
1113
|
+
}
|
1114
|
+
}),
|
1115
|
+
|
1116
|
+
invalid: DS.State.extend({
|
1117
|
+
isValid: false,
|
1118
|
+
|
1119
|
+
setProperty: function(manager, context) {
|
1120
|
+
setProperty(manager, context);
|
1121
|
+
|
1122
|
+
var stateName = getPath(this, 'parentState.stateName'),
|
1123
|
+
model = get(manager, 'model'),
|
1124
|
+
errors = get(model, 'errors'),
|
1125
|
+
key = context.key;
|
1126
|
+
|
1127
|
+
delete errors[key];
|
1128
|
+
|
1129
|
+
if (isEmptyObject(errors)) {
|
1130
|
+
manager.goToState(stateName);
|
1131
|
+
}
|
1132
|
+
}
|
1133
|
+
})
|
1134
|
+
});
|
1135
|
+
|
942
1136
|
var states = {
|
943
1137
|
rootState: SC.State.create({
|
944
1138
|
isLoaded: false,
|
@@ -947,6 +1141,7 @@ var states = {
|
|
947
1141
|
isDeleted: false,
|
948
1142
|
isError: false,
|
949
1143
|
isNew: false,
|
1144
|
+
isValid: true,
|
950
1145
|
|
951
1146
|
willLoadData: cantLoadData,
|
952
1147
|
|
@@ -988,16 +1183,7 @@ var states = {
|
|
988
1183
|
willLoadData: SC.K,
|
989
1184
|
|
990
1185
|
setProperty: function(manager, context) {
|
991
|
-
|
992
|
-
|
993
|
-
var model = get(manager, 'model'), type = model.constructor;
|
994
|
-
var store = get(model, 'store');
|
995
|
-
var data = get(model, 'data');
|
996
|
-
|
997
|
-
data[key] = value;
|
998
|
-
|
999
|
-
if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
|
1000
|
-
|
1186
|
+
setProperty(manager, context);
|
1001
1187
|
manager.goToState('updated');
|
1002
1188
|
},
|
1003
1189
|
|
@@ -1005,83 +1191,21 @@ var states = {
|
|
1005
1191
|
manager.goToState('deleted');
|
1006
1192
|
},
|
1007
1193
|
|
1008
|
-
created:
|
1194
|
+
created: DirtyState.create({
|
1195
|
+
stateName: 'created',
|
1009
1196
|
isNew: true,
|
1010
|
-
isDirty: true,
|
1011
|
-
|
1012
|
-
enter: function(manager) {
|
1013
|
-
var model = get(manager, 'model');
|
1014
|
-
var store = get(model, 'store');
|
1015
|
-
|
1016
|
-
if (store) { store.modelBecameDirty('created', model); }
|
1017
|
-
},
|
1018
|
-
|
1019
|
-
exit: function(manager) {
|
1020
|
-
var model = get(manager, 'model');
|
1021
|
-
var store = get(model, 'store');
|
1022
1197
|
|
1198
|
+
notifyModel: function(model) {
|
1023
1199
|
model.didCreate();
|
1024
|
-
|
1025
|
-
if (store) { store.modelBecameClean('created', model); }
|
1026
|
-
},
|
1027
|
-
|
1028
|
-
setProperty: function(manager, context) {
|
1029
|
-
var key = context.key, value = context.value;
|
1030
|
-
|
1031
|
-
var model = get(manager, 'model'), type = model.constructor;
|
1032
|
-
var store = get(model, 'store');
|
1033
|
-
var data = get(model, 'data');
|
1034
|
-
|
1035
|
-
data[key] = value;
|
1036
|
-
|
1037
|
-
if (store) { store.hashWasUpdated(type, get(model, 'clientId')); }
|
1038
|
-
},
|
1039
|
-
|
1040
|
-
willCommit: function(manager) {
|
1041
|
-
manager.goToState('saving');
|
1042
|
-
},
|
1043
|
-
|
1044
|
-
saving: DS.State.create({
|
1045
|
-
isSaving: true,
|
1046
|
-
|
1047
|
-
didUpdate: function(manager) {
|
1048
|
-
manager.goToState('loaded');
|
1049
|
-
}
|
1050
|
-
})
|
1200
|
+
}
|
1051
1201
|
}),
|
1052
1202
|
|
1053
|
-
updated:
|
1054
|
-
|
1055
|
-
|
1056
|
-
willLoadData: cantLoadData,
|
1057
|
-
|
1058
|
-
enter: function(manager) {
|
1059
|
-
var model = get(manager, 'model');
|
1060
|
-
var store = get(model, 'store');
|
1061
|
-
|
1062
|
-
if (store) { store.modelBecameDirty('updated', model); }
|
1063
|
-
},
|
1064
|
-
|
1065
|
-
willCommit: function(manager) {
|
1066
|
-
manager.goToState('saving');
|
1067
|
-
},
|
1068
|
-
|
1069
|
-
exit: function(manager) {
|
1070
|
-
var model = get(manager, 'model');
|
1071
|
-
var store = get(model, 'store');
|
1203
|
+
updated: DirtyState.create({
|
1204
|
+
stateName: 'updated',
|
1072
1205
|
|
1206
|
+
notifyModel: function(model) {
|
1073
1207
|
model.didUpdate();
|
1074
|
-
|
1075
|
-
if (store) { store.modelBecameClean('updated', model); }
|
1076
|
-
},
|
1077
|
-
|
1078
|
-
saving: DS.State.create({
|
1079
|
-
isSaving: true,
|
1080
|
-
|
1081
|
-
didUpdate: function(manager) {
|
1082
|
-
manager.goToState('loaded');
|
1083
|
-
}
|
1084
|
-
})
|
1208
|
+
}
|
1085
1209
|
})
|
1086
1210
|
}),
|
1087
1211
|
|
@@ -1098,8 +1222,11 @@ var states = {
|
|
1098
1222
|
|
1099
1223
|
if (store) {
|
1100
1224
|
store.removeFromModelArrays(model);
|
1101
|
-
store.modelBecameDirty('deleted', model);
|
1102
1225
|
}
|
1226
|
+
|
1227
|
+
model.withTransaction(function(t) {
|
1228
|
+
t.modelBecameDirty('deleted', model);
|
1229
|
+
});
|
1103
1230
|
},
|
1104
1231
|
|
1105
1232
|
willCommit: function(manager) {
|
@@ -1115,9 +1242,10 @@ var states = {
|
|
1115
1242
|
|
1116
1243
|
exit: function(stateManager) {
|
1117
1244
|
var model = get(stateManager, 'model');
|
1118
|
-
var store = get(model, 'store');
|
1119
1245
|
|
1120
|
-
|
1246
|
+
model.withTransaction(function(t) {
|
1247
|
+
t.modelBecameClean('deleted', model);
|
1248
|
+
});
|
1121
1249
|
}
|
1122
1250
|
}),
|
1123
1251
|
|
@@ -1149,11 +1277,15 @@ DS.Model = SC.Object.extend({
|
|
1149
1277
|
isDeleted: retrieveFromCurrentState,
|
1150
1278
|
isError: retrieveFromCurrentState,
|
1151
1279
|
isNew: retrieveFromCurrentState,
|
1280
|
+
isValid: retrieveFromCurrentState,
|
1152
1281
|
|
1153
1282
|
clientId: null,
|
1154
1283
|
|
1284
|
+
// because unknownProperty is used, any internal property
|
1285
|
+
// must be initialized here.
|
1155
1286
|
primaryKey: 'id',
|
1156
1287
|
data: null,
|
1288
|
+
transaction: null,
|
1157
1289
|
|
1158
1290
|
didLoad: Ember.K,
|
1159
1291
|
didUpdate: Ember.K,
|
@@ -1168,6 +1300,12 @@ DS.Model = SC.Object.extend({
|
|
1168
1300
|
stateManager.goToState('empty');
|
1169
1301
|
},
|
1170
1302
|
|
1303
|
+
withTransaction: function(fn) {
|
1304
|
+
var transaction = get(this, 'transaction') || getPath(this, 'store.defaultTransaction');
|
1305
|
+
|
1306
|
+
if (transaction) { fn(transaction); }
|
1307
|
+
},
|
1308
|
+
|
1171
1309
|
setData: function(data) {
|
1172
1310
|
var stateManager = get(this, 'stateManager');
|
1173
1311
|
stateManager.send('setData', data);
|
@@ -1178,11 +1316,16 @@ DS.Model = SC.Object.extend({
|
|
1178
1316
|
stateManager.send('setProperty', { key: key, value: value });
|
1179
1317
|
},
|
1180
1318
|
|
1181
|
-
|
1319
|
+
deleteRecord: function() {
|
1182
1320
|
var stateManager = get(this, 'stateManager');
|
1183
1321
|
stateManager.send('delete');
|
1184
1322
|
},
|
1185
1323
|
|
1324
|
+
destroy: function() {
|
1325
|
+
this.deleteRecord();
|
1326
|
+
this._super();
|
1327
|
+
},
|
1328
|
+
|
1186
1329
|
loadingData: function() {
|
1187
1330
|
var stateManager = get(this, 'stateManager');
|
1188
1331
|
stateManager.send('loadingData');
|
@@ -1213,6 +1356,11 @@ DS.Model = SC.Object.extend({
|
|
1213
1356
|
stateManager.send('didDelete');
|
1214
1357
|
},
|
1215
1358
|
|
1359
|
+
wasInvalid: function(errors) {
|
1360
|
+
var stateManager = get(this, 'stateManager');
|
1361
|
+
stateManager.send('wasInvalid', errors);
|
1362
|
+
},
|
1363
|
+
|
1216
1364
|
unknownProperty: function(key) {
|
1217
1365
|
var data = get(this, 'data');
|
1218
1366
|
|
@@ -1332,7 +1480,7 @@ DS.attr.transforms = {
|
|
1332
1480
|
|
1333
1481
|
if (type === "string" || type === "number") {
|
1334
1482
|
return new Date(serialized);
|
1335
|
-
} else if (serialized
|
1483
|
+
} else if (serialized === null || serialized === undefined) {
|
1336
1484
|
// if the value is not present in the data,
|
1337
1485
|
// return undefined, not null.
|
1338
1486
|
return serialized;
|