embient 0.0.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/.gitignore +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +114 -0
- data/MIT-LICENSE +20 -0
- data/README.md +15 -0
- data/Rakefile +27 -0
- data/embient.gemspec +24 -0
- data/lib/embient.rb +4 -0
- data/lib/embient/engine.rb +7 -0
- data/lib/embient/version.rb +3 -0
- data/lib/tasks/embient_tasks.rake +4 -0
- data/vendor/assets/javascripts/embient/addons/ember-data.js +2176 -0
- data/vendor/assets/javascripts/embient/addons/sproutcore-routing.js +570 -0
- data/vendor/assets/javascripts/embient/addons/sproutcore-statechart.js +3790 -0
- data/vendor/assets/javascripts/embient/require.js +2052 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
embient (0.0.1)
|
|
5
|
+
coffee-rails
|
|
6
|
+
emberjs-rails
|
|
7
|
+
jquery-rails
|
|
8
|
+
rails (~> 3.2.1)
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: http://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
actionmailer (3.2.1)
|
|
14
|
+
actionpack (= 3.2.1)
|
|
15
|
+
mail (~> 2.4.0)
|
|
16
|
+
actionpack (3.2.1)
|
|
17
|
+
activemodel (= 3.2.1)
|
|
18
|
+
activesupport (= 3.2.1)
|
|
19
|
+
builder (~> 3.0.0)
|
|
20
|
+
erubis (~> 2.7.0)
|
|
21
|
+
journey (~> 1.0.1)
|
|
22
|
+
rack (~> 1.4.0)
|
|
23
|
+
rack-cache (~> 1.1)
|
|
24
|
+
rack-test (~> 0.6.1)
|
|
25
|
+
sprockets (~> 2.1.2)
|
|
26
|
+
activemodel (3.2.1)
|
|
27
|
+
activesupport (= 3.2.1)
|
|
28
|
+
builder (~> 3.0.0)
|
|
29
|
+
activerecord (3.2.1)
|
|
30
|
+
activemodel (= 3.2.1)
|
|
31
|
+
activesupport (= 3.2.1)
|
|
32
|
+
arel (~> 3.0.0)
|
|
33
|
+
tzinfo (~> 0.3.29)
|
|
34
|
+
activeresource (3.2.1)
|
|
35
|
+
activemodel (= 3.2.1)
|
|
36
|
+
activesupport (= 3.2.1)
|
|
37
|
+
activesupport (3.2.1)
|
|
38
|
+
i18n (~> 0.6)
|
|
39
|
+
multi_json (~> 1.0)
|
|
40
|
+
arel (3.0.0)
|
|
41
|
+
builder (3.0.0)
|
|
42
|
+
coffee-rails (3.2.2)
|
|
43
|
+
coffee-script (>= 2.2.0)
|
|
44
|
+
railties (~> 3.2.0)
|
|
45
|
+
coffee-script (2.2.0)
|
|
46
|
+
coffee-script-source
|
|
47
|
+
execjs
|
|
48
|
+
coffee-script-source (1.2.0)
|
|
49
|
+
emberjs-rails (2012.1.25)
|
|
50
|
+
hamlbars
|
|
51
|
+
rails (~> 3.1)
|
|
52
|
+
erubis (2.7.0)
|
|
53
|
+
execjs (1.3.0)
|
|
54
|
+
multi_json (~> 1.0)
|
|
55
|
+
haml (3.1.4)
|
|
56
|
+
hamlbars (2012.1.13)
|
|
57
|
+
haml
|
|
58
|
+
sprockets
|
|
59
|
+
tilt
|
|
60
|
+
hike (1.2.1)
|
|
61
|
+
i18n (0.6.0)
|
|
62
|
+
journey (1.0.1)
|
|
63
|
+
jquery-rails (2.0.0)
|
|
64
|
+
railties (>= 3.2.0.beta, < 5.0)
|
|
65
|
+
thor (~> 0.14)
|
|
66
|
+
json (1.6.5)
|
|
67
|
+
mail (2.4.1)
|
|
68
|
+
i18n (>= 0.4.0)
|
|
69
|
+
mime-types (~> 1.16)
|
|
70
|
+
treetop (~> 1.4.8)
|
|
71
|
+
mime-types (1.17.2)
|
|
72
|
+
multi_json (1.1.0)
|
|
73
|
+
polyglot (0.3.3)
|
|
74
|
+
rack (1.4.1)
|
|
75
|
+
rack-cache (1.1)
|
|
76
|
+
rack (>= 0.4)
|
|
77
|
+
rack-ssl (1.3.2)
|
|
78
|
+
rack
|
|
79
|
+
rack-test (0.6.1)
|
|
80
|
+
rack (>= 1.0)
|
|
81
|
+
rails (3.2.1)
|
|
82
|
+
actionmailer (= 3.2.1)
|
|
83
|
+
actionpack (= 3.2.1)
|
|
84
|
+
activerecord (= 3.2.1)
|
|
85
|
+
activeresource (= 3.2.1)
|
|
86
|
+
activesupport (= 3.2.1)
|
|
87
|
+
bundler (~> 1.0)
|
|
88
|
+
railties (= 3.2.1)
|
|
89
|
+
railties (3.2.1)
|
|
90
|
+
actionpack (= 3.2.1)
|
|
91
|
+
activesupport (= 3.2.1)
|
|
92
|
+
rack-ssl (~> 1.3.2)
|
|
93
|
+
rake (>= 0.8.7)
|
|
94
|
+
rdoc (~> 3.4)
|
|
95
|
+
thor (~> 0.14.6)
|
|
96
|
+
rake (0.9.2.2)
|
|
97
|
+
rdoc (3.12)
|
|
98
|
+
json (~> 1.4)
|
|
99
|
+
sprockets (2.1.2)
|
|
100
|
+
hike (~> 1.2)
|
|
101
|
+
rack (~> 1.0)
|
|
102
|
+
tilt (~> 1.1, != 1.3.0)
|
|
103
|
+
thor (0.14.6)
|
|
104
|
+
tilt (1.3.3)
|
|
105
|
+
treetop (1.4.10)
|
|
106
|
+
polyglot
|
|
107
|
+
polyglot (>= 0.3.1)
|
|
108
|
+
tzinfo (0.3.31)
|
|
109
|
+
|
|
110
|
+
PLATFORMS
|
|
111
|
+
ruby
|
|
112
|
+
|
|
113
|
+
DEPENDENCIES
|
|
114
|
+
embient!
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2012 YOURNAME
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Embient
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Embient is an opinionated gem that pulls together Ember.js and its best
|
|
5
|
+
addons to be used in the Rails 3.1+ asset pipeline. It also provides
|
|
6
|
+
Require.js to build modular JavaScript applications and libraries with
|
|
7
|
+
and around Embient.
|
|
8
|
+
|
|
9
|
+
To use Embient in your Rails application simply include it in your Gemfile:
|
|
10
|
+
|
|
11
|
+
gem 'embient'
|
|
12
|
+
|
|
13
|
+
and run
|
|
14
|
+
|
|
15
|
+
bundle install
|
data/Rakefile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
begin
|
|
8
|
+
require 'rdoc/task'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
require 'rdoc/rdoc'
|
|
11
|
+
require 'rake/rdoctask'
|
|
12
|
+
RDoc::Task = Rake::RDocTask
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
17
|
+
rdoc.title = 'Embient'
|
|
18
|
+
rdoc.options << '--line-numbers'
|
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Bundler::GemHelper.install_tasks
|
|
27
|
+
|
data/embient.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
2
|
+
|
|
3
|
+
# Maintain your gem's version:
|
|
4
|
+
require "embient/version"
|
|
5
|
+
|
|
6
|
+
# Describe your gem and declare its dependencies:
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
s.name = "embient"
|
|
9
|
+
s.version = Embient::VERSION
|
|
10
|
+
s.platform = Gem::Platform::RUBY
|
|
11
|
+
s.authors = ["Dominik Guzei"]
|
|
12
|
+
s.email = ["dominik.guzei@gmail.com"]
|
|
13
|
+
s.homepage = "https://github.com/DominikGuzei/embient"
|
|
14
|
+
s.summary = "Opinionated Ember.js environment and addons for Ruby on Rails"
|
|
15
|
+
s.description = "Pulls together various existing ember/sproutcore addons and provides structure for future extensions."
|
|
16
|
+
|
|
17
|
+
s.files = `git ls-files`.split("\n")
|
|
18
|
+
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
s.add_dependency "rails", ">= 3.1.0"
|
|
22
|
+
s.add_dependency "ember-rails"
|
|
23
|
+
|
|
24
|
+
end
|
data/lib/embient.rb
ADDED
|
@@ -0,0 +1,2176 @@
|
|
|
1
|
+
|
|
2
|
+
(function(exports) {
|
|
3
|
+
window.DS = Ember.Namespace.create();
|
|
4
|
+
|
|
5
|
+
})({});
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
(function(exports) {
|
|
9
|
+
DS.Adapter = Ember.Object.extend({
|
|
10
|
+
commit: function(store, commitDetails) {
|
|
11
|
+
commitDetails.updated.eachType(function(type, array) {
|
|
12
|
+
this.updateRecords(store, type, array.slice());
|
|
13
|
+
}, this);
|
|
14
|
+
|
|
15
|
+
commitDetails.created.eachType(function(type, array) {
|
|
16
|
+
this.createRecords(store, type, array.slice());
|
|
17
|
+
}, this);
|
|
18
|
+
|
|
19
|
+
commitDetails.deleted.eachType(function(type, array) {
|
|
20
|
+
this.deleteRecords(store, type, array.slice());
|
|
21
|
+
}, this);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
createRecords: function(store, type, models) {
|
|
25
|
+
models.forEach(function(model) {
|
|
26
|
+
this.createRecord(store, type, model);
|
|
27
|
+
}, this);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
updateRecords: function(store, type, models) {
|
|
31
|
+
models.forEach(function(model) {
|
|
32
|
+
this.updateRecord(store, type, model);
|
|
33
|
+
}, this);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
deleteRecords: function(store, type, models) {
|
|
37
|
+
models.forEach(function(model) {
|
|
38
|
+
this.deleteRecord(store, type, model);
|
|
39
|
+
}, this);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
findMany: function(store, type, ids) {
|
|
43
|
+
ids.forEach(function(id) {
|
|
44
|
+
this.find(store, type, id);
|
|
45
|
+
}, this);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
})({});
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
(function(exports) {
|
|
52
|
+
DS.fixtureAdapter = DS.Adapter.create({
|
|
53
|
+
find: function(store, type, id) {
|
|
54
|
+
var fixtures = type.FIXTURES;
|
|
55
|
+
|
|
56
|
+
ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
|
|
57
|
+
if (fixtures.hasLoaded) { return; }
|
|
58
|
+
|
|
59
|
+
setTimeout(function() {
|
|
60
|
+
store.loadMany(type, fixtures);
|
|
61
|
+
fixtures.hasLoaded = true;
|
|
62
|
+
}, 300);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
findMany: function() {
|
|
66
|
+
this.find.apply(this, arguments);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
findAll: function(store, type) {
|
|
70
|
+
var fixtures = type.FIXTURES;
|
|
71
|
+
|
|
72
|
+
ember_assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
|
|
73
|
+
|
|
74
|
+
var ids = fixtures.map(function(item, index, self){ return item.id; });
|
|
75
|
+
store.loadMany(type, ids, fixtures);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
})({});
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
(function(exports) {
|
|
84
|
+
/*global jQuery*/
|
|
85
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
|
86
|
+
|
|
87
|
+
DS.RESTAdapter = DS.Adapter.extend({
|
|
88
|
+
createRecord: function(store, type, model) {
|
|
89
|
+
var root = this.rootForType(type);
|
|
90
|
+
|
|
91
|
+
var data = {};
|
|
92
|
+
data[root] = get(model, 'data');
|
|
93
|
+
|
|
94
|
+
this.ajax("/" + this.pluralize(root), "POST", {
|
|
95
|
+
data: data,
|
|
96
|
+
success: function(json) {
|
|
97
|
+
store.didCreateRecord(model, json[root]);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
createRecords: function(store, type, models) {
|
|
103
|
+
if (get(this, 'bulkCommit') === false) {
|
|
104
|
+
return this._super(store, type, models);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var root = this.rootForType(type),
|
|
108
|
+
plural = this.pluralize(root);
|
|
109
|
+
|
|
110
|
+
var data = {};
|
|
111
|
+
data[plural] = models.map(function(model) {
|
|
112
|
+
return get(model, 'data');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.ajax("/" + this.pluralize(root), "POST", {
|
|
116
|
+
data: data,
|
|
117
|
+
success: function(json) {
|
|
118
|
+
store.didCreateRecords(type, models, json[plural]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
updateRecord: function(store, type, model) {
|
|
124
|
+
var id = get(model, 'id');
|
|
125
|
+
var root = this.rootForType(type);
|
|
126
|
+
|
|
127
|
+
var data = {};
|
|
128
|
+
data[root] = get(model, 'data');
|
|
129
|
+
|
|
130
|
+
var url = ["", this.pluralize(root), id].join("/");
|
|
131
|
+
|
|
132
|
+
this.ajax(url, "PUT", {
|
|
133
|
+
data: data,
|
|
134
|
+
success: function(json) {
|
|
135
|
+
store.didUpdateRecord(model, json[root]);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
updateRecords: function(store, type, models) {
|
|
141
|
+
if (get(this, 'bulkCommit') === false) {
|
|
142
|
+
return this._super(store, type, models);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var root = this.rootForType(type),
|
|
146
|
+
plural = this.pluralize(root);
|
|
147
|
+
|
|
148
|
+
var data = {};
|
|
149
|
+
data[plural] = models.map(function(model) {
|
|
150
|
+
return get(model, 'data');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.ajax("/" + this.pluralize(root), "POST", {
|
|
154
|
+
data: data,
|
|
155
|
+
success: function(json) {
|
|
156
|
+
store.didUpdateRecords(models, json[plural]);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
deleteRecord: function(store, type, model) {
|
|
162
|
+
var id = get(model, 'id');
|
|
163
|
+
var root = this.rootForType(type);
|
|
164
|
+
|
|
165
|
+
var url = ["", this.pluralize(root), id].join("/");
|
|
166
|
+
|
|
167
|
+
this.ajax(url, "DELETE", {
|
|
168
|
+
success: function(json) {
|
|
169
|
+
store.didDeleteRecord(model);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
deleteRecords: function(store, type, models) {
|
|
175
|
+
if (get(this, 'bulkCommit') === false) {
|
|
176
|
+
return this._super(store, type, models);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
var root = this.rootForType(type),
|
|
180
|
+
plural = this.pluralize(root);
|
|
181
|
+
|
|
182
|
+
var data = {};
|
|
183
|
+
data[plural] = models.map(function(model) {
|
|
184
|
+
return get(model, 'id');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.ajax("/" + this.pluralize(root) + "/delete", "POST", {
|
|
188
|
+
data: data,
|
|
189
|
+
success: function(json) {
|
|
190
|
+
store.didDeleteRecords(models);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
find: function(store, type, id) {
|
|
196
|
+
var root = this.rootForType(type);
|
|
197
|
+
|
|
198
|
+
var url = ["", this.pluralize(root), id].join("/");
|
|
199
|
+
|
|
200
|
+
this.ajax(url, "GET", {
|
|
201
|
+
success: function(json) {
|
|
202
|
+
store.load(type, json[root]);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
findMany: function(store, type, ids) {
|
|
208
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
|
209
|
+
|
|
210
|
+
this.ajax("/" + plural, "GET", {
|
|
211
|
+
data: { ids: ids },
|
|
212
|
+
success: function(json) {
|
|
213
|
+
store.loadMany(type, ids, json[plural]);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
findAll: function(store, type) {
|
|
219
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
|
220
|
+
|
|
221
|
+
this.ajax("/" + plural, "GET", {
|
|
222
|
+
success: function(json) {
|
|
223
|
+
store.loadMany(type, json[plural]);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
findQuery: function(store, type, query, modelArray) {
|
|
229
|
+
var root = this.rootForType(type), plural = this.pluralize(root);
|
|
230
|
+
|
|
231
|
+
this.ajax("/" + plural, "GET", {
|
|
232
|
+
data: query,
|
|
233
|
+
success: function(json) {
|
|
234
|
+
modelArray.load(json[plural]);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// HELPERS
|
|
240
|
+
|
|
241
|
+
plurals: {},
|
|
242
|
+
|
|
243
|
+
// define a plurals hash in your subclass to define
|
|
244
|
+
// special-case pluralization
|
|
245
|
+
pluralize: function(name) {
|
|
246
|
+
return this.plurals[name] || name + "s";
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
rootForType: function(type) {
|
|
250
|
+
if (type.url) { return type.url; }
|
|
251
|
+
|
|
252
|
+
// use the last part of the name as the URL
|
|
253
|
+
var parts = type.toString().split(".");
|
|
254
|
+
var name = parts[parts.length - 1];
|
|
255
|
+
return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
ajax: function(url, type, hash) {
|
|
259
|
+
hash.url = url;
|
|
260
|
+
hash.type = type;
|
|
261
|
+
hash.dataType = "json";
|
|
262
|
+
|
|
263
|
+
jQuery.ajax(hash);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
})({});
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
(function(exports) {
|
|
272
|
+
var get = Ember.get, set = Ember.set;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
A model array is an array that contains records of a certain type. The model
|
|
276
|
+
array materializes records as needed when they are retrieved for the first
|
|
277
|
+
time. You should not create model arrays yourself. Instead, an instance of
|
|
278
|
+
DS.ModelArray or its subclasses will be returned by your application's store
|
|
279
|
+
in response to queries.
|
|
280
|
+
*/
|
|
281
|
+
|
|
282
|
+
DS.ModelArray = Ember.ArrayProxy.extend({
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
The model type contained by this model array.
|
|
286
|
+
|
|
287
|
+
@type DS.Model
|
|
288
|
+
*/
|
|
289
|
+
type: null,
|
|
290
|
+
|
|
291
|
+
// The array of client ids backing the model array. When a
|
|
292
|
+
// record is requested from the model array, the record
|
|
293
|
+
// for the client id at the same index is materialized, if
|
|
294
|
+
// necessary, by the store.
|
|
295
|
+
content: null,
|
|
296
|
+
|
|
297
|
+
// The store that created this model array.
|
|
298
|
+
store: null,
|
|
299
|
+
|
|
300
|
+
// for associations, the model that this association belongs to.
|
|
301
|
+
parentModel: null,
|
|
302
|
+
|
|
303
|
+
init: function() {
|
|
304
|
+
set(this, 'modelCache', Ember.A([]));
|
|
305
|
+
this._super();
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
// Overrides Ember.Array's replace method to implement
|
|
309
|
+
replace: function(index, removed, added) {
|
|
310
|
+
var parentRecord = get(this, 'parentRecord');
|
|
311
|
+
var pendingParent = parentRecord && !get(parentRecord, 'id');
|
|
312
|
+
|
|
313
|
+
added = added.map(function(item) {
|
|
314
|
+
ember_assert("You can only add items of " + (get(this, 'type') && get(this, 'type').toString()) + " to this association.", !get(this, 'type') || (get(this, 'type') === item.constructor));
|
|
315
|
+
|
|
316
|
+
if (pendingParent) { item.send('waitingOn', parentRecord); }
|
|
317
|
+
return item.get('clientId');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
this._super(index, removed, added);
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
arrayDidChange: function(array, index, removed, added) {
|
|
324
|
+
var modelCache = get(this, 'modelCache');
|
|
325
|
+
modelCache.replace(index, 0, new Array(added));
|
|
326
|
+
|
|
327
|
+
this._super(array, index, removed, added);
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
arrayWillChange: function(array, index, removed, added) {
|
|
331
|
+
this._super(array, index, removed, added);
|
|
332
|
+
|
|
333
|
+
var modelCache = get(this, 'modelCache');
|
|
334
|
+
modelCache.replace(index, removed);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
objectAtContent: function(index) {
|
|
338
|
+
var modelCache = get(this, 'modelCache');
|
|
339
|
+
var model = modelCache.objectAt(index);
|
|
340
|
+
|
|
341
|
+
if (!model) {
|
|
342
|
+
var store = get(this, 'store');
|
|
343
|
+
var content = get(this, 'content');
|
|
344
|
+
|
|
345
|
+
var contentObject = content.objectAt(index);
|
|
346
|
+
|
|
347
|
+
if (contentObject !== undefined) {
|
|
348
|
+
model = store.findByClientId(get(this, 'type'), contentObject);
|
|
349
|
+
modelCache.replace(index, 1, [model]);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return model;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
})({});
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
(function(exports) {
|
|
361
|
+
var get = Ember.get;
|
|
362
|
+
|
|
363
|
+
DS.FilteredModelArray = DS.ModelArray.extend({
|
|
364
|
+
filterFunction: null,
|
|
365
|
+
|
|
366
|
+
updateFilter: Ember.observer(function() {
|
|
367
|
+
var store = get(this, 'store');
|
|
368
|
+
store.updateModelArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
|
|
369
|
+
}, 'filterFunction')
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
})({});
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
(function(exports) {
|
|
376
|
+
var get = Ember.get, set = Ember.set;
|
|
377
|
+
|
|
378
|
+
DS.AdapterPopulatedModelArray = DS.ModelArray.extend({
|
|
379
|
+
query: null,
|
|
380
|
+
isLoaded: false,
|
|
381
|
+
|
|
382
|
+
load: function(array) {
|
|
383
|
+
var store = get(this, 'store'), type = get(this, 'type');
|
|
384
|
+
|
|
385
|
+
var clientIds = store.loadMany(type, array).clientIds;
|
|
386
|
+
|
|
387
|
+
this.beginPropertyChanges();
|
|
388
|
+
set(this, 'content', Ember.A(clientIds));
|
|
389
|
+
set(this, 'isLoaded', true);
|
|
390
|
+
this.endPropertyChanges();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
})({});
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
(function(exports) {
|
|
399
|
+
})({});
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
(function(exports) {
|
|
403
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
|
404
|
+
|
|
405
|
+
var OrderedSet = Ember.Object.extend({
|
|
406
|
+
init: function() {
|
|
407
|
+
this.clear();
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
clear: function() {
|
|
411
|
+
this.set('presenceSet', {});
|
|
412
|
+
this.set('list', Ember.NativeArray.apply([]));
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
add: function(obj) {
|
|
416
|
+
var guid = Ember.guidFor(obj),
|
|
417
|
+
presenceSet = get(this, 'presenceSet'),
|
|
418
|
+
list = get(this, 'list');
|
|
419
|
+
|
|
420
|
+
if (guid in presenceSet) { return; }
|
|
421
|
+
|
|
422
|
+
presenceSet[guid] = true;
|
|
423
|
+
list.pushObject(obj);
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
remove: function(obj) {
|
|
427
|
+
var guid = Ember.guidFor(obj),
|
|
428
|
+
presenceSet = get(this, 'presenceSet'),
|
|
429
|
+
list = get(this, 'list');
|
|
430
|
+
|
|
431
|
+
delete presenceSet[guid];
|
|
432
|
+
list.removeObject(obj);
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
isEmpty: function() {
|
|
436
|
+
return getPath(this, 'list.length') === 0;
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
forEach: function(fn, self) {
|
|
440
|
+
// allow mutation during iteration
|
|
441
|
+
get(this, 'list').slice().forEach(function(item) {
|
|
442
|
+
fn.call(self, item);
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
toArray: function() {
|
|
447
|
+
return get(this, 'list').slice();
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
A Hash stores values indexed by keys. Unlike JavaScript's
|
|
453
|
+
default Objects, the keys of a Hash can be any JavaScript
|
|
454
|
+
object.
|
|
455
|
+
|
|
456
|
+
Internally, a Hash has two data structures:
|
|
457
|
+
|
|
458
|
+
`keys`: an OrderedSet of all of the existing keys
|
|
459
|
+
`values`: a JavaScript Object indexed by the
|
|
460
|
+
Ember.guidFor(key)
|
|
461
|
+
|
|
462
|
+
When a key/value pair is added for the first time, we
|
|
463
|
+
add the key to the `keys` OrderedSet, and create or
|
|
464
|
+
replace an entry in `values`. When an entry is deleted,
|
|
465
|
+
we delete its entry in `keys` and `values`.
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
var Hash = Ember.Object.extend({
|
|
469
|
+
init: function() {
|
|
470
|
+
set(this, 'keys', OrderedSet.create());
|
|
471
|
+
set(this, 'values', {});
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
add: function(key, value) {
|
|
475
|
+
var keys = get(this, 'keys'), values = get(this, 'values');
|
|
476
|
+
var guid = Ember.guidFor(key);
|
|
477
|
+
|
|
478
|
+
keys.add(key);
|
|
479
|
+
values[guid] = value;
|
|
480
|
+
|
|
481
|
+
return value;
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
remove: function(key) {
|
|
485
|
+
var keys = get(this, 'keys'), values = get(this, 'values');
|
|
486
|
+
var guid = Ember.guidFor(key), value;
|
|
487
|
+
|
|
488
|
+
keys.remove(key);
|
|
489
|
+
|
|
490
|
+
value = values[guid];
|
|
491
|
+
delete values[guid];
|
|
492
|
+
|
|
493
|
+
return value;
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
fetch: function(key) {
|
|
497
|
+
var values = get(this, 'values');
|
|
498
|
+
var guid = Ember.guidFor(key);
|
|
499
|
+
|
|
500
|
+
return values[guid];
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
forEach: function(fn, binding) {
|
|
504
|
+
var keys = get(this, 'keys'),
|
|
505
|
+
values = get(this, 'values');
|
|
506
|
+
|
|
507
|
+
keys.forEach(function(key) {
|
|
508
|
+
var guid = Ember.guidFor(key);
|
|
509
|
+
fn.call(binding, key, values[guid]);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
DS.Transaction = Ember.Object.extend({
|
|
515
|
+
init: function() {
|
|
516
|
+
set(this, 'dirty', {
|
|
517
|
+
created: Hash.create(),
|
|
518
|
+
updated: Hash.create(),
|
|
519
|
+
deleted: Hash.create()
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
createRecord: function(type, hash) {
|
|
524
|
+
var store = get(this, 'store');
|
|
525
|
+
|
|
526
|
+
return store.createRecord(type, hash, this);
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
add: function(model) {
|
|
530
|
+
var modelTransaction = get(model, 'transaction');
|
|
531
|
+
ember_assert("Models cannot belong to more than one transaction at a time.", !modelTransaction);
|
|
532
|
+
|
|
533
|
+
set(model, 'transaction', this);
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
modelBecameDirty: function(kind, model) {
|
|
537
|
+
var dirty = get(get(this, 'dirty'), kind),
|
|
538
|
+
type = model.constructor;
|
|
539
|
+
|
|
540
|
+
var models = dirty.fetch(type);
|
|
541
|
+
|
|
542
|
+
models = models || dirty.add(type, OrderedSet.create());
|
|
543
|
+
models.add(model);
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
modelBecameClean: function(kind, model) {
|
|
547
|
+
var dirty = get(get(this, 'dirty'), kind),
|
|
548
|
+
type = model.constructor;
|
|
549
|
+
|
|
550
|
+
var models = dirty.fetch(type);
|
|
551
|
+
models.remove(model);
|
|
552
|
+
|
|
553
|
+
set(model, 'transaction', null);
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
commit: function() {
|
|
557
|
+
var dirtyMap = get(this, 'dirty');
|
|
558
|
+
|
|
559
|
+
var iterate = function(kind, fn, binding) {
|
|
560
|
+
var dirty = get(dirtyMap, kind);
|
|
561
|
+
|
|
562
|
+
dirty.forEach(function(type, models) {
|
|
563
|
+
if (models.isEmpty()) { return; }
|
|
564
|
+
|
|
565
|
+
var array = [];
|
|
566
|
+
|
|
567
|
+
models.forEach(function(model) {
|
|
568
|
+
model.send('willCommit');
|
|
569
|
+
|
|
570
|
+
if (get(model, 'isPending') === false) {
|
|
571
|
+
array.push(model);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
fn.call(binding, type, array);
|
|
576
|
+
});
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
var commitDetails = {
|
|
580
|
+
updated: {
|
|
581
|
+
eachType: function(fn, binding) { iterate('updated', fn, binding); }
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
created: {
|
|
585
|
+
eachType: function(fn, binding) { iterate('created', fn, binding); }
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
deleted: {
|
|
589
|
+
eachType: function(fn, binding) { iterate('deleted', fn, binding); }
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
var store = get(this, 'store');
|
|
594
|
+
var adapter = get(store, '_adapter');
|
|
595
|
+
if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
|
|
596
|
+
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
})({});
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
(function(exports) {
|
|
604
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, fmt = Ember.String.fmt;
|
|
605
|
+
|
|
606
|
+
var OrderedSet = Ember.Object.extend({
|
|
607
|
+
init: function() {
|
|
608
|
+
this.clear();
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
clear: function() {
|
|
612
|
+
this.set('presenceSet', {});
|
|
613
|
+
this.set('list', Ember.NativeArray.apply([]));
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
add: function(obj) {
|
|
617
|
+
var guid = Ember.guidFor(obj),
|
|
618
|
+
presenceSet = get(this, 'presenceSet'),
|
|
619
|
+
list = get(this, 'list');
|
|
620
|
+
|
|
621
|
+
if (guid in presenceSet) { return; }
|
|
622
|
+
|
|
623
|
+
presenceSet[guid] = true;
|
|
624
|
+
list.pushObject(obj);
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
remove: function(obj) {
|
|
628
|
+
var guid = Ember.guidFor(obj),
|
|
629
|
+
presenceSet = get(this, 'presenceSet'),
|
|
630
|
+
list = get(this, 'list');
|
|
631
|
+
|
|
632
|
+
delete presenceSet[guid];
|
|
633
|
+
list.removeObject(obj);
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
isEmpty: function() {
|
|
637
|
+
return getPath(this, 'list.length') === 0;
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
forEach: function(fn, self) {
|
|
641
|
+
get(this, 'list').forEach(function(item) {
|
|
642
|
+
fn.call(self, item);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Implementors Note:
|
|
648
|
+
//
|
|
649
|
+
// The variables in this file are consistently named according to the following
|
|
650
|
+
// scheme:
|
|
651
|
+
//
|
|
652
|
+
// * +id+ means an identifier managed by an external source, provided inside the
|
|
653
|
+
// data hash provided by that source.
|
|
654
|
+
// * +clientId+ means a transient numerical identifier generated at runtime by
|
|
655
|
+
// the data store. It is important primarily because newly created objects may
|
|
656
|
+
// not yet have an externally generated id.
|
|
657
|
+
// * +type+ means a subclass of DS.Model.
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
The store contains all of the hashes for data models loaded from the server.
|
|
661
|
+
It is also responsible for creating instances of DS.Model when you request one
|
|
662
|
+
of these data hashes, so that they can be bound to in your Handlebars templates.
|
|
663
|
+
|
|
664
|
+
Create a new store like this:
|
|
665
|
+
|
|
666
|
+
MyApp.store = DS.Store.create();
|
|
667
|
+
|
|
668
|
+
You can retrieve DS.Model instances from the store in several ways. To retrieve
|
|
669
|
+
a model for a specific id, use the `find()` method:
|
|
670
|
+
|
|
671
|
+
var model = MyApp.store.find(MyApp.Contact, 123);
|
|
672
|
+
|
|
673
|
+
By default, the store will talk to your backend using a standard REST mechanism.
|
|
674
|
+
You can customize how the store talks to your backend by specifying a custom adapter:
|
|
675
|
+
|
|
676
|
+
MyApp.store = DS.Store.create({
|
|
677
|
+
adapter: 'MyApp.CustomAdapter'
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
You can learn more about writing a custom adapter by reading the `DS.Adapter`
|
|
681
|
+
documentation.
|
|
682
|
+
*/
|
|
683
|
+
DS.Store = Ember.Object.extend({
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
Many methods can be invoked without specifying which store should be used.
|
|
687
|
+
In those cases, the first store created will be used as the default. If
|
|
688
|
+
an application has multiple stores, it should specify which store to use
|
|
689
|
+
when performing actions, such as finding records by id.
|
|
690
|
+
|
|
691
|
+
The init method registers this store as the default if none is specified.
|
|
692
|
+
*/
|
|
693
|
+
init: function() {
|
|
694
|
+
if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
|
|
695
|
+
set(DS, 'defaultStore', this);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
set(this, 'data', []);
|
|
699
|
+
set(this, '_typeMap', {});
|
|
700
|
+
set(this, 'models', []);
|
|
701
|
+
set(this, 'modelArrays', []);
|
|
702
|
+
set(this, 'modelArraysByClientId', {});
|
|
703
|
+
set(this, 'defaultTransaction', DS.Transaction.create({ store: this }));
|
|
704
|
+
|
|
705
|
+
return this._super();
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
transaction: function() {
|
|
709
|
+
return DS.Transaction.create({ store: this });
|
|
710
|
+
},
|
|
711
|
+
|
|
712
|
+
modelArraysForClientId: function(clientId) {
|
|
713
|
+
var modelArrays = get(this, 'modelArraysByClientId');
|
|
714
|
+
var ret = modelArrays[clientId];
|
|
715
|
+
|
|
716
|
+
if (!ret) {
|
|
717
|
+
ret = modelArrays[clientId] = OrderedSet.create();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return ret;
|
|
721
|
+
},
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
The adapter to use to communicate to a backend server or other persistence layer.
|
|
725
|
+
|
|
726
|
+
This can be specified as an instance, a class, or a property path that specifies
|
|
727
|
+
where the adapter can be located.
|
|
728
|
+
|
|
729
|
+
@property {DS.Adapter|String}
|
|
730
|
+
*/
|
|
731
|
+
adapter: null,
|
|
732
|
+
|
|
733
|
+
_adapter: Ember.computed(function() {
|
|
734
|
+
var adapter = get(this, 'adapter');
|
|
735
|
+
if (typeof adapter === 'string') {
|
|
736
|
+
return getPath(this, adapter, false) || getPath(window, adapter);
|
|
737
|
+
}
|
|
738
|
+
return adapter;
|
|
739
|
+
}).property('adapter').cacheable(),
|
|
740
|
+
|
|
741
|
+
clientIdCounter: -1,
|
|
742
|
+
|
|
743
|
+
// ....................
|
|
744
|
+
// . CREATE NEW MODEL .
|
|
745
|
+
// ....................
|
|
746
|
+
|
|
747
|
+
createRecord: function(type, properties, transaction) {
|
|
748
|
+
properties = properties || {};
|
|
749
|
+
|
|
750
|
+
var id = properties[getPath(type, 'proto.primaryKey')] || null;
|
|
751
|
+
|
|
752
|
+
var model = type._create({
|
|
753
|
+
store: this,
|
|
754
|
+
transaction: transaction
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
var hash = {}, clientId;
|
|
758
|
+
|
|
759
|
+
clientId = this.pushHash(hash, id, type);
|
|
760
|
+
model.send('setData', hash);
|
|
761
|
+
|
|
762
|
+
var models = get(this, 'models');
|
|
763
|
+
|
|
764
|
+
set(model, 'clientId', clientId);
|
|
765
|
+
models[clientId] = model;
|
|
766
|
+
|
|
767
|
+
model.setProperties(properties);
|
|
768
|
+
this.updateModelArrays(type, clientId, hash);
|
|
769
|
+
|
|
770
|
+
return model;
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
// ................
|
|
774
|
+
// . DELETE MODEL .
|
|
775
|
+
// ................
|
|
776
|
+
|
|
777
|
+
deleteRecord: function(model) {
|
|
778
|
+
model.send('deleteRecord');
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
// ...............
|
|
782
|
+
// . FIND MODELS .
|
|
783
|
+
// ...............
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
Finds a model by its id. If the data for that model has already been
|
|
787
|
+
loaded, an instance of DS.Model with that data will be returned
|
|
788
|
+
immediately. Otherwise, an empty DS.Model instance will be returned in
|
|
789
|
+
the loading state. As soon as the requested data is available, the model
|
|
790
|
+
will be moved into the loaded state and all of the information will be
|
|
791
|
+
available.
|
|
792
|
+
|
|
793
|
+
Note that only one DS.Model instance is ever created per unique id for a
|
|
794
|
+
given type.
|
|
795
|
+
|
|
796
|
+
Example:
|
|
797
|
+
|
|
798
|
+
var record = MyApp.store.find(MyApp.Person, 1234);
|
|
799
|
+
|
|
800
|
+
@param {DS.Model} type
|
|
801
|
+
@param {String|Number} id
|
|
802
|
+
*/
|
|
803
|
+
find: function(type, id, query) {
|
|
804
|
+
if (id === undefined) {
|
|
805
|
+
return this.findAll(type);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (query !== undefined) {
|
|
809
|
+
return this.findMany(type, id, query);
|
|
810
|
+
} else if (Ember.typeOf(id) === 'object') {
|
|
811
|
+
return this.findQuery(type, id);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (Ember.isArray(id)) {
|
|
815
|
+
return this.findMany(type, id);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
var clientId = this.clientIdForId(type, id);
|
|
819
|
+
|
|
820
|
+
return this.findByClientId(type, clientId, id);
|
|
821
|
+
},
|
|
822
|
+
|
|
823
|
+
findByClientId: function(type, clientId, id) {
|
|
824
|
+
var model;
|
|
825
|
+
|
|
826
|
+
var models = get(this, 'models');
|
|
827
|
+
var data = this.clientIdToHashMap(type);
|
|
828
|
+
|
|
829
|
+
// If there is already a clientId assigned for this
|
|
830
|
+
// type/id combination, try to find an existing
|
|
831
|
+
// model for that id and return. Otherwise,
|
|
832
|
+
// materialize a new model and set its data to the
|
|
833
|
+
// value we already have.
|
|
834
|
+
if (clientId !== undefined) {
|
|
835
|
+
model = models[clientId];
|
|
836
|
+
|
|
837
|
+
if (!model) {
|
|
838
|
+
// create a new instance of the model in the
|
|
839
|
+
// 'isLoading' state
|
|
840
|
+
model = this.materializeRecord(type, clientId);
|
|
841
|
+
|
|
842
|
+
// immediately set its data
|
|
843
|
+
model.send('setData', data[clientId] || null);
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
clientId = this.pushHash(null, id, type);
|
|
847
|
+
|
|
848
|
+
// create a new instance of the model in the
|
|
849
|
+
// 'isLoading' state
|
|
850
|
+
model = this.materializeRecord(type, clientId);
|
|
851
|
+
|
|
852
|
+
// let the adapter set the data, possibly async
|
|
853
|
+
var adapter = get(this, '_adapter');
|
|
854
|
+
if (adapter && adapter.find) { adapter.find(this, type, id); }
|
|
855
|
+
else { throw fmt("Adapter is either null or does not implement `find` method", this); }
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return model;
|
|
859
|
+
},
|
|
860
|
+
|
|
861
|
+
/** @private
|
|
862
|
+
*/
|
|
863
|
+
findMany: function(type, ids, query) {
|
|
864
|
+
var idToClientIdMap = this.idToClientIdMap(type);
|
|
865
|
+
var data = this.clientIdToHashMap(type), needed;
|
|
866
|
+
|
|
867
|
+
var clientIds = Ember.A([]);
|
|
868
|
+
|
|
869
|
+
if (ids) {
|
|
870
|
+
needed = [];
|
|
871
|
+
|
|
872
|
+
ids.forEach(function(id) {
|
|
873
|
+
var clientId = idToClientIdMap[id];
|
|
874
|
+
if (clientId === undefined || data[clientId] === undefined) {
|
|
875
|
+
clientId = this.pushHash(null, id, type);
|
|
876
|
+
needed.push(id);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
clientIds.push(clientId);
|
|
880
|
+
}, this);
|
|
881
|
+
} else {
|
|
882
|
+
needed = null;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if ((needed && get(needed, 'length') > 0) || query) {
|
|
886
|
+
var adapter = get(this, '_adapter');
|
|
887
|
+
if (adapter && adapter.findMany) { adapter.findMany(this, type, needed, query); }
|
|
888
|
+
else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return this.createModelArray(type, clientIds);
|
|
892
|
+
},
|
|
893
|
+
|
|
894
|
+
findQuery: function(type, query) {
|
|
895
|
+
var array = DS.AdapterPopulatedModelArray.create({ type: type, content: Ember.A([]), store: this });
|
|
896
|
+
var adapter = get(this, '_adapter');
|
|
897
|
+
if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
|
|
898
|
+
else { throw fmt("Adapter is either null or does not implement `findQuery` method", this); }
|
|
899
|
+
return array;
|
|
900
|
+
},
|
|
901
|
+
|
|
902
|
+
findAll: function(type) {
|
|
903
|
+
|
|
904
|
+
var typeMap = this.typeMapFor(type),
|
|
905
|
+
findAllCache = typeMap.findAllCache;
|
|
906
|
+
|
|
907
|
+
if (findAllCache) { return findAllCache; }
|
|
908
|
+
|
|
909
|
+
var array = DS.ModelArray.create({ type: type, content: Ember.A([]), store: this });
|
|
910
|
+
this.registerModelArray(array, type);
|
|
911
|
+
|
|
912
|
+
var adapter = get(this, '_adapter');
|
|
913
|
+
if (adapter && adapter.findAll) { adapter.findAll(this, type); }
|
|
914
|
+
|
|
915
|
+
typeMap.findAllCache = array;
|
|
916
|
+
return array;
|
|
917
|
+
},
|
|
918
|
+
|
|
919
|
+
filter: function(type, filter) {
|
|
920
|
+
var array = DS.FilteredModelArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
|
|
921
|
+
|
|
922
|
+
this.registerModelArray(array, type, filter);
|
|
923
|
+
|
|
924
|
+
return array;
|
|
925
|
+
},
|
|
926
|
+
|
|
927
|
+
// ............
|
|
928
|
+
// . UPDATING .
|
|
929
|
+
// ............
|
|
930
|
+
|
|
931
|
+
hashWasUpdated: function(type, clientId) {
|
|
932
|
+
var clientIdToHashMap = this.clientIdToHashMap(type);
|
|
933
|
+
var hash = clientIdToHashMap[clientId];
|
|
934
|
+
|
|
935
|
+
this.updateModelArrays(type, clientId, hash);
|
|
936
|
+
},
|
|
937
|
+
|
|
938
|
+
// ..............
|
|
939
|
+
// . PERSISTING .
|
|
940
|
+
// ..............
|
|
941
|
+
|
|
942
|
+
commit: function() {
|
|
943
|
+
get(this, 'defaultTransaction').commit();
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
didUpdateRecords: function(array, hashes) {
|
|
947
|
+
if (arguments.length === 2) {
|
|
948
|
+
array.forEach(function(model, idx) {
|
|
949
|
+
this.didUpdateRecord(model, hashes[idx]);
|
|
950
|
+
}, this);
|
|
951
|
+
} else {
|
|
952
|
+
array.forEach(function(model) {
|
|
953
|
+
this.didUpdateRecord(model);
|
|
954
|
+
}, this);
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
|
|
958
|
+
didUpdateRecord: function(model, hash) {
|
|
959
|
+
if (arguments.length === 2) {
|
|
960
|
+
var clientId = get(model, 'clientId');
|
|
961
|
+
var data = this.clientIdToHashMap(model.constructor);
|
|
962
|
+
|
|
963
|
+
data[clientId] = hash;
|
|
964
|
+
model.send('setData', hash);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
model.send('didCommit');
|
|
968
|
+
},
|
|
969
|
+
|
|
970
|
+
didDeleteRecords: function(array) {
|
|
971
|
+
array.forEach(function(model) {
|
|
972
|
+
model.send('didCommit');
|
|
973
|
+
});
|
|
974
|
+
},
|
|
975
|
+
|
|
976
|
+
didDeleteRecord: function(model) {
|
|
977
|
+
model.send('didCommit');
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
didCreateRecords: function(type, array, hashes) {
|
|
981
|
+
var id, clientId, primaryKey = getPath(type, 'proto.primaryKey');
|
|
982
|
+
|
|
983
|
+
var idToClientIdMap = this.idToClientIdMap(type);
|
|
984
|
+
var data = this.clientIdToHashMap(type);
|
|
985
|
+
var idList = this.idList(type);
|
|
986
|
+
|
|
987
|
+
for (var i=0, l=get(array, 'length'); i<l; i++) {
|
|
988
|
+
var model = array[i], hash = hashes[i];
|
|
989
|
+
id = hash[primaryKey];
|
|
990
|
+
clientId = get(model, 'clientId');
|
|
991
|
+
|
|
992
|
+
data[clientId] = hash;
|
|
993
|
+
model.send('setData', hash);
|
|
994
|
+
|
|
995
|
+
idToClientIdMap[id] = clientId;
|
|
996
|
+
idList.push(id);
|
|
997
|
+
|
|
998
|
+
model.send('didCommit');
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
|
|
1002
|
+
didCreateRecord: function(model, hash) {
|
|
1003
|
+
var type = model.constructor;
|
|
1004
|
+
|
|
1005
|
+
var id, clientId, primaryKey;
|
|
1006
|
+
|
|
1007
|
+
var idToClientIdMap = this.idToClientIdMap(type);
|
|
1008
|
+
var data = this.clientIdToHashMap(type);
|
|
1009
|
+
var idList = this.idList(type);
|
|
1010
|
+
|
|
1011
|
+
// The hash is optional, but if it is not provided, the client must have
|
|
1012
|
+
// provided a primary key.
|
|
1013
|
+
|
|
1014
|
+
primaryKey = getPath(type, 'proto.primaryKey');
|
|
1015
|
+
|
|
1016
|
+
// TODO: Make ember_assert more flexible and convert this into an ember_assert
|
|
1017
|
+
if (hash) {
|
|
1018
|
+
ember_assert("The server must provide a primary key: " + primaryKey, get(hash, primaryKey));
|
|
1019
|
+
} else {
|
|
1020
|
+
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));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// If a hash was provided, index it under the model's client ID
|
|
1024
|
+
// and update the model.
|
|
1025
|
+
if (arguments.length === 2) {
|
|
1026
|
+
id = hash[primaryKey];
|
|
1027
|
+
|
|
1028
|
+
data[clientId] = hash;
|
|
1029
|
+
set(model, 'data', hash);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
clientId = get(model, 'clientId');
|
|
1033
|
+
|
|
1034
|
+
idToClientIdMap[id] = clientId;
|
|
1035
|
+
idList.push(id);
|
|
1036
|
+
|
|
1037
|
+
model.send('didCommit');
|
|
1038
|
+
},
|
|
1039
|
+
|
|
1040
|
+
recordWasInvalid: function(record, errors) {
|
|
1041
|
+
record.send('becameInvalid', errors);
|
|
1042
|
+
},
|
|
1043
|
+
|
|
1044
|
+
// ................
|
|
1045
|
+
// . MODEL ARRAYS .
|
|
1046
|
+
// ................
|
|
1047
|
+
|
|
1048
|
+
registerModelArray: function(array, type, filter) {
|
|
1049
|
+
var modelArrays = get(this, 'modelArrays');
|
|
1050
|
+
|
|
1051
|
+
modelArrays.push(array);
|
|
1052
|
+
|
|
1053
|
+
this.updateModelArrayFilter(array, type, filter);
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
createModelArray: function(type, clientIds) {
|
|
1057
|
+
var array = DS.ModelArray.create({ type: type, content: clientIds, store: this });
|
|
1058
|
+
|
|
1059
|
+
clientIds.forEach(function(clientId) {
|
|
1060
|
+
var modelArrays = this.modelArraysForClientId(clientId);
|
|
1061
|
+
modelArrays.add(array);
|
|
1062
|
+
}, this);
|
|
1063
|
+
|
|
1064
|
+
return array;
|
|
1065
|
+
},
|
|
1066
|
+
|
|
1067
|
+
updateModelArrayFilter: function(array, type, filter) {
|
|
1068
|
+
var data = this.clientIdToHashMap(type);
|
|
1069
|
+
var allClientIds = this.clientIdList(type), clientId, hash;
|
|
1070
|
+
|
|
1071
|
+
for (var i=0, l=allClientIds.length; i<l; i++) {
|
|
1072
|
+
clientId = allClientIds[i];
|
|
1073
|
+
|
|
1074
|
+
hash = data[clientId];
|
|
1075
|
+
|
|
1076
|
+
if (hash) {
|
|
1077
|
+
this.updateModelArray(array, filter, type, clientId, hash);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
|
|
1082
|
+
updateModelArrays: function(type, clientId, hash) {
|
|
1083
|
+
var modelArrays = get(this, 'modelArrays'),
|
|
1084
|
+
modelArrayType, filter;
|
|
1085
|
+
|
|
1086
|
+
modelArrays.forEach(function(array) {
|
|
1087
|
+
modelArrayType = get(array, 'type');
|
|
1088
|
+
filter = get(array, 'filterFunction');
|
|
1089
|
+
|
|
1090
|
+
if (type !== modelArrayType) { return; }
|
|
1091
|
+
|
|
1092
|
+
this.updateModelArray(array, filter, type, clientId, hash);
|
|
1093
|
+
}, this);
|
|
1094
|
+
},
|
|
1095
|
+
|
|
1096
|
+
updateModelArray: function(array, filter, type, clientId, hash) {
|
|
1097
|
+
var shouldBeInArray;
|
|
1098
|
+
|
|
1099
|
+
if (!filter) {
|
|
1100
|
+
shouldBeInArray = true;
|
|
1101
|
+
} else {
|
|
1102
|
+
shouldBeInArray = filter(hash);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
var content = get(array, 'content');
|
|
1106
|
+
var alreadyInArray = content.indexOf(clientId) !== -1;
|
|
1107
|
+
|
|
1108
|
+
var modelArrays = this.modelArraysForClientId(clientId);
|
|
1109
|
+
|
|
1110
|
+
if (shouldBeInArray && !alreadyInArray) {
|
|
1111
|
+
modelArrays.add(array);
|
|
1112
|
+
content.pushObject(clientId);
|
|
1113
|
+
} else if (!shouldBeInArray && alreadyInArray) {
|
|
1114
|
+
modelArrays.remove(array);
|
|
1115
|
+
content.removeObject(clientId);
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
|
|
1119
|
+
removeFromModelArrays: function(model) {
|
|
1120
|
+
var clientId = get(model, 'clientId');
|
|
1121
|
+
var modelArrays = this.modelArraysForClientId(clientId);
|
|
1122
|
+
|
|
1123
|
+
modelArrays.forEach(function(array) {
|
|
1124
|
+
var content = get(array, 'content');
|
|
1125
|
+
content.removeObject(clientId);
|
|
1126
|
+
});
|
|
1127
|
+
},
|
|
1128
|
+
|
|
1129
|
+
// ............
|
|
1130
|
+
// . TYPE MAP .
|
|
1131
|
+
// ............
|
|
1132
|
+
|
|
1133
|
+
typeMapFor: function(type) {
|
|
1134
|
+
var ids = get(this, '_typeMap');
|
|
1135
|
+
var guidForType = Ember.guidFor(type);
|
|
1136
|
+
|
|
1137
|
+
var typeMap = ids[guidForType];
|
|
1138
|
+
|
|
1139
|
+
if (typeMap) {
|
|
1140
|
+
return typeMap;
|
|
1141
|
+
} else {
|
|
1142
|
+
return (ids[guidForType] =
|
|
1143
|
+
{
|
|
1144
|
+
idToCid: {},
|
|
1145
|
+
idList: [],
|
|
1146
|
+
cidList: [],
|
|
1147
|
+
cidToHash: {}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
|
|
1152
|
+
idToClientIdMap: function(type) {
|
|
1153
|
+
return this.typeMapFor(type).idToCid;
|
|
1154
|
+
},
|
|
1155
|
+
|
|
1156
|
+
idList: function(type) {
|
|
1157
|
+
return this.typeMapFor(type).idList;
|
|
1158
|
+
},
|
|
1159
|
+
|
|
1160
|
+
clientIdList: function(type) {
|
|
1161
|
+
return this.typeMapFor(type).cidList;
|
|
1162
|
+
},
|
|
1163
|
+
|
|
1164
|
+
clientIdToHashMap: function(type) {
|
|
1165
|
+
return this.typeMapFor(type).cidToHash;
|
|
1166
|
+
},
|
|
1167
|
+
|
|
1168
|
+
/** @private
|
|
1169
|
+
|
|
1170
|
+
For a given type and id combination, returns the client id used by the store.
|
|
1171
|
+
If no client id has been assigned yet, `undefined` is returned.
|
|
1172
|
+
|
|
1173
|
+
@param {DS.Model} type
|
|
1174
|
+
@param {String|Number} id
|
|
1175
|
+
*/
|
|
1176
|
+
clientIdForId: function(type, id) {
|
|
1177
|
+
return this.typeMapFor(type).idToCid[id];
|
|
1178
|
+
},
|
|
1179
|
+
|
|
1180
|
+
idForHash: function(type, hash) {
|
|
1181
|
+
var primaryKey = getPath(type, 'proto.primaryKey');
|
|
1182
|
+
|
|
1183
|
+
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
|
1184
|
+
return hash[primaryKey];
|
|
1185
|
+
},
|
|
1186
|
+
|
|
1187
|
+
// ................
|
|
1188
|
+
// . LOADING DATA .
|
|
1189
|
+
// ................
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
Load a new data hash into the store for a given id and type combination.
|
|
1193
|
+
If data for that model had been loaded previously, the new information
|
|
1194
|
+
overwrites the old.
|
|
1195
|
+
|
|
1196
|
+
If the model you are loading data for has outstanding changes that have not
|
|
1197
|
+
yet been saved, an exception will be thrown.
|
|
1198
|
+
|
|
1199
|
+
@param {DS.Model} type
|
|
1200
|
+
@param {String|Number} id
|
|
1201
|
+
@param {Object} hash the data hash to load
|
|
1202
|
+
*/
|
|
1203
|
+
load: function(type, id, hash) {
|
|
1204
|
+
if (hash === undefined) {
|
|
1205
|
+
hash = id;
|
|
1206
|
+
var primaryKey = getPath(type, 'proto.primaryKey');
|
|
1207
|
+
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
|
1208
|
+
id = hash[primaryKey];
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
var data = this.clientIdToHashMap(type);
|
|
1212
|
+
var models = get(this, 'models');
|
|
1213
|
+
|
|
1214
|
+
var clientId = this.clientIdForId(type, id);
|
|
1215
|
+
|
|
1216
|
+
if (clientId !== undefined) {
|
|
1217
|
+
data[clientId] = hash;
|
|
1218
|
+
|
|
1219
|
+
var model = models[clientId];
|
|
1220
|
+
if (model) {
|
|
1221
|
+
model.send('setData', hash);
|
|
1222
|
+
}
|
|
1223
|
+
} else {
|
|
1224
|
+
clientId = this.pushHash(hash, id, type);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
this.updateModelArrays(type, clientId, hash);
|
|
1228
|
+
|
|
1229
|
+
return { id: id, clientId: clientId };
|
|
1230
|
+
},
|
|
1231
|
+
|
|
1232
|
+
loadMany: function(type, ids, hashes) {
|
|
1233
|
+
var clientIds = Ember.A([]);
|
|
1234
|
+
|
|
1235
|
+
if (hashes === undefined) {
|
|
1236
|
+
hashes = ids;
|
|
1237
|
+
ids = [];
|
|
1238
|
+
var primaryKey = getPath(type, 'proto.primaryKey');
|
|
1239
|
+
|
|
1240
|
+
ids = hashes.map(function(hash) {
|
|
1241
|
+
ember_assert("A data hash was loaded for a model of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", !!hash[primaryKey]);
|
|
1242
|
+
return hash[primaryKey];
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
for (var i=0, l=get(ids, 'length'); i<l; i++) {
|
|
1247
|
+
var loaded = this.load(type, ids[i], hashes[i]);
|
|
1248
|
+
clientIds.pushObject(loaded.clientId);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return { clientIds: clientIds, ids: ids };
|
|
1252
|
+
},
|
|
1253
|
+
|
|
1254
|
+
/** @private
|
|
1255
|
+
|
|
1256
|
+
Stores a data hash for the specified type and id combination and returns
|
|
1257
|
+
the client id.
|
|
1258
|
+
|
|
1259
|
+
@param {Object} hash
|
|
1260
|
+
@param {String|Number} id
|
|
1261
|
+
@param {DS.Model} type
|
|
1262
|
+
@returns {Number}
|
|
1263
|
+
*/
|
|
1264
|
+
pushHash: function(hash, id, type) {
|
|
1265
|
+
var idToClientIdMap = this.idToClientIdMap(type);
|
|
1266
|
+
var clientIdList = this.clientIdList(type);
|
|
1267
|
+
var idList = this.idList(type);
|
|
1268
|
+
var data = this.clientIdToHashMap(type);
|
|
1269
|
+
|
|
1270
|
+
var clientId = this.incrementProperty('clientIdCounter');
|
|
1271
|
+
|
|
1272
|
+
data[clientId] = hash;
|
|
1273
|
+
|
|
1274
|
+
// if we're creating an item, this process will be done
|
|
1275
|
+
// later, once the object has been persisted.
|
|
1276
|
+
if (id) {
|
|
1277
|
+
idToClientIdMap[id] = clientId;
|
|
1278
|
+
idList.push(id);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
clientIdList.push(clientId);
|
|
1282
|
+
|
|
1283
|
+
return clientId;
|
|
1284
|
+
},
|
|
1285
|
+
|
|
1286
|
+
// .........................
|
|
1287
|
+
// . MODEL MATERIALIZATION .
|
|
1288
|
+
// .........................
|
|
1289
|
+
|
|
1290
|
+
materializeRecord: function(type, clientId) {
|
|
1291
|
+
var model;
|
|
1292
|
+
|
|
1293
|
+
get(this, 'models')[clientId] = model = type._create({ store: this, clientId: clientId });
|
|
1294
|
+
set(model, 'clientId', clientId);
|
|
1295
|
+
model.send('loadingData');
|
|
1296
|
+
return model;
|
|
1297
|
+
},
|
|
1298
|
+
|
|
1299
|
+
destroy: function() {
|
|
1300
|
+
if (get(DS, 'defaultStore') === this) {
|
|
1301
|
+
set(DS, 'defaultStore', null);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return this._super();
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
})({});
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
(function(exports) {
|
|
1313
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath, guidFor = Ember.guidFor;
|
|
1314
|
+
|
|
1315
|
+
var stateProperty = Ember.computed(function(key) {
|
|
1316
|
+
var parent = get(this, 'parentState');
|
|
1317
|
+
if (parent) {
|
|
1318
|
+
return get(parent, key);
|
|
1319
|
+
}
|
|
1320
|
+
}).property();
|
|
1321
|
+
|
|
1322
|
+
var isEmptyObject = function(object) {
|
|
1323
|
+
for (var name in object) {
|
|
1324
|
+
if (object.hasOwnProperty(name)) { return false; }
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
return true;
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
DS.State = Ember.State.extend({
|
|
1331
|
+
isLoaded: stateProperty,
|
|
1332
|
+
isDirty: stateProperty,
|
|
1333
|
+
isSaving: stateProperty,
|
|
1334
|
+
isDeleted: stateProperty,
|
|
1335
|
+
isError: stateProperty,
|
|
1336
|
+
isNew: stateProperty,
|
|
1337
|
+
isValid: stateProperty,
|
|
1338
|
+
isPending: stateProperty,
|
|
1339
|
+
|
|
1340
|
+
// For states that are substates of a
|
|
1341
|
+
// DirtyState (updated or created), it is
|
|
1342
|
+
// useful to be able to determine which
|
|
1343
|
+
// type of dirty state it is.
|
|
1344
|
+
dirtyType: stateProperty
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
var isEmptyObject = function(obj) {
|
|
1348
|
+
for (var prop in obj) {
|
|
1349
|
+
if (!obj.hasOwnProperty(prop)) { continue; }
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
return true;
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
var setProperty = function(manager, context) {
|
|
1357
|
+
var key = context.key, value = context.value;
|
|
1358
|
+
|
|
1359
|
+
var model = get(manager, 'model'),
|
|
1360
|
+
data = get(model, 'data');
|
|
1361
|
+
|
|
1362
|
+
data[key] = value;
|
|
1363
|
+
|
|
1364
|
+
// At the end of the run loop, notify model arrays that
|
|
1365
|
+
// this record has changed so they can re-evaluate its contents
|
|
1366
|
+
// to determine membership.
|
|
1367
|
+
Ember.run.once(model, model.notifyHashWasUpdated);
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// The waitingOn event shares common functionality
|
|
1371
|
+
// between the different dirty states, but each is
|
|
1372
|
+
// treated slightly differently. This method is exposed
|
|
1373
|
+
// so that each implementation can invoke the common
|
|
1374
|
+
// behavior, and then implement the behavior specific
|
|
1375
|
+
// to the state.
|
|
1376
|
+
var waitingOn = function(manager, object) {
|
|
1377
|
+
var model = get(manager, 'model'),
|
|
1378
|
+
pendingQueue = get(model, 'pendingQueue'),
|
|
1379
|
+
objectGuid = guidFor(object);
|
|
1380
|
+
|
|
1381
|
+
var observer = function() {
|
|
1382
|
+
if (get(object, 'id')) {
|
|
1383
|
+
manager.send('doneWaitingOn', object);
|
|
1384
|
+
Ember.removeObserver(object, 'id', observer);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
pendingQueue[objectGuid] = [object, observer];
|
|
1389
|
+
Ember.addObserver(object, 'id', observer);
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
// Implementation notes:
|
|
1393
|
+
//
|
|
1394
|
+
// Each state has a boolean value for all of the following flags:
|
|
1395
|
+
//
|
|
1396
|
+
// * isLoaded: The record has a populated `data` property. When a
|
|
1397
|
+
// record is loaded via `store.find`, `isLoaded` is false
|
|
1398
|
+
// until the adapter sets it. When a record is created locally,
|
|
1399
|
+
// its `isLoaded` property is always true.
|
|
1400
|
+
// * isDirty: The record has local changes that have not yet been
|
|
1401
|
+
// saved by the adapter. This includes records that have been
|
|
1402
|
+
// created (but not yet saved) or deleted.
|
|
1403
|
+
// * isSaving: The record's transaction has been committed, but
|
|
1404
|
+
// the adapter has not yet acknowledged that the changes have
|
|
1405
|
+
// been persisted to the backend.
|
|
1406
|
+
// * isDeleted: The record was marked for deletion. When `isDeleted`
|
|
1407
|
+
// is true and `isDirty` is true, the record is deleted locally
|
|
1408
|
+
// but the deletion was not yet persisted. When `isSaving` is
|
|
1409
|
+
// true, the change is in-flight. When both `isDirty` and
|
|
1410
|
+
// `isSaving` are false, the change has persisted.
|
|
1411
|
+
// * isError: The adapter reported that it was unable to save
|
|
1412
|
+
// local changes to the backend. This may also result in the
|
|
1413
|
+
// record having its `isValid` property become false if the
|
|
1414
|
+
// adapter reported that server-side validations failed.
|
|
1415
|
+
// * isNew: The record was created on the client and the adapter
|
|
1416
|
+
// did not yet report that it was successfully saved.
|
|
1417
|
+
// * isValid: No client-side validations have failed and the
|
|
1418
|
+
// adapter did not report any server-side validation failures.
|
|
1419
|
+
// * isPending: A record `isPending` when it belongs to an
|
|
1420
|
+
// association on another record and that record has not been
|
|
1421
|
+
// saved. A record in this state cannot be saved because it
|
|
1422
|
+
// lacks a "foreign key" that will be supplied by its parent
|
|
1423
|
+
// association when the parent record has been created. When
|
|
1424
|
+
// the adapter reports that the parent has saved, the
|
|
1425
|
+
// `isPending` property on all children will become `false`
|
|
1426
|
+
// and the transaction will try to commit the records.
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
// The dirty state is a abstract state whose functionality is
|
|
1430
|
+
// shared between the `created` and `updated` states.
|
|
1431
|
+
//
|
|
1432
|
+
// The deleted state shares the `isDirty` flag with the
|
|
1433
|
+
// subclasses of `DirtyState`, but with a very different
|
|
1434
|
+
// implementation.
|
|
1435
|
+
var DirtyState = DS.State.extend({
|
|
1436
|
+
initialState: 'uncommitted',
|
|
1437
|
+
|
|
1438
|
+
// FLAGS
|
|
1439
|
+
isDirty: true,
|
|
1440
|
+
|
|
1441
|
+
// SUBSTATES
|
|
1442
|
+
|
|
1443
|
+
// When a record first becomes dirty, it is `uncommitted`.
|
|
1444
|
+
// This means that there are local pending changes,
|
|
1445
|
+
// but they have not yet begun to be saved.
|
|
1446
|
+
uncommitted: DS.State.extend({
|
|
1447
|
+
// TRANSITIONS
|
|
1448
|
+
enter: function(manager) {
|
|
1449
|
+
var dirtyType = get(this, 'dirtyType'),
|
|
1450
|
+
model = get(manager, 'model');
|
|
1451
|
+
|
|
1452
|
+
model.withTransaction(function (t) {
|
|
1453
|
+
t.modelBecameDirty(dirtyType, model);
|
|
1454
|
+
});
|
|
1455
|
+
},
|
|
1456
|
+
|
|
1457
|
+
exit: function(manager) {
|
|
1458
|
+
var model = get(manager, 'model');
|
|
1459
|
+
manager.send('invokeLifecycleCallbacks', model);
|
|
1460
|
+
},
|
|
1461
|
+
|
|
1462
|
+
// EVENTS
|
|
1463
|
+
setProperty: setProperty,
|
|
1464
|
+
|
|
1465
|
+
deleteRecord: function(manager) {
|
|
1466
|
+
manager.goToState('deleted');
|
|
1467
|
+
},
|
|
1468
|
+
|
|
1469
|
+
waitingOn: function(manager, object) {
|
|
1470
|
+
waitingOn(manager, object);
|
|
1471
|
+
manager.goToState('pending');
|
|
1472
|
+
},
|
|
1473
|
+
|
|
1474
|
+
willCommit: function(manager) {
|
|
1475
|
+
manager.goToState('inFlight');
|
|
1476
|
+
}
|
|
1477
|
+
}),
|
|
1478
|
+
|
|
1479
|
+
// Once a record has been handed off to the adapter to be
|
|
1480
|
+
// saved, it is in the 'in flight' state. Changes to the
|
|
1481
|
+
// record cannot be made during this window.
|
|
1482
|
+
inFlight: DS.State.extend({
|
|
1483
|
+
// FLAGS
|
|
1484
|
+
isSaving: true,
|
|
1485
|
+
|
|
1486
|
+
// TRANSITIONS
|
|
1487
|
+
enter: function(manager) {
|
|
1488
|
+
var dirtyType = get(this, 'dirtyType'),
|
|
1489
|
+
model = get(manager, 'model');
|
|
1490
|
+
|
|
1491
|
+
model.withTransaction(function (t) {
|
|
1492
|
+
t.modelBecameClean(dirtyType, model);
|
|
1493
|
+
});
|
|
1494
|
+
},
|
|
1495
|
+
|
|
1496
|
+
// EVENTS
|
|
1497
|
+
didCommit: function(manager) {
|
|
1498
|
+
manager.goToState('loaded');
|
|
1499
|
+
},
|
|
1500
|
+
|
|
1501
|
+
becameInvalid: function(manager, errors) {
|
|
1502
|
+
var model = get(manager, 'model');
|
|
1503
|
+
|
|
1504
|
+
set(model, 'errors', errors);
|
|
1505
|
+
manager.goToState('invalid');
|
|
1506
|
+
},
|
|
1507
|
+
|
|
1508
|
+
setData: function(manager, hash) {
|
|
1509
|
+
var model = get(manager, 'model');
|
|
1510
|
+
set(model, 'data', hash);
|
|
1511
|
+
}
|
|
1512
|
+
}),
|
|
1513
|
+
|
|
1514
|
+
// If a record becomes associated with a newly created
|
|
1515
|
+
// parent record, it will be `pending` until the parent
|
|
1516
|
+
// record has successfully persisted. Once this happens,
|
|
1517
|
+
// this record can use the parent's primary key as its
|
|
1518
|
+
// foreign key.
|
|
1519
|
+
//
|
|
1520
|
+
// If the record's transaction had already started to
|
|
1521
|
+
// commit, the record will transition to the `inFlight`
|
|
1522
|
+
// state. If it had not, the record will transition to
|
|
1523
|
+
// the `uncommitted` state.
|
|
1524
|
+
pending: DS.State.extend({
|
|
1525
|
+
initialState: 'uncommitted',
|
|
1526
|
+
|
|
1527
|
+
// FLAGS
|
|
1528
|
+
isPending: true,
|
|
1529
|
+
|
|
1530
|
+
// SUBSTATES
|
|
1531
|
+
|
|
1532
|
+
// A pending record whose transaction has not yet
|
|
1533
|
+
// started to commit is in this state.
|
|
1534
|
+
uncommitted: DS.State.extend({
|
|
1535
|
+
// EVENTS
|
|
1536
|
+
setProperty: setProperty,
|
|
1537
|
+
|
|
1538
|
+
deleteRecord: function(manager) {
|
|
1539
|
+
var model = get(manager, 'model'),
|
|
1540
|
+
pendingQueue = get(model, 'pendingQueue'),
|
|
1541
|
+
tuple;
|
|
1542
|
+
|
|
1543
|
+
// since we are leaving the pending state, remove any
|
|
1544
|
+
// observers we have registered on other records.
|
|
1545
|
+
for (var prop in pendingQueue) {
|
|
1546
|
+
if (!pendingQueue.hasOwnProperty(prop)) { continue; }
|
|
1547
|
+
|
|
1548
|
+
tuple = pendingQueue[prop];
|
|
1549
|
+
Ember.removeObserver(tuple[0], 'id', tuple[1]);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
manager.goToState('deleted');
|
|
1553
|
+
},
|
|
1554
|
+
|
|
1555
|
+
willCommit: function(manager) {
|
|
1556
|
+
manager.goToState('committing');
|
|
1557
|
+
},
|
|
1558
|
+
|
|
1559
|
+
doneWaitingOn: function(manager, object) {
|
|
1560
|
+
var model = get(manager, 'model'),
|
|
1561
|
+
pendingQueue = get(model, 'pendingQueue'),
|
|
1562
|
+
objectGuid = guidFor(object);
|
|
1563
|
+
|
|
1564
|
+
delete pendingQueue[objectGuid];
|
|
1565
|
+
|
|
1566
|
+
if (isEmptyObject(pendingQueue)) {
|
|
1567
|
+
manager.send('doneWaiting');
|
|
1568
|
+
}
|
|
1569
|
+
},
|
|
1570
|
+
|
|
1571
|
+
doneWaiting: function(manager) {
|
|
1572
|
+
var dirtyType = get(this, 'dirtyType');
|
|
1573
|
+
manager.goToState(dirtyType + '.uncommitted');
|
|
1574
|
+
}
|
|
1575
|
+
}),
|
|
1576
|
+
|
|
1577
|
+
// A pending record whose transaction has started
|
|
1578
|
+
// to commit is in this state. Since it has not yet
|
|
1579
|
+
// been sent to the adapter, it is not `inFlight`
|
|
1580
|
+
// until all of its dependencies have been committed.
|
|
1581
|
+
committing: DS.State.extend({
|
|
1582
|
+
// FLAGS
|
|
1583
|
+
isSaving: true,
|
|
1584
|
+
|
|
1585
|
+
// EVENTS
|
|
1586
|
+
doneWaitingOn: function(manager, object) {
|
|
1587
|
+
var model = get(manager, 'model'),
|
|
1588
|
+
pendingQueue = get(model, 'pendingQueue'),
|
|
1589
|
+
objectGuid = guidFor(object);
|
|
1590
|
+
|
|
1591
|
+
delete pendingQueue[objectGuid];
|
|
1592
|
+
|
|
1593
|
+
if (isEmptyObject(pendingQueue)) {
|
|
1594
|
+
manager.send('doneWaiting');
|
|
1595
|
+
}
|
|
1596
|
+
},
|
|
1597
|
+
|
|
1598
|
+
doneWaiting: function(manager) {
|
|
1599
|
+
var dirtyType = get(this, 'dirtyType');
|
|
1600
|
+
manager.goToState(dirtyType + '.inFlight');
|
|
1601
|
+
}
|
|
1602
|
+
})
|
|
1603
|
+
}),
|
|
1604
|
+
|
|
1605
|
+
// A record is in the `invalid` state when its client-side
|
|
1606
|
+
// invalidations have failed, or if the adapter has indicated
|
|
1607
|
+
// the the record failed server-side invalidations.
|
|
1608
|
+
invalid: DS.State.extend({
|
|
1609
|
+
// FLAGS
|
|
1610
|
+
isValid: false,
|
|
1611
|
+
|
|
1612
|
+
// EVENTS
|
|
1613
|
+
deleteRecord: function(manager) {
|
|
1614
|
+
manager.goToState('deleted');
|
|
1615
|
+
},
|
|
1616
|
+
|
|
1617
|
+
setProperty: function(manager, context) {
|
|
1618
|
+
setProperty(manager, context);
|
|
1619
|
+
|
|
1620
|
+
var model = get(manager, 'model'),
|
|
1621
|
+
errors = get(model, 'errors'),
|
|
1622
|
+
key = context.key;
|
|
1623
|
+
|
|
1624
|
+
delete errors[key];
|
|
1625
|
+
|
|
1626
|
+
if (isEmptyObject(errors)) {
|
|
1627
|
+
manager.send('becameValid');
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1631
|
+
becameValid: function(manager) {
|
|
1632
|
+
manager.goToState('uncommitted');
|
|
1633
|
+
}
|
|
1634
|
+
})
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
var states = {
|
|
1638
|
+
rootState: Ember.State.create({
|
|
1639
|
+
// FLAGS
|
|
1640
|
+
isLoaded: false,
|
|
1641
|
+
isDirty: false,
|
|
1642
|
+
isSaving: false,
|
|
1643
|
+
isDeleted: false,
|
|
1644
|
+
isError: false,
|
|
1645
|
+
isNew: false,
|
|
1646
|
+
isValid: true,
|
|
1647
|
+
isPending: false,
|
|
1648
|
+
|
|
1649
|
+
// SUBSTATES
|
|
1650
|
+
|
|
1651
|
+
// A record begins its lifecycle in the `empty` state.
|
|
1652
|
+
// If its data will come from the adapter, it will
|
|
1653
|
+
// transition into the `loading` state. Otherwise, if
|
|
1654
|
+
// the record is being created on the client, it will
|
|
1655
|
+
// transition into the `created` state.
|
|
1656
|
+
empty: DS.State.create({
|
|
1657
|
+
// EVENTS
|
|
1658
|
+
loadingData: function(manager) {
|
|
1659
|
+
manager.goToState('loading');
|
|
1660
|
+
},
|
|
1661
|
+
|
|
1662
|
+
setData: function(manager, hash) {
|
|
1663
|
+
var model = get(manager, 'model');
|
|
1664
|
+
set(model, 'data', hash);
|
|
1665
|
+
manager.goToState('loaded.created');
|
|
1666
|
+
}
|
|
1667
|
+
}),
|
|
1668
|
+
|
|
1669
|
+
// A record enters this state when the store askes
|
|
1670
|
+
// the adapter for its data. It remains in this state
|
|
1671
|
+
// until the adapter provides the requested data.
|
|
1672
|
+
//
|
|
1673
|
+
// Usually, this process is asynchronous, using an
|
|
1674
|
+
// XHR to retrieve the data.
|
|
1675
|
+
loading: DS.State.create({
|
|
1676
|
+
// TRANSITIONS
|
|
1677
|
+
exit: function(manager) {
|
|
1678
|
+
var model = get(manager, 'model');
|
|
1679
|
+
model.didLoad();
|
|
1680
|
+
},
|
|
1681
|
+
|
|
1682
|
+
// EVENTS
|
|
1683
|
+
setData: function(manager, data) {
|
|
1684
|
+
var model = get(manager, 'model');
|
|
1685
|
+
|
|
1686
|
+
model.beginPropertyChanges();
|
|
1687
|
+
set(model, 'data', data);
|
|
1688
|
+
|
|
1689
|
+
if (data !== null) {
|
|
1690
|
+
manager.send('loadedData');
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
model.endPropertyChanges();
|
|
1694
|
+
},
|
|
1695
|
+
|
|
1696
|
+
loadedData: function(manager) {
|
|
1697
|
+
manager.goToState('loaded');
|
|
1698
|
+
}
|
|
1699
|
+
}),
|
|
1700
|
+
|
|
1701
|
+
// A record enters this state when its data is populated.
|
|
1702
|
+
// Most of a record's lifecycle is spent inside substates
|
|
1703
|
+
// of the `loaded` state.
|
|
1704
|
+
loaded: DS.State.create({
|
|
1705
|
+
initialState: 'saved',
|
|
1706
|
+
|
|
1707
|
+
// FLAGS
|
|
1708
|
+
isLoaded: true,
|
|
1709
|
+
|
|
1710
|
+
// SUBSTATES
|
|
1711
|
+
|
|
1712
|
+
// If there are no local changes to a record, it remains
|
|
1713
|
+
// in the `saved` state.
|
|
1714
|
+
saved: DS.State.create({
|
|
1715
|
+
// EVENTS
|
|
1716
|
+
setProperty: function(manager, context) {
|
|
1717
|
+
setProperty(manager, context);
|
|
1718
|
+
manager.goToState('updated');
|
|
1719
|
+
},
|
|
1720
|
+
|
|
1721
|
+
deleteRecord: function(manager) {
|
|
1722
|
+
manager.goToState('deleted');
|
|
1723
|
+
},
|
|
1724
|
+
|
|
1725
|
+
waitingOn: function(manager, object) {
|
|
1726
|
+
waitingOn(manager, object);
|
|
1727
|
+
manager.goToState('updated.pending');
|
|
1728
|
+
}
|
|
1729
|
+
}),
|
|
1730
|
+
|
|
1731
|
+
// A record is in this state after it has been locally
|
|
1732
|
+
// created but before the adapter has indicated that
|
|
1733
|
+
// it has been saved.
|
|
1734
|
+
created: DirtyState.create({
|
|
1735
|
+
dirtyType: 'created',
|
|
1736
|
+
|
|
1737
|
+
// FLAGS
|
|
1738
|
+
isNew: true,
|
|
1739
|
+
|
|
1740
|
+
// EVENTS
|
|
1741
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
|
1742
|
+
model.didCreate();
|
|
1743
|
+
}
|
|
1744
|
+
}),
|
|
1745
|
+
|
|
1746
|
+
// A record is in this state if it has already been
|
|
1747
|
+
// saved to the server, but there are new local changes
|
|
1748
|
+
// that have not yet been saved.
|
|
1749
|
+
updated: DirtyState.create({
|
|
1750
|
+
dirtyType: 'updated',
|
|
1751
|
+
|
|
1752
|
+
// EVENTS
|
|
1753
|
+
invokeLifecycleCallbacks: function(manager, model) {
|
|
1754
|
+
model.didUpdate();
|
|
1755
|
+
}
|
|
1756
|
+
})
|
|
1757
|
+
}),
|
|
1758
|
+
|
|
1759
|
+
// A record is in this state if it was deleted from the store.
|
|
1760
|
+
deleted: DS.State.create({
|
|
1761
|
+
// FLAGS
|
|
1762
|
+
isDeleted: true,
|
|
1763
|
+
isLoaded: true,
|
|
1764
|
+
isDirty: true,
|
|
1765
|
+
|
|
1766
|
+
// TRANSITIONS
|
|
1767
|
+
enter: function(manager) {
|
|
1768
|
+
var model = get(manager, 'model');
|
|
1769
|
+
var store = get(model, 'store');
|
|
1770
|
+
|
|
1771
|
+
if (store) {
|
|
1772
|
+
store.removeFromModelArrays(model);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
model.withTransaction(function(t) {
|
|
1776
|
+
t.modelBecameDirty('deleted', model);
|
|
1777
|
+
});
|
|
1778
|
+
},
|
|
1779
|
+
|
|
1780
|
+
// SUBSTATES
|
|
1781
|
+
|
|
1782
|
+
// When a record is deleted, it enters the `start`
|
|
1783
|
+
// state. It will exit this state when the record's
|
|
1784
|
+
// transaction starts to commit.
|
|
1785
|
+
start: DS.State.create({
|
|
1786
|
+
willCommit: function(manager) {
|
|
1787
|
+
manager.goToState('inFlight');
|
|
1788
|
+
}
|
|
1789
|
+
}),
|
|
1790
|
+
|
|
1791
|
+
// After a record's transaction is committing, but
|
|
1792
|
+
// before the adapter indicates that the deletion
|
|
1793
|
+
// has saved to the server, a record is in the
|
|
1794
|
+
// `inFlight` substate of `deleted`.
|
|
1795
|
+
inFlight: DS.State.create({
|
|
1796
|
+
// FLAGS
|
|
1797
|
+
isSaving: true,
|
|
1798
|
+
|
|
1799
|
+
// TRANSITIONS
|
|
1800
|
+
exit: function(stateManager) {
|
|
1801
|
+
var model = get(stateManager, 'model');
|
|
1802
|
+
|
|
1803
|
+
model.withTransaction(function(t) {
|
|
1804
|
+
t.modelBecameClean('deleted', model);
|
|
1805
|
+
});
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1808
|
+
// EVENTS
|
|
1809
|
+
didCommit: function(manager) {
|
|
1810
|
+
manager.goToState('saved');
|
|
1811
|
+
}
|
|
1812
|
+
}),
|
|
1813
|
+
|
|
1814
|
+
// Once the adapter indicates that the deletion has
|
|
1815
|
+
// been saved, the record enters the `saved` substate
|
|
1816
|
+
// of `deleted`.
|
|
1817
|
+
saved: DS.State.create({
|
|
1818
|
+
isDirty: false
|
|
1819
|
+
})
|
|
1820
|
+
}),
|
|
1821
|
+
|
|
1822
|
+
// If the adapter indicates that there was an unknown
|
|
1823
|
+
// error saving a record, the record enters the `error`
|
|
1824
|
+
// state.
|
|
1825
|
+
error: DS.State.create({
|
|
1826
|
+
isError: true
|
|
1827
|
+
})
|
|
1828
|
+
})
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
DS.StateManager = Ember.StateManager.extend({
|
|
1832
|
+
model: null,
|
|
1833
|
+
initialState: 'rootState',
|
|
1834
|
+
states: states
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
})({});
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
(function(exports) {
|
|
1841
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
|
1842
|
+
|
|
1843
|
+
var retrieveFromCurrentState = Ember.computed(function(key) {
|
|
1844
|
+
return get(getPath(this, 'stateManager.currentState'), key);
|
|
1845
|
+
}).property('stateManager.currentState').cacheable();
|
|
1846
|
+
|
|
1847
|
+
DS.Model = Ember.Object.extend({
|
|
1848
|
+
isLoaded: retrieveFromCurrentState,
|
|
1849
|
+
isDirty: retrieveFromCurrentState,
|
|
1850
|
+
isSaving: retrieveFromCurrentState,
|
|
1851
|
+
isDeleted: retrieveFromCurrentState,
|
|
1852
|
+
isError: retrieveFromCurrentState,
|
|
1853
|
+
isNew: retrieveFromCurrentState,
|
|
1854
|
+
isPending: retrieveFromCurrentState,
|
|
1855
|
+
isValid: retrieveFromCurrentState,
|
|
1856
|
+
|
|
1857
|
+
clientId: null,
|
|
1858
|
+
|
|
1859
|
+
// because unknownProperty is used, any internal property
|
|
1860
|
+
// must be initialized here.
|
|
1861
|
+
primaryKey: 'id',
|
|
1862
|
+
id: Ember.computed(function(key, value) {
|
|
1863
|
+
var primaryKey = get(this, 'primaryKey'),
|
|
1864
|
+
data = get(this, 'data');
|
|
1865
|
+
|
|
1866
|
+
if (arguments.length === 2) {
|
|
1867
|
+
set(data, primaryKey, value);
|
|
1868
|
+
return value;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
return data && get(data, primaryKey);
|
|
1872
|
+
}).property('primaryKey', 'data'),
|
|
1873
|
+
|
|
1874
|
+
data: null,
|
|
1875
|
+
pendingQueue: null,
|
|
1876
|
+
transaction: null,
|
|
1877
|
+
errors: null,
|
|
1878
|
+
|
|
1879
|
+
didLoad: Ember.K,
|
|
1880
|
+
didUpdate: Ember.K,
|
|
1881
|
+
didCreate: Ember.K,
|
|
1882
|
+
|
|
1883
|
+
init: function() {
|
|
1884
|
+
var stateManager = DS.StateManager.create({
|
|
1885
|
+
model: this
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
set(this, 'pendingQueue', {});
|
|
1889
|
+
set(this, 'stateManager', stateManager);
|
|
1890
|
+
stateManager.goToState('empty');
|
|
1891
|
+
},
|
|
1892
|
+
|
|
1893
|
+
destroy: function() {
|
|
1894
|
+
if (!get(this, 'isDeleted')) {
|
|
1895
|
+
this.deleteRecord();
|
|
1896
|
+
}
|
|
1897
|
+
this._super();
|
|
1898
|
+
},
|
|
1899
|
+
|
|
1900
|
+
send: function(name, context) {
|
|
1901
|
+
return get(this, 'stateManager').send(name, context);
|
|
1902
|
+
},
|
|
1903
|
+
|
|
1904
|
+
withTransaction: function(fn) {
|
|
1905
|
+
var transaction = get(this, 'transaction') || getPath(this, 'store.defaultTransaction');
|
|
1906
|
+
if (transaction) { fn(transaction); }
|
|
1907
|
+
},
|
|
1908
|
+
|
|
1909
|
+
setProperty: function(key, value) {
|
|
1910
|
+
this.send('setProperty', { key: key, value: value });
|
|
1911
|
+
},
|
|
1912
|
+
|
|
1913
|
+
deleteRecord: function() {
|
|
1914
|
+
this.send('deleteRecord');
|
|
1915
|
+
},
|
|
1916
|
+
|
|
1917
|
+
waitingOn: function(record) {
|
|
1918
|
+
this.send('waitingOn', record);
|
|
1919
|
+
},
|
|
1920
|
+
|
|
1921
|
+
notifyHashWasUpdated: function() {
|
|
1922
|
+
var store = get(this, 'store');
|
|
1923
|
+
if (store) {
|
|
1924
|
+
store.hashWasUpdated(this.constructor, get(this, 'clientId'));
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
|
|
1928
|
+
unknownProperty: function(key) {
|
|
1929
|
+
var data = get(this, 'data');
|
|
1930
|
+
|
|
1931
|
+
if (data && key in data) {
|
|
1932
|
+
ember_assert("You attempted to access the " + key + " property on a model without defining an attribute.", false);
|
|
1933
|
+
}
|
|
1934
|
+
},
|
|
1935
|
+
|
|
1936
|
+
setUnknownProperty: function(key, value) {
|
|
1937
|
+
var data = get(this, 'data');
|
|
1938
|
+
|
|
1939
|
+
if (data && key in data) {
|
|
1940
|
+
ember_assert("You attempted to set the " + key + " property on a model without defining an attribute.", false);
|
|
1941
|
+
} else {
|
|
1942
|
+
return this._super(key, value);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
// Helper function to generate store aliases.
|
|
1948
|
+
// This returns a function that invokes the named alias
|
|
1949
|
+
// on the default store, but injects the class as the
|
|
1950
|
+
// first parameter.
|
|
1951
|
+
var storeAlias = function(methodName) {
|
|
1952
|
+
return function() {
|
|
1953
|
+
var store = get(DS, 'defaultStore'),
|
|
1954
|
+
args = [].slice.call(arguments);
|
|
1955
|
+
|
|
1956
|
+
args.unshift(this);
|
|
1957
|
+
return store[methodName].apply(store, args);
|
|
1958
|
+
};
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
DS.Model.reopenClass({
|
|
1962
|
+
find: storeAlias('find'),
|
|
1963
|
+
filter: storeAlias('filter'),
|
|
1964
|
+
|
|
1965
|
+
_create: DS.Model.create,
|
|
1966
|
+
|
|
1967
|
+
create: function() {
|
|
1968
|
+
throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.");
|
|
1969
|
+
},
|
|
1970
|
+
|
|
1971
|
+
createRecord: storeAlias('createRecord')
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
})({});
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
(function(exports) {
|
|
1978
|
+
var get = Ember.get, getPath = Ember.getPath;
|
|
1979
|
+
DS.attr = function(type, options) {
|
|
1980
|
+
var transform = DS.attr.transforms[type];
|
|
1981
|
+
ember_assert("Could not find model attribute of type " + type, !!transform);
|
|
1982
|
+
|
|
1983
|
+
var transformFrom = transform.from;
|
|
1984
|
+
var transformTo = transform.to;
|
|
1985
|
+
|
|
1986
|
+
return Ember.computed(function(key, value) {
|
|
1987
|
+
var data = get(this, 'data');
|
|
1988
|
+
|
|
1989
|
+
key = (options && options.key) ? options.key : key;
|
|
1990
|
+
|
|
1991
|
+
if (value === undefined) {
|
|
1992
|
+
if (!data) { return; }
|
|
1993
|
+
|
|
1994
|
+
return transformFrom(data[key]);
|
|
1995
|
+
} else {
|
|
1996
|
+
ember_assert("You cannot set a model attribute before its data is loaded.", !!data);
|
|
1997
|
+
|
|
1998
|
+
value = transformTo(value);
|
|
1999
|
+
this.setProperty(key, value);
|
|
2000
|
+
return value;
|
|
2001
|
+
}
|
|
2002
|
+
}).property('data');
|
|
2003
|
+
};
|
|
2004
|
+
DS.attr.transforms = {
|
|
2005
|
+
string: {
|
|
2006
|
+
from: function(serialized) {
|
|
2007
|
+
return Ember.none(serialized) ? null : String(serialized);
|
|
2008
|
+
},
|
|
2009
|
+
|
|
2010
|
+
to: function(deserialized) {
|
|
2011
|
+
return Ember.none(deserialized) ? null : String(deserialized);
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
|
|
2015
|
+
integer: {
|
|
2016
|
+
from: function(serialized) {
|
|
2017
|
+
return Ember.none(serialized) ? null : Number(serialized);
|
|
2018
|
+
},
|
|
2019
|
+
|
|
2020
|
+
to: function(deserialized) {
|
|
2021
|
+
return Ember.none(deserialized) ? null : Number(deserialized);
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
boolean: {
|
|
2026
|
+
from: function(serialized) {
|
|
2027
|
+
return Boolean(serialized);
|
|
2028
|
+
},
|
|
2029
|
+
|
|
2030
|
+
to: function(deserialized) {
|
|
2031
|
+
return Boolean(deserialized);
|
|
2032
|
+
}
|
|
2033
|
+
},
|
|
2034
|
+
|
|
2035
|
+
date: {
|
|
2036
|
+
from: function(serialized) {
|
|
2037
|
+
var type = typeof serialized;
|
|
2038
|
+
|
|
2039
|
+
if (type === "string" || type === "number") {
|
|
2040
|
+
return new Date(serialized);
|
|
2041
|
+
} else if (serialized === null || serialized === undefined) {
|
|
2042
|
+
// if the value is not present in the data,
|
|
2043
|
+
// return undefined, not null.
|
|
2044
|
+
return serialized;
|
|
2045
|
+
} else {
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2049
|
+
|
|
2050
|
+
to: function(date) {
|
|
2051
|
+
if (date instanceof Date) {
|
|
2052
|
+
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
2053
|
+
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
2054
|
+
|
|
2055
|
+
var pad = function(num) {
|
|
2056
|
+
return num < 10 ? "0"+num : ""+num;
|
|
2057
|
+
};
|
|
2058
|
+
|
|
2059
|
+
var utcYear = date.getUTCFullYear(),
|
|
2060
|
+
utcMonth = date.getUTCMonth(),
|
|
2061
|
+
utcDayOfMonth = date.getUTCDate(),
|
|
2062
|
+
utcDay = date.getUTCDay(),
|
|
2063
|
+
utcHours = date.getUTCHours(),
|
|
2064
|
+
utcMinutes = date.getUTCMinutes(),
|
|
2065
|
+
utcSeconds = date.getUTCSeconds();
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
var dayOfWeek = days[utcDay];
|
|
2069
|
+
var dayOfMonth = pad(utcDayOfMonth);
|
|
2070
|
+
var month = months[utcMonth];
|
|
2071
|
+
|
|
2072
|
+
return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
|
|
2073
|
+
pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
|
|
2074
|
+
} else if (date === undefined) {
|
|
2075
|
+
return undefined;
|
|
2076
|
+
} else {
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
|
|
2083
|
+
|
|
2084
|
+
})({});
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
(function(exports) {
|
|
2088
|
+
var get = Ember.get, set = Ember.set, getPath = Ember.getPath;
|
|
2089
|
+
DS.Model.reopenClass({
|
|
2090
|
+
typeForAssociation: function(association) {
|
|
2091
|
+
var type = this.metaForProperty(association).type;
|
|
2092
|
+
if (typeof type === 'string') {
|
|
2093
|
+
type = getPath(this, type, false) || getPath(window, type);
|
|
2094
|
+
}
|
|
2095
|
+
return type;
|
|
2096
|
+
}
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
|
|
2100
|
+
var embeddedFindRecord = function(store, type, data, key, one) {
|
|
2101
|
+
var association = data ? get(data, key) : one ? null : [];
|
|
2102
|
+
if (one) {
|
|
2103
|
+
return association ? store.load(type, association).id : null;
|
|
2104
|
+
} else {
|
|
2105
|
+
return association ? store.loadMany(type, association).ids : [];
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
var referencedFindRecord = function(store, type, data, key, one) {
|
|
2110
|
+
return data ? get(data, key) : one ? null : [];
|
|
2111
|
+
};
|
|
2112
|
+
|
|
2113
|
+
var hasAssociation = function(type, options, one) {
|
|
2114
|
+
var embedded = options && options.embedded,
|
|
2115
|
+
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
|
|
2116
|
+
|
|
2117
|
+
return Ember.computed(function(key) {
|
|
2118
|
+
var data = get(this, 'data'), ids, id, association,
|
|
2119
|
+
store = get(this, 'store');
|
|
2120
|
+
|
|
2121
|
+
if (typeof type === 'string') {
|
|
2122
|
+
type = getPath(this, type, false) || getPath(window, type);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
key = (options && options.key) ? options.key : key;
|
|
2126
|
+
if (one) {
|
|
2127
|
+
id = findRecord(store, type, data, key, true);
|
|
2128
|
+
association = id ? store.find(type, id) : null;
|
|
2129
|
+
} else {
|
|
2130
|
+
ids = findRecord(store, type, data, key);
|
|
2131
|
+
association = store.findMany(type, ids);
|
|
2132
|
+
set(association, 'parentRecord', this);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
return association;
|
|
2136
|
+
}).property('data').cacheable().meta({ type: type });
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
DS.hasMany = function(type, options) {
|
|
2140
|
+
ember_assert("The type passed to DS.hasMany must be defined", !!type);
|
|
2141
|
+
return hasAssociation(type, options);
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
DS.hasOne = function(type, options) {
|
|
2145
|
+
ember_assert("The type passed to DS.hasOne must be defined", !!type);
|
|
2146
|
+
return hasAssociation(type, options, true);
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
})({});
|
|
2150
|
+
|
|
2151
|
+
|
|
2152
|
+
(function(exports) {
|
|
2153
|
+
})({});
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
(function(exports) {
|
|
2157
|
+
//Copyright (C) 2011 by Living Social, Inc.
|
|
2158
|
+
|
|
2159
|
+
//Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
2160
|
+
//this software and associated documentation files (the "Software"), to deal in
|
|
2161
|
+
//the Software without restriction, including without limitation the rights to
|
|
2162
|
+
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
2163
|
+
//of the Software, and to permit persons to whom the Software is furnished to do
|
|
2164
|
+
//so, subject to the following conditions:
|
|
2165
|
+
|
|
2166
|
+
//The above copyright notice and this permission notice shall be included in all
|
|
2167
|
+
//copies or substantial portions of the Software.
|
|
2168
|
+
|
|
2169
|
+
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
2170
|
+
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
2171
|
+
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
2172
|
+
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
2173
|
+
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
2174
|
+
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
2175
|
+
//SOFTWARE.
|
|
2176
|
+
})({});
|