modularity-rails 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/demo/spec/javascripts/data/ajax_loader_spec.coffee +136 -0
- data/demo/spec/javascripts/{tools → data}/cache_spec.coffee +19 -2
- data/demo/spec/javascripts/data/indexed_cache_spec.coffee +51 -0
- data/demo/spec/javascripts/data/persistence_manager_spec.coffee +276 -0
- data/demo/spec/javascripts/tools/object_tools_spec.coffee +27 -0
- data/lib/modularity-rails/version.rb +1 -1
- data/vendor/assets/javascripts/modularity/{tools → data}/ajax_loader.coffee +10 -3
- data/vendor/assets/javascripts/modularity/{tools → data}/cache.coffee +8 -2
- data/vendor/assets/javascripts/modularity/data/indexed_cache.coffee +26 -0
- data/vendor/assets/javascripts/modularity/data/persistence_manager.coffee +131 -0
- data/vendor/assets/javascripts/modularity/tools/object_tools.coffee +15 -0
- metadata +22 -16
- data/demo/spec/javascripts/tools/ajax_loader_spec.coffee +0 -79
@@ -0,0 +1,136 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
#= require modularity/data/ajax_loader
|
3
|
+
|
4
|
+
|
5
|
+
describe 'ajax_loader', ->
|
6
|
+
|
7
|
+
ajax_loader = spy = spy_get = null
|
8
|
+
beforeEach ->
|
9
|
+
spy = sinon.spy()
|
10
|
+
spy_get = sinon.spy jQuery, 'get'
|
11
|
+
|
12
|
+
afterEach ->
|
13
|
+
spy_get.restore()
|
14
|
+
|
15
|
+
describe 'constructor', ->
|
16
|
+
it "stores the 'cache' settings", ->
|
17
|
+
loader = new modularity.AjaxLoader {caching: yes}
|
18
|
+
loader.caching.should.be.true
|
19
|
+
|
20
|
+
|
21
|
+
describe 'get', ->
|
22
|
+
url = "/users/4"
|
23
|
+
|
24
|
+
describe 'with caching enabled', ->
|
25
|
+
|
26
|
+
beforeEach ->
|
27
|
+
ajax_loader = new modularity.AjaxLoader {caching: yes}
|
28
|
+
|
29
|
+
describe 'first time request', ->
|
30
|
+
|
31
|
+
beforeEach ->
|
32
|
+
ajax_loader.get url, spy
|
33
|
+
|
34
|
+
it 'makes an ajax request', ->
|
35
|
+
jQuery.get.should.have.been.called
|
36
|
+
|
37
|
+
|
38
|
+
it 'saves the callback for later', ->
|
39
|
+
ajax_loader.cache.get(url).should.have.length 1
|
40
|
+
ajax_loader.cache.get(url)[0].should.equal spy
|
41
|
+
|
42
|
+
it 'returns without calling the callback', ->
|
43
|
+
spy.should.not.have.been.called
|
44
|
+
|
45
|
+
describe 'the request is already in progress', ->
|
46
|
+
|
47
|
+
beforeEach ->
|
48
|
+
ajax_loader.cache.cache[url] = [spy]
|
49
|
+
|
50
|
+
|
51
|
+
it 'adds the callback to the callback array', ->
|
52
|
+
ajax_loader.cache.get(url).should.have.length 1
|
53
|
+
ajax_loader.cache.get(url)[0].should.equal spy
|
54
|
+
|
55
|
+
|
56
|
+
it 'returns without calling the callback', ->
|
57
|
+
spy.should.not.have.been.called
|
58
|
+
|
59
|
+
|
60
|
+
it 'does not make another ajax request', ->
|
61
|
+
jQuery.get.should.not.have.been.called
|
62
|
+
|
63
|
+
describe 'ajax request successful', ->
|
64
|
+
|
65
|
+
beforeEach ->
|
66
|
+
jquery_callback = null
|
67
|
+
jQuery.get = (url, callback) -> jquery_callback = callback
|
68
|
+
ajax_loader.get url, spy
|
69
|
+
jquery_callback('result')
|
70
|
+
|
71
|
+
it 'calls the given callbacks with the server data', ->
|
72
|
+
spy.should.have.been.calledWith 'result'
|
73
|
+
|
74
|
+
it 'replaces the cache callbacks with returned data', ->
|
75
|
+
ajax_loader.cache.get(url).should.equal 'result'
|
76
|
+
|
77
|
+
describe 'the data has already been loaded', ->
|
78
|
+
|
79
|
+
it 'calls the callback with the cached data', ->
|
80
|
+
ajax_loader.cache.add url, "my data"
|
81
|
+
|
82
|
+
ajax_loader.get(url, spy)
|
83
|
+
|
84
|
+
spy.should.have.been.called
|
85
|
+
spy.should.have.been.calledWith "my data"
|
86
|
+
|
87
|
+
|
88
|
+
describe 'with caching disabled', ->
|
89
|
+
|
90
|
+
beforeEach ->
|
91
|
+
ajax_loader = new modularity.AjaxLoader {caching: no}
|
92
|
+
|
93
|
+
describe 'first time request', ->
|
94
|
+
|
95
|
+
beforeEach ->
|
96
|
+
ajax_loader.get url, spy
|
97
|
+
|
98
|
+
it 'makes an ajax request', ->
|
99
|
+
jQuery.get.should.have.been.called
|
100
|
+
|
101
|
+
it 'saves the callback for later', ->
|
102
|
+
ajax_loader.cache.get(url).should.have.length 1
|
103
|
+
ajax_loader.cache.get(url)[0].should.equal spy
|
104
|
+
|
105
|
+
it 'returns without calling the callback', ->
|
106
|
+
spy.should.not.have.been.called
|
107
|
+
|
108
|
+
describe 'the request is already in progress', ->
|
109
|
+
|
110
|
+
beforeEach ->
|
111
|
+
ajax_loader.cache.cache[url] = [spy]
|
112
|
+
|
113
|
+
it 'adds the callback to the callback array', ->
|
114
|
+
ajax_loader.cache.get(url).should.have.length 1
|
115
|
+
ajax_loader.cache.get(url)[0].should.equal spy
|
116
|
+
|
117
|
+
it 'returns without calling the callback', ->
|
118
|
+
spy.should.not.have.been.called
|
119
|
+
|
120
|
+
it 'does not make another ajax request', ->
|
121
|
+
jQuery.get.should.not.have.been.called
|
122
|
+
|
123
|
+
describe 'ajax request successful', ->
|
124
|
+
|
125
|
+
beforeEach ->
|
126
|
+
jquery_callback = null
|
127
|
+
jQuery.get = (url, callback) -> jquery_callback = callback
|
128
|
+
ajax_loader.get url, spy
|
129
|
+
jquery_callback('result')
|
130
|
+
|
131
|
+
it 'calls the given callbacks with the server data', ->
|
132
|
+
spy.should.have.been.calledWith 'result'
|
133
|
+
|
134
|
+
it 'replaces the cache callbacks with returned data', ->
|
135
|
+
expect(ajax_loader.cache.get(url)).to.be.undefined
|
136
|
+
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#= require spec_helper
|
2
|
-
#= require modularity/
|
2
|
+
#= require modularity/data/cache
|
3
3
|
|
4
4
|
|
5
5
|
describe 'Cache', ->
|
@@ -18,7 +18,7 @@ describe 'Cache', ->
|
|
18
18
|
describe 'add', ->
|
19
19
|
|
20
20
|
it 'stores the given data in the cache', ->
|
21
|
-
cache.add
|
21
|
+
cache.add 'foo', 'bar'
|
22
22
|
cache.cache.should.eql({'foo': 'bar'})
|
23
23
|
|
24
24
|
it 'overwrites existing entries', ->
|
@@ -27,6 +27,23 @@ describe 'Cache', ->
|
|
27
27
|
(cache.cache['foo']).should.be.equal('new')
|
28
28
|
|
29
29
|
|
30
|
+
describe 'remove', ->
|
31
|
+
|
32
|
+
it 'removes the entry with the given key from the chache', ->
|
33
|
+
cache.add 'foo', 'bar'
|
34
|
+
cache.remove 'foo'
|
35
|
+
expect(cache.get('foo')).to.be.undefined
|
36
|
+
cache.length().should.equal 0
|
37
|
+
|
38
|
+
it 'only removes the given data and leaves the rest of the cache alone', ->
|
39
|
+
cache.add 1, 'one'
|
40
|
+
cache.add 2, 'two'
|
41
|
+
cache.remove 1
|
42
|
+
expect(cache.get(1)).to.be.undefined
|
43
|
+
expect(cache.get(2)).to.equal 'two'
|
44
|
+
cache.length().should.equal 1
|
45
|
+
|
46
|
+
|
30
47
|
describe 'get', ->
|
31
48
|
|
32
49
|
it "returns undefined if the entry doesn't exist", ->
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
#= require modularity/data/indexed_cache
|
3
|
+
|
4
|
+
|
5
|
+
describe 'IndexedCache', ->
|
6
|
+
|
7
|
+
indexed_cache = null
|
8
|
+
entry_1 = {id: 1, value: 'one'}
|
9
|
+
entry_2 = {id: 2, value: 'two'}
|
10
|
+
beforeEach ->
|
11
|
+
indexed_cache = new modularity.IndexedCache 'id'
|
12
|
+
|
13
|
+
|
14
|
+
describe 'constructor', ->
|
15
|
+
|
16
|
+
it 'stores the given key attribute', ->
|
17
|
+
indexed_cache.key.should == 'id'
|
18
|
+
|
19
|
+
|
20
|
+
describe 'add', ->
|
21
|
+
|
22
|
+
beforeEach ->
|
23
|
+
indexed_cache.add entry_1
|
24
|
+
|
25
|
+
it 'adds the given element', ->
|
26
|
+
indexed_cache.cache.length().should.equal 1
|
27
|
+
indexed_cache.cache.cache[1].should.eql entry_1
|
28
|
+
|
29
|
+
|
30
|
+
describe 'add_all', ->
|
31
|
+
it 'adds all the given elements', ->
|
32
|
+
indexed_cache.add_all [entry_1, entry_2]
|
33
|
+
indexed_cache.length().should.equal 2
|
34
|
+
indexed_cache.cache.cache[1].should.eql entry_1
|
35
|
+
indexed_cache.cache.cache[2].should.eql entry_2
|
36
|
+
|
37
|
+
|
38
|
+
describe 'remove', ->
|
39
|
+
it 'removes the given object from the server', ->
|
40
|
+
indexed_cache.add entry_1
|
41
|
+
indexed_cache.remove entry_1
|
42
|
+
indexed_cache.length().should.equal 0
|
43
|
+
|
44
|
+
describe 'get', ->
|
45
|
+
it 'returns the element indexed by the given key', ->
|
46
|
+
indexed_cache.add entry_1
|
47
|
+
indexed_cache.get(1).should.eql entry_1
|
48
|
+
|
49
|
+
it "returns undefined if the element doesn't exist", ->
|
50
|
+
expect(indexed_cache.get(2)).to.be.undefined
|
51
|
+
|
@@ -0,0 +1,276 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
#= require modularity/data/persistence_manager
|
3
|
+
#= require jquery
|
4
|
+
|
5
|
+
|
6
|
+
describe 'PersistenceManager', ->
|
7
|
+
|
8
|
+
# Global variables for testing.
|
9
|
+
persistence_manager = null
|
10
|
+
entry_1 = {id: 1, value: 'one'}
|
11
|
+
entry_2 = {id: 2, value: 'two'}
|
12
|
+
|
13
|
+
beforeEach ->
|
14
|
+
persistence_manager = new modularity.PersistenceManager {url: '/users'}
|
15
|
+
|
16
|
+
|
17
|
+
describe 'constructor', ->
|
18
|
+
|
19
|
+
it 'initializes an empty server data cache', ->
|
20
|
+
persistence_manager.server_data.length().should.equal 0
|
21
|
+
|
22
|
+
it 'initializes an empty client data cache', ->
|
23
|
+
persistence_manager.client_data.length().should.eql 0
|
24
|
+
|
25
|
+
it 'stores the given base url', ->
|
26
|
+
persistence_manager.base_url.should.equal '/users'
|
27
|
+
|
28
|
+
it 'stores the given index key', ->
|
29
|
+
persistence_manager = new modularity.PersistenceManager {key: 'my_key', url: '/users'}
|
30
|
+
persistence_manager.key.should.equal 'my_key'
|
31
|
+
|
32
|
+
it "uses 'id' as the default if no key is given", ->
|
33
|
+
persistence_manager.key.should.equal 'id'
|
34
|
+
|
35
|
+
|
36
|
+
describe 'clone', ->
|
37
|
+
|
38
|
+
clone = null
|
39
|
+
beforeEach ->
|
40
|
+
clone = persistence_manager.clone entry_1
|
41
|
+
|
42
|
+
it 'returns an object that has the same properties as the given object', ->
|
43
|
+
clone.id.should.equal 1
|
44
|
+
clone.value.should.equal 'one'
|
45
|
+
|
46
|
+
it 'returns an object that can be changed independently from the given object', ->
|
47
|
+
clone.id = 2
|
48
|
+
clone.value = 'two'
|
49
|
+
entry_1.id.should == 1
|
50
|
+
entry_1.value.should == 'one'
|
51
|
+
|
52
|
+
|
53
|
+
describe 'create', ->
|
54
|
+
new_obj = {value: 'foo'}
|
55
|
+
|
56
|
+
beforeEach ->
|
57
|
+
sinon.stub(jQuery, 'ajax').yieldsTo('success', entry_2)
|
58
|
+
|
59
|
+
afterEach ->
|
60
|
+
jQuery.ajax.restore()
|
61
|
+
|
62
|
+
it 'makes an ajax POST request to the CREATE url on the server', ->
|
63
|
+
persistence_manager.create new_obj, ->
|
64
|
+
jQuery.ajax.should.have.been.calledOnce
|
65
|
+
args = jQuery.ajax.args[0][0]
|
66
|
+
args.url.should.equal '/users.json'
|
67
|
+
args.type.should.equal 'POST'
|
68
|
+
|
69
|
+
it 'sends the object as payload', ->
|
70
|
+
persistence_manager.create new_obj, ->
|
71
|
+
args = jQuery.ajax.args[0][0]
|
72
|
+
args.data.should.equal new_obj
|
73
|
+
|
74
|
+
it "doesn't updates the server cache immediately because the object doesn't have an id", ->
|
75
|
+
persistence_manager.create new_obj, ->
|
76
|
+
expect(persistence_manager.server_data.get(1)).to.be.undefined
|
77
|
+
|
78
|
+
it 'updates the server cache with the server data when the call returns', (done) ->
|
79
|
+
persistence_manager.create {value: 'bar'}, (server_obj) ->
|
80
|
+
persistence_manager.server_data.get(server_obj.id).should.equal entry_2
|
81
|
+
done()
|
82
|
+
|
83
|
+
it 'calls the given callback when done', (done) ->
|
84
|
+
persistence_manager.create new_obj, ->
|
85
|
+
done()
|
86
|
+
|
87
|
+
|
88
|
+
describe 'delete', ->
|
89
|
+
|
90
|
+
beforeEach ->
|
91
|
+
sinon.stub(jQuery, 'ajax').yieldsTo('success')
|
92
|
+
|
93
|
+
afterEach ->
|
94
|
+
jQuery.ajax.restore()
|
95
|
+
|
96
|
+
it 'makes an ajax DELETE request to the item url on the server', ->
|
97
|
+
persistence_manager.delete entry_1
|
98
|
+
jQuery.ajax.should.have.been.calledOnce
|
99
|
+
args = jQuery.ajax.args[0][0]
|
100
|
+
args.url.should.equal '/users/1.json'
|
101
|
+
args.type.should.equal 'DELETE'
|
102
|
+
|
103
|
+
it 'removes the object from the client and server cache', ->
|
104
|
+
persistence_manager.server_data.add entry_1
|
105
|
+
persistence_manager.client_data.add entry_1
|
106
|
+
persistence_manager.delete entry_1
|
107
|
+
persistence_manager.server_data.length().should.equal 0
|
108
|
+
persistence_manager.client_data.length().should.equal 0
|
109
|
+
|
110
|
+
it 'calls the given callback when done', (done) ->
|
111
|
+
persistence_manager.delete entry_1, ->
|
112
|
+
done()
|
113
|
+
|
114
|
+
|
115
|
+
describe 'entry_url', ->
|
116
|
+
|
117
|
+
it 'returns the url to access a single entry', ->
|
118
|
+
persistence_manager.entry_url(entry_1).should.equal '/users/1.json'
|
119
|
+
|
120
|
+
|
121
|
+
describe 'load_all', ->
|
122
|
+
|
123
|
+
loading_done_callback = null
|
124
|
+
beforeEach ->
|
125
|
+
sinon.stub(jQuery, 'ajax').yieldsTo('success', [entry_1, entry_2])
|
126
|
+
loading_done_callback = sinon.spy()
|
127
|
+
persistence_manager.load_all loading_done_callback, {'q': 'foo'},
|
128
|
+
|
129
|
+
afterEach ->
|
130
|
+
jQuery.ajax.restore()
|
131
|
+
|
132
|
+
it 'makes a request to the INDEX action of the server', ->
|
133
|
+
jQuery.ajax.should.have.been.calledOnce
|
134
|
+
jQuery.ajax.args[0][0].url.should.equal '/users.json'
|
135
|
+
|
136
|
+
it 'provides the given data as parameters to the request', ->
|
137
|
+
jQuery.ajax.args[0][0].data.should.eql {'q': 'foo'}
|
138
|
+
|
139
|
+
it 'fills the server cache with the response data', ->
|
140
|
+
persistence_manager.server_data.length().should.eql 2
|
141
|
+
persistence_manager.server_data.get(1).should.eql entry_1
|
142
|
+
persistence_manager.server_data.get(2).should.eql entry_2
|
143
|
+
|
144
|
+
it 'calls the given callback method when the data is available', ->
|
145
|
+
loading_done_callback.should.have.been.calledOnce
|
146
|
+
|
147
|
+
|
148
|
+
describe 'load', ->
|
149
|
+
|
150
|
+
describe 'entry exists in client data cache', ->
|
151
|
+
it 'returns the entry from the @client_data cache if it exists there', (done) ->
|
152
|
+
persistence_manager.client_data.add entry_1
|
153
|
+
persistence_manager.load 1, (entry) ->
|
154
|
+
entry.should.equal entry_1
|
155
|
+
done()
|
156
|
+
|
157
|
+
describe 'entry exists in server data cache', ->
|
158
|
+
|
159
|
+
beforeEach ->
|
160
|
+
persistence_manager.server_data.add entry_1
|
161
|
+
|
162
|
+
it 'adds the entry to the client data cache', (done) ->
|
163
|
+
persistence_manager.load 1, ->
|
164
|
+
persistence_manager.client_data.length().should.equal 1
|
165
|
+
persistence_manager.client_data.get(1).should.eql entry_1
|
166
|
+
done()
|
167
|
+
|
168
|
+
it 'returns the entry from the client data cache', (done) ->
|
169
|
+
persistence_manager.load 1, (entry) ->
|
170
|
+
client_cache_entry = persistence_manager.client_data.get 1
|
171
|
+
entry.should.equal client_cache_entry
|
172
|
+
done()
|
173
|
+
|
174
|
+
it 'returns a different hash than the server data, so that the user can make changes to it', (done) ->
|
175
|
+
persistence_manager.load 1, (entry) ->
|
176
|
+
server_cache_entry = persistence_manager.server_data.get 1
|
177
|
+
|
178
|
+
entry.should.not.equal server_cache_entry
|
179
|
+
done()
|
180
|
+
|
181
|
+
|
182
|
+
describe "entry doesn't exist on the client at all", ->
|
183
|
+
|
184
|
+
beforeEach ->
|
185
|
+
sinon.stub(jQuery, 'ajax').yieldsTo('success', entry_1)
|
186
|
+
|
187
|
+
afterEach ->
|
188
|
+
jQuery.ajax.restore()
|
189
|
+
|
190
|
+
it "retrieves the entry from the server", (done) ->
|
191
|
+
persistence_manager.load 1, ->
|
192
|
+
jQuery.ajax.should.have.been.calledOnce
|
193
|
+
done()
|
194
|
+
|
195
|
+
|
196
|
+
it 'stores the entry in the server cache', (done) ->
|
197
|
+
persistence_manager.load 1, ->
|
198
|
+
persistence_manager.server_data.length().should.equal 1
|
199
|
+
persistence_manager.server_data.get(1).should.equal entry_1
|
200
|
+
done()
|
201
|
+
|
202
|
+
it 'stores a replica of the entry in the client cache', (done) ->
|
203
|
+
persistence_manager.load 1, ->
|
204
|
+
persistence_manager.client_data.get(1).should.not.equal entry_1
|
205
|
+
persistence_manager.client_data.get(1).should.eql entry_1
|
206
|
+
done()
|
207
|
+
|
208
|
+
it 'returns the client replica', (done) ->
|
209
|
+
persistence_manager.load 1, (entry) ->
|
210
|
+
persistence_manager.client_data.get(1).should.equal entry
|
211
|
+
persistence_manager.server_data.get(1).should.not.equal entry
|
212
|
+
done()
|
213
|
+
|
214
|
+
|
215
|
+
describe 'save', ->
|
216
|
+
|
217
|
+
describe 'for unsaved objects', ->
|
218
|
+
it "calls the 'create' action", ->
|
219
|
+
sinon.stub persistence_manager, 'create'
|
220
|
+
persistence_manager.save {value: 'foo'}, ->
|
221
|
+
persistence_manager.create.should.have.been.calledOnce
|
222
|
+
|
223
|
+
describe 'for objects from the server', ->
|
224
|
+
it "calls the 'update' action", ->
|
225
|
+
sinon.stub persistence_manager, 'update'
|
226
|
+
persistence_manager.save entry_1, ->
|
227
|
+
persistence_manager.update.should.have.been.calledOnce
|
228
|
+
|
229
|
+
|
230
|
+
describe 'update', ->
|
231
|
+
|
232
|
+
beforeEach ->
|
233
|
+
sinon.stub(jQuery, 'ajax').yieldsTo('success', entry_2)
|
234
|
+
server_entry = {id: 1, title: 'server title', desc: 'server desc'}
|
235
|
+
persistence_manager.server_data.add server_entry
|
236
|
+
|
237
|
+
afterEach ->
|
238
|
+
jQuery.ajax.restore()
|
239
|
+
|
240
|
+
it 'makes an ajax PUT request to the UPDATE url on the server', ->
|
241
|
+
persistence_manager.update entry_1, ->
|
242
|
+
jQuery.ajax.should.have.been.calledOnce
|
243
|
+
args = jQuery.ajax.args[0][0]
|
244
|
+
args.url.should.equal '/users/1.json'
|
245
|
+
args.type.should.equal 'PUT'
|
246
|
+
|
247
|
+
it 'sends only updated colums of the object as payload', (done) ->
|
248
|
+
persistence_manager.load 1, (client_entry) ->
|
249
|
+
client_entry.title = 'client title'
|
250
|
+
|
251
|
+
persistence_manager.update client_entry
|
252
|
+
|
253
|
+
|
254
|
+
args = jQuery.ajax.args[0][0]
|
255
|
+
args.data.should.eql {id: 1, title: 'client title'}
|
256
|
+
done()
|
257
|
+
|
258
|
+
it "doesn't perform the call if nothing is changed", (done) ->
|
259
|
+
persistence_manager.load 1, (client_entry) ->
|
260
|
+
persistence_manager.update client_entry
|
261
|
+
jQuery.ajax.should.not.have.been.called
|
262
|
+
done()
|
263
|
+
|
264
|
+
it 'updates the server cache immediately with the given object', ->
|
265
|
+
persistence_manager.update entry_1, ->
|
266
|
+
persistence_manager.server_data.get(1).should.equal entry_1
|
267
|
+
|
268
|
+
it 'updates the server cache with the server object when done', (done) ->
|
269
|
+
persistence_manager.update entry_1, (server_obj) ->
|
270
|
+
persistence_manager.server_data.get(2).should.equal entry_2
|
271
|
+
done()
|
272
|
+
|
273
|
+
it 'calls the given callback when done', (done) ->
|
274
|
+
persistence_manager.update entry_1, ->
|
275
|
+
done()
|
276
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
#= require modularity/tools/object_tools
|
3
|
+
|
4
|
+
describe 'object_tools', ->
|
5
|
+
|
6
|
+
describe 'object_diff', ->
|
7
|
+
obj_1 = {title: 'title 1', value: 'value 1'}
|
8
|
+
obj_2 = {title: 'title 2', value: 'value 1'}
|
9
|
+
obj_1b = {title: 'title 1', value: 'value 1'}
|
10
|
+
|
11
|
+
it 'returns a new object that contains only the changed attributes', ->
|
12
|
+
modularity.object_diff(obj_1, obj_2).should.eql {title: 'title 2'}
|
13
|
+
|
14
|
+
it 'returns an empty object if the two objects are equal', ->
|
15
|
+
modularity.object_diff(obj_1, obj_1b).should.eql {}
|
16
|
+
|
17
|
+
|
18
|
+
describe 'object_length', ->
|
19
|
+
|
20
|
+
it 'returns the number of attributes of the given object', ->
|
21
|
+
obj_1 = {1: 'one'}
|
22
|
+
obj_2 = {1: 'one', 2: 'two'}
|
23
|
+
obj_3 = {1: 'one', 2: 'two', 3: 'three'}
|
24
|
+
modularity.object_length({}).should.equal 0
|
25
|
+
modularity.object_length(obj_1).should.equal 1
|
26
|
+
modularity.object_length(obj_2).should.equal 2
|
27
|
+
modularity.object_length(obj_3).should.equal 3
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#= require modularity/
|
1
|
+
#= require modularity/data/cache
|
2
2
|
|
3
3
|
|
4
4
|
# A generic ajax loader for parallel GET requests.
|
@@ -10,9 +10,13 @@
|
|
10
10
|
# any new content on the same URL will not be visible!
|
11
11
|
class window.modularity.AjaxLoader
|
12
12
|
|
13
|
-
constructor: ->
|
13
|
+
constructor: (params = {}) ->
|
14
14
|
@cache = new modularity.Cache()
|
15
15
|
|
16
|
+
# Whether to perform caching of data.
|
17
|
+
# Default: no.
|
18
|
+
@caching = params.caching
|
19
|
+
|
16
20
|
|
17
21
|
get: (url, callback) ->
|
18
22
|
cached_value = @cache.get url
|
@@ -22,7 +26,10 @@ class window.modularity.AjaxLoader
|
|
22
26
|
@cache.add url, [callback]
|
23
27
|
return jQuery.get url, (data) =>
|
24
28
|
cb(data) for cb in @cache.get(url)
|
25
|
-
@
|
29
|
+
if @caching
|
30
|
+
@cache.add url, data
|
31
|
+
else
|
32
|
+
@cache.remove url
|
26
33
|
|
27
34
|
# Request while the GET call is still pending -->
|
28
35
|
# add the given callback to the list of waiting callbacks.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
#= require modularity/tools/object_tools
|
2
|
+
|
1
3
|
# A generic cache.
|
2
4
|
# Stores key-value pairs.
|
3
5
|
class window.modularity.Cache
|
@@ -34,8 +36,12 @@ class window.modularity.Cache
|
|
34
36
|
|
35
37
|
# Returns the number of cached objects.
|
36
38
|
length: () ->
|
37
|
-
|
38
|
-
|
39
|
+
modularity.object_length @cache
|
40
|
+
|
41
|
+
|
42
|
+
# Removes the entry with the given key.
|
43
|
+
remove: (key) =>
|
44
|
+
delete @cache[key]
|
39
45
|
|
40
46
|
|
41
47
|
# Replaces the cache with the given data.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#= require modularity/data/cache
|
2
|
+
|
3
|
+
# Provides fast and convenient retrieval of hash objects
|
4
|
+
# by indexing them on a given key column.
|
5
|
+
class modularity.IndexedCache
|
6
|
+
|
7
|
+
constructor: (@key) ->
|
8
|
+
@cache = new modularity.Cache
|
9
|
+
|
10
|
+
|
11
|
+
add: (entry) ->
|
12
|
+
@cache.add entry[@key], entry
|
13
|
+
|
14
|
+
|
15
|
+
add_all: (entries) ->
|
16
|
+
@add(entry) for entry in entries
|
17
|
+
|
18
|
+
|
19
|
+
remove: (entry) ->
|
20
|
+
@cache.remove entry[@key]
|
21
|
+
|
22
|
+
get: (key) ->
|
23
|
+
@cache.get key
|
24
|
+
|
25
|
+
|
26
|
+
length: => @cache.length()
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#= require modularity/data/indexed_cache
|
2
|
+
#= require modularity/tools/object_tools
|
3
|
+
|
4
|
+
# Provides persistence services for data models.
|
5
|
+
class modularity.PersistenceManager
|
6
|
+
|
7
|
+
constructor: (params) ->
|
8
|
+
|
9
|
+
# Copy of the data as it is on the server.
|
10
|
+
@server_data = new modularity.IndexedCache 'id'
|
11
|
+
|
12
|
+
# Copy of the data as it is on the client.
|
13
|
+
@client_data = new modularity.IndexedCache 'id'
|
14
|
+
|
15
|
+
# The base url on the server. Expected to be a fully RESTful API.
|
16
|
+
@base_url = params.url
|
17
|
+
|
18
|
+
@key = params.key or 'id'
|
19
|
+
|
20
|
+
|
21
|
+
# Returns a deep replica of the given object.
|
22
|
+
clone: (obj) ->
|
23
|
+
result = {}
|
24
|
+
result[key] = value for own key, value of obj
|
25
|
+
result
|
26
|
+
|
27
|
+
|
28
|
+
# Returns the URL to access the collection of objects.
|
29
|
+
collection_url: ->
|
30
|
+
"#{@base_url}.json"
|
31
|
+
|
32
|
+
|
33
|
+
# Creates the given object on the server.
|
34
|
+
create: (obj, callback) ->
|
35
|
+
jQuery.ajax {
|
36
|
+
url: @collection_url()
|
37
|
+
type: 'POST'
|
38
|
+
data: obj
|
39
|
+
success: (server_obj) =>
|
40
|
+
@server_data.add server_obj
|
41
|
+
callback server_obj
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
delete: (obj, callback) ->
|
46
|
+
@client_data.remove obj
|
47
|
+
@server_data.remove obj
|
48
|
+
jQuery.ajax {
|
49
|
+
url: @entry_url(obj)
|
50
|
+
type: 'DELETE'
|
51
|
+
success: ->
|
52
|
+
callback() if callback?
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
# Returns the url to access a single entry.
|
57
|
+
entry_url: (entry) ->
|
58
|
+
"#{@base_url}/#{entry[@key]}.json"
|
59
|
+
|
60
|
+
|
61
|
+
# Returns the entry with the given key.
|
62
|
+
load: (key, callback) ->
|
63
|
+
|
64
|
+
# Try to use client_data cache.
|
65
|
+
client_obj = @client_data.get key
|
66
|
+
return callback(client_obj) if client_obj
|
67
|
+
|
68
|
+
# No data in client cache --> try to use server cache.
|
69
|
+
server_obj = @server_data.get key
|
70
|
+
if server_obj
|
71
|
+
client_obj = @clone server_obj
|
72
|
+
@client_data.add client_obj
|
73
|
+
return callback client_obj
|
74
|
+
|
75
|
+
# No data on client at all --> load data from server.
|
76
|
+
jQuery.ajax {
|
77
|
+
url: "#{@base_url}/#{key}"
|
78
|
+
cache: no
|
79
|
+
success: (server_entry) =>
|
80
|
+
@server_data.add server_entry
|
81
|
+
client_entry = @clone server_entry
|
82
|
+
@client_data.add client_entry
|
83
|
+
callback client_entry
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
# Loads all objects from the server.
|
88
|
+
# Provides the given params as parameters to the GET request.
|
89
|
+
load_all: (callback, params) ->
|
90
|
+
jQuery.ajax {
|
91
|
+
url: @collection_url()
|
92
|
+
cache: no
|
93
|
+
data: params
|
94
|
+
success: (data) =>
|
95
|
+
@server_data.add_all data
|
96
|
+
callback()
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
# Saves the given object.
|
101
|
+
# Does the right thing (create or update) dependent on
|
102
|
+
# whether the object already has a key parameter.
|
103
|
+
save: (obj, callback) ->
|
104
|
+
if obj[@key]?
|
105
|
+
@update obj, callback
|
106
|
+
else
|
107
|
+
@create obj, callback
|
108
|
+
|
109
|
+
|
110
|
+
update: (obj, callback) ->
|
111
|
+
|
112
|
+
# Create a new hash, containing only the changed attributes between obj and it's replica in @server_data.
|
113
|
+
diff_obj = modularity.object_diff @server_data.get(obj[@key]), obj
|
114
|
+
return if modularity.object_length(diff_obj) == 0
|
115
|
+
|
116
|
+
# Add key attribute.
|
117
|
+
diff_obj[@key] = obj[@key]
|
118
|
+
|
119
|
+
# Update server_data version.
|
120
|
+
@server_data.add obj
|
121
|
+
|
122
|
+
# Send to server
|
123
|
+
jQuery.ajax {
|
124
|
+
url: @entry_url(obj)
|
125
|
+
type: 'PUT'
|
126
|
+
data: diff_obj
|
127
|
+
success: (server_obj) =>
|
128
|
+
@server_data.add server_obj
|
129
|
+
callback server_obj if callback
|
130
|
+
}
|
131
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Returns an object that contains only the attributes
|
2
|
+
# that are different between obj_1 and obj_2.
|
3
|
+
# Only looks for changed attributes, not missing attributes.
|
4
|
+
modularity.object_diff = (obj_1, obj_2) ->
|
5
|
+
result = {}
|
6
|
+
for own key, value_2 of obj_2
|
7
|
+
do (key, value_2) ->
|
8
|
+
value_1 = obj_1[key]
|
9
|
+
result[key] = value_2 if value_1 != value_2
|
10
|
+
result
|
11
|
+
|
12
|
+
|
13
|
+
modularity.object_length = (obj) ->
|
14
|
+
# NOTE(KG): This doesn't work in IE8.
|
15
|
+
Object.keys(obj).length
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modularity-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &70234292434960 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.1.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70234292434960
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: capybara-webkit
|
27
|
-
requirement: &
|
27
|
+
requirement: &70234292434440 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70234292434440
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: evergreen
|
38
|
-
requirement: &
|
38
|
+
requirement: &70234292433920 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70234292433920
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rb-fsevent
|
49
|
-
requirement: &
|
49
|
+
requirement: &70234292433260 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70234292433260
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: guard-livereload
|
60
|
-
requirement: &
|
60
|
+
requirement: &70234292432580 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70234292432580
|
69
69
|
description: Description of ModularityRails.
|
70
70
|
email:
|
71
71
|
- kevin.goslar@gmail.com
|
@@ -121,6 +121,10 @@ files:
|
|
121
121
|
- demo/public/index.html
|
122
122
|
- demo/public/robots.txt
|
123
123
|
- demo/script/rails
|
124
|
+
- demo/spec/javascripts/data/ajax_loader_spec.coffee
|
125
|
+
- demo/spec/javascripts/data/cache_spec.coffee
|
126
|
+
- demo/spec/javascripts/data/indexed_cache_spec.coffee
|
127
|
+
- demo/spec/javascripts/data/persistence_manager_spec.coffee
|
124
128
|
- demo/spec/javascripts/konacha_config.coffee
|
125
129
|
- demo/spec/javascripts/mixins/closable_spec.coffee
|
126
130
|
- demo/spec/javascripts/modularity_spec.coffee
|
@@ -134,9 +138,8 @@ files:
|
|
134
138
|
- demo/spec/javascripts/templates/button.jst.ejs
|
135
139
|
- demo/spec/javascripts/templates/closable.jst.ejs
|
136
140
|
- demo/spec/javascripts/templates/modularity.jst.ejs
|
137
|
-
- demo/spec/javascripts/tools/ajax_loader_spec.coffee
|
138
141
|
- demo/spec/javascripts/tools/array_tools_spec.coffee
|
139
|
-
- demo/spec/javascripts/tools/
|
142
|
+
- demo/spec/javascripts/tools/object_tools_spec.coffee
|
140
143
|
- demo/vendor/assets/javascripts/.gitkeep
|
141
144
|
- demo/vendor/assets/stylesheets/.gitkeep
|
142
145
|
- demo/vendor/plugins/.gitkeep
|
@@ -146,14 +149,17 @@ files:
|
|
146
149
|
- modularity-rails.gemspec
|
147
150
|
- run_tests
|
148
151
|
- vendor/assets/javascripts/modularity.js.coffee
|
152
|
+
- vendor/assets/javascripts/modularity/data/ajax_loader.coffee
|
153
|
+
- vendor/assets/javascripts/modularity/data/cache.coffee
|
154
|
+
- vendor/assets/javascripts/modularity/data/indexed_cache.coffee
|
155
|
+
- vendor/assets/javascripts/modularity/data/persistence_manager.coffee
|
149
156
|
- vendor/assets/javascripts/modularity/mixins/clickable.coffee
|
150
157
|
- vendor/assets/javascripts/modularity/mixins/closable.coffee
|
151
158
|
- vendor/assets/javascripts/modularity/modules/autogrow_textarea.coffee
|
152
159
|
- vendor/assets/javascripts/modularity/modules/button.coffee
|
153
160
|
- vendor/assets/javascripts/modularity/modules/counter_button.coffee
|
154
|
-
- vendor/assets/javascripts/modularity/tools/ajax_loader.coffee
|
155
161
|
- vendor/assets/javascripts/modularity/tools/array_tools.coffee
|
156
|
-
- vendor/assets/javascripts/modularity/tools/
|
162
|
+
- vendor/assets/javascripts/modularity/tools/object_tools.coffee
|
157
163
|
homepage: http://github.com/kevgo/modularity-rails
|
158
164
|
licenses: []
|
159
165
|
post_install_message:
|
@@ -1,79 +0,0 @@
|
|
1
|
-
#= require spec_helper
|
2
|
-
#= require modularity/tools/ajax_loader
|
3
|
-
|
4
|
-
|
5
|
-
describe 'ajax_loader', ->
|
6
|
-
|
7
|
-
ajax_loader = spy = spy_get = null
|
8
|
-
beforeEach ->
|
9
|
-
ajax_loader = new modularity.AjaxLoader()
|
10
|
-
spy = sinon.spy()
|
11
|
-
spy_get = sinon.spy jQuery, 'get'
|
12
|
-
|
13
|
-
afterEach ->
|
14
|
-
spy_get.restore()
|
15
|
-
|
16
|
-
describe 'get', ->
|
17
|
-
|
18
|
-
url = "/users/4"
|
19
|
-
|
20
|
-
describe 'the data has already been loaded', ->
|
21
|
-
|
22
|
-
it 'calls the callback with the cached data', ->
|
23
|
-
ajax_loader.cache.add url, "my data"
|
24
|
-
|
25
|
-
ajax_loader.get(url, spy)
|
26
|
-
|
27
|
-
spy.should.have.been.called
|
28
|
-
spy.should.have.been.calledWith "my data"
|
29
|
-
|
30
|
-
|
31
|
-
describe 'the request is already in progress', ->
|
32
|
-
|
33
|
-
beforeEach ->
|
34
|
-
ajax_loader.cache.cache[url] = [spy]
|
35
|
-
|
36
|
-
|
37
|
-
it 'adds the callback to the callback array', ->
|
38
|
-
ajax_loader.cache.get(url).should.have.length 1
|
39
|
-
ajax_loader.cache.get(url)[0].should.equal spy
|
40
|
-
|
41
|
-
|
42
|
-
it 'returns without calling the callback', ->
|
43
|
-
spy.should.not.have.been.called
|
44
|
-
|
45
|
-
|
46
|
-
it 'does not make another ajax request', ->
|
47
|
-
jQuery.get.should.not.have.been.called
|
48
|
-
|
49
|
-
|
50
|
-
describe 'first time request', ->
|
51
|
-
|
52
|
-
beforeEach ->
|
53
|
-
ajax_loader.get url, spy
|
54
|
-
|
55
|
-
it 'makes an ajax request', ->
|
56
|
-
jQuery.get.should.have.been.called
|
57
|
-
|
58
|
-
|
59
|
-
it 'saves the callback for later', ->
|
60
|
-
ajax_loader.cache.get(url).should.have.length 1
|
61
|
-
ajax_loader.cache.get(url)[0].should.equal spy
|
62
|
-
|
63
|
-
it 'returns without calling the callback', ->
|
64
|
-
spy.should.not.have.been.called
|
65
|
-
|
66
|
-
|
67
|
-
describe 'ajax request successful', ->
|
68
|
-
|
69
|
-
beforeEach ->
|
70
|
-
jquery_callback = null
|
71
|
-
jQuery.get = (url, callback) -> jquery_callback = callback
|
72
|
-
ajax_loader.get url, spy
|
73
|
-
jquery_callback('result')
|
74
|
-
|
75
|
-
it 'calls the given callbacks', ->
|
76
|
-
spy.should.have.been.calledWith 'result'
|
77
|
-
|
78
|
-
it 'replaces the cache callbacks with returned data', ->
|
79
|
-
ajax_loader.cache.get(url).should.equal 'result'
|