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.
@@ -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/tools/cache
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('foo', 'bar')
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,3 +1,3 @@
1
1
  module ModularityRails
2
- VERSION = "0.11.1"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -1,4 +1,4 @@
1
- #= require modularity/tools/cache
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
- @cache.add url, data
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
- # NOTE(KG): This doesn't work in IE8.
38
- Object.keys(@cache).length
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.11.1
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-18 00:00:00.000000000 Z
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: &70319159978280 !ruby/object:Gem::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: *70319159978280
24
+ version_requirements: *70234292434960
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: capybara-webkit
27
- requirement: &70319159977360 !ruby/object:Gem::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: *70319159977360
35
+ version_requirements: *70234292434440
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: evergreen
38
- requirement: &70319159976860 !ruby/object:Gem::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: *70319159976860
46
+ version_requirements: *70234292433920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rb-fsevent
49
- requirement: &70319159976360 !ruby/object:Gem::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: *70319159976360
57
+ version_requirements: *70234292433260
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: guard-livereload
60
- requirement: &70319159975780 !ruby/object:Gem::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: *70319159975780
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/cache_spec.coffee
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/cache.coffee
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'