modularity-rails 0.11.1 → 0.12.0

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.
@@ -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'