vault-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vault-rails.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Dennis Reimann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,6 @@
1
+ module Vault
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Vault
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "vault-rails/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "vault-rails"
7
+ s.version = Vault::Rails::VERSION
8
+ s.authors = ["Jordan MacDonald"]
9
+ s.email = ["jordan@wastedintelligence.com"]
10
+ s.homepage = 'https://github.com/cityofgreatersudbury/vault-rails'
11
+ s.summary = 'CoffeeScript collection class for offline use.'
12
+ s.description = 'Store and manage collections of objects without a connection.'
13
+
14
+ s.rubyforge_project = "vault-rails"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,521 @@
1
+ class Vault
2
+ constructor: (name, urls, options = {}) ->
3
+ # Setup some internal variables.
4
+ @objects = []
5
+ @dirty_object_count = 0
6
+ @errors = []
7
+ @save_error_count = 0
8
+
9
+ # This property is used to temporarily lock the vault during mutation methods.
10
+ @locked = false
11
+
12
+ # Create a date object which will be used to
13
+ # generate unique IDs for new records.
14
+ @date = new Date
15
+
16
+ # Import required parameters for the data store.
17
+ @name = name
18
+ @urls = urls
19
+
20
+ # Declare default options.
21
+ @options =
22
+ autoload: true
23
+ after_load: ->
24
+ id_attribute: "id"
25
+ offline: false
26
+ sub_collections: []
27
+
28
+ # Merge default options with user-defined ones.
29
+ for option, value of options
30
+ @options[option] = value
31
+
32
+ # Setup the vault for offline use.
33
+ if @options.offline
34
+ # Bind a cache routine to save data should the window be closed or url changed.
35
+ $(window).unload =>
36
+ @store()
37
+
38
+ # Load the collection if configured to do so.
39
+ if @options.autoload
40
+ # Check the offline data store first, if configured to do so.
41
+ if @options.offline
42
+ if @load()
43
+ if @dirty_object_count > 0
44
+ # Offline data loaded and modifications found; keep existing data.
45
+
46
+ # Extend the loaded objects with vault-specific variables and functions.
47
+ for object in @objects
48
+ @extend object
49
+
50
+ # Detach the callback to after_load so that the call to the
51
+ # vault constructor can complete/return, allowing any post-load code
52
+ # to use the newly instantiated vault object as required.
53
+ window.setTimeout @options.after_load, 100
54
+ else
55
+ # No modifications in offline data; reload fresh data.
56
+ @reload(@options.after_load)
57
+ else
58
+ if navigator.onLine
59
+ # Load failed, but we're connected; reload fresh data.
60
+ @reload(@options.after_load)
61
+ else
62
+ # Load failed and we're offline; log an error.
63
+ @errors.push "Offline data failed to load. Could not load live data as browser is offline."
64
+ else
65
+ @reload(@options.after_load)
66
+
67
+ # Create convenience attributes for sub-collections.
68
+ for sub_collection in @options.sub_collections
69
+ do (sub_collection) =>
70
+ @[sub_collection] = {
71
+ 'find': (id) =>
72
+ for object in @objects
73
+ for sub_object in object[sub_collection]
74
+ if sub_object[@options.id_attribute] is parseInt(id)
75
+ return sub_object
76
+
77
+ # Object with specified id couldn't be found.
78
+ return false
79
+ }
80
+
81
+ # Iterate over non-deleted items in the collection.
82
+ each: (logic) ->
83
+ for object in @objects
84
+ unless object.status == "deleted"
85
+ logic object
86
+
87
+ # Add a new item to the collection.
88
+ add: (object) ->
89
+ # Don't bother if the vault is locked.
90
+ if @locked
91
+ @errors.push 'Cannot add, vault is locked.'
92
+ return false
93
+
94
+ # If the object has no id, generate a temporary one and add it to the object.
95
+ unless object[@options.id_attribute]? and object[@options.id_attribute] isnt ''
96
+ object[@options.id_attribute] = @date.getTime()
97
+
98
+ # Extend the object with vault-specific variables and functions.
99
+ @extend object,"new"
100
+
101
+ # Add the object to the collection.
102
+ @objects.push object
103
+
104
+ # Increase the count of dirty objects.
105
+ @dirty_object_count++
106
+
107
+ # Store the collection.
108
+ @store
109
+
110
+ # Return the extended object.
111
+ return object
112
+
113
+ # Find an object in the collection using its id.
114
+ find: (id) ->
115
+ for object in @objects
116
+ if object[@options.id_attribute] == parseInt(id)
117
+ return object
118
+
119
+ # Object with specified id couldn't be found.
120
+ return false
121
+
122
+ # Update an existing item in the collection.
123
+ update: (attributes, id) ->
124
+ # Don't bother if the vault is locked.
125
+ if @locked
126
+ @errors.push 'Cannot update, vault is locked.'
127
+ return false
128
+
129
+ # Get the id of the object from the attributes if it's not explicitly defined.
130
+ id = attributes[@options.id_attribute] unless id?
131
+
132
+ # Get the object; return if it's undefined.
133
+ object = @find(id)
134
+ unless object?
135
+ @errors.push 'Cannot update, object not found.'
136
+ return false
137
+
138
+ # Flag it as dirty.
139
+ if object.status is "clean"
140
+ object.status = "dirty"
141
+ @dirty_object_count++
142
+
143
+ # Merge in the updated attributes, if they're specified and defined on the object.
144
+ if attributes?
145
+ for attribute, value of attributes
146
+ if object[attribute]? and attribute isnt @options['id_attribute']
147
+ object[attribute] = value
148
+
149
+ # Store the collection.
150
+ @store
151
+
152
+ # Update was successful.
153
+ return true
154
+
155
+ # Flag an object in the collection for deletion,
156
+ # or if the object is new, remove it.
157
+ delete: (id) ->
158
+ # Don't bother if the vault is locked.
159
+ if @locked
160
+ @errors.push 'Cannot delete, vault is locked.'
161
+ return false
162
+
163
+ for object, index in @objects
164
+ if object[@options.id_attribute] == id
165
+ switch object.status
166
+ when "new"
167
+ # New objects are special; we essentially want to
168
+ # reverse the steps taken during the add operation.
169
+ @objects.splice(index, 1)
170
+ @dirty_object_count--
171
+ when "clean"
172
+ object.status = "deleted"
173
+ @dirty_object_count++
174
+ when "dirty"
175
+ object.status = "deleted"
176
+
177
+ # Store the collection.
178
+ @store
179
+
180
+ # Delete was successful.
181
+ return true
182
+
183
+ # Object not found.
184
+ return false
185
+
186
+ # Write an object back to the server.
187
+ save: (id, after_save = ->) ->
188
+ # Don't bother if the vault is locked, we're offline or there's nothing to sync.
189
+ if @locked
190
+ @errors.push 'Cannot save, vault is locked.'
191
+ return after_save()
192
+ else if not navigator.onLine
193
+ @errors.push 'Cannot save, navigator is offline.'
194
+ return after_save()
195
+ else if @dirty_object_count is 0
196
+ @errors.push 'Nothing to save.'
197
+ return after_save()
198
+
199
+ # Lock the vault until the save is complete.
200
+ @locked = true
201
+
202
+ # Find the object using the specified id.
203
+ object = @find(id)
204
+
205
+ switch object.status
206
+ when "deleted"
207
+ $.ajax
208
+ type: 'DELETE'
209
+ url: @urls.delete
210
+ data: @strip object
211
+ fixture: (settings) ->
212
+ return true
213
+ success: (data) =>
214
+ # Forcibly remove the deleted object from the collection.
215
+ for vault_object, index in @objects
216
+ if vault_object.id == object.id
217
+ @objects.splice(index, 1)
218
+ @dirty_object_count--
219
+ error: =>
220
+ @errors.push 'Failed to delete.'
221
+ complete: =>
222
+ # Store the collection, unlock the vault, and execute the callback method.
223
+ @store
224
+ @locked = false
225
+ after_save()
226
+ dataType: 'json'
227
+ when "new"
228
+ $.ajax
229
+ type: 'POST'
230
+ url: @urls.create
231
+ data: @strip object
232
+ fixture: (settings) =>
233
+ settings.data.id = @date.getTime()
234
+
235
+ return settings.data
236
+ success: (data) =>
237
+ # Replace the existing object with the new one from the server and extend it.
238
+ object = @extend data # This will also set its status to clean.
239
+ @dirty_object_count--
240
+ error: =>
241
+ @errors.push 'Failed to create.'
242
+ complete: =>
243
+ # Store the collection, unlock the vault, and execute the callback method.
244
+ @store
245
+ @locked = false
246
+ after_save()
247
+ dataType: 'json'
248
+ when "dirty"
249
+ $.ajax
250
+ type: 'POST'
251
+ url: @urls.update
252
+ data: @strip object
253
+ fixture: (settings) ->
254
+ return true
255
+ success: (data) =>
256
+ object.status = "clean"
257
+ @dirty_object_count--
258
+ error: =>
259
+ @errors.push 'Failed to update.'
260
+ complete: =>
261
+ # Store the collection, unlock the vault, and execute the callback method.
262
+ @store
263
+ @locked = false
264
+ after_save()
265
+ dataType: 'json'
266
+
267
+ # Used to wipe out the in-memory object list with a fresh one from the server.
268
+ reload: (after_load = ->) ->
269
+ # Don't bother if the vault is locked or we're offline.
270
+ if @locked
271
+ @errors.push 'Cannot reload, vault is locked.'
272
+ return after_load()
273
+ else if not navigator.onLine
274
+ @errors.push 'Cannot reload, navigator is offline.'
275
+ return after_load()
276
+
277
+ # Lock the vault until the reload is complete.
278
+ @locked = true
279
+
280
+ $.ajax
281
+ url: @urls.list
282
+ dataType: 'json'
283
+ success: (data) =>
284
+ # Replace the list of in-memory objects with the new data.
285
+ @objects = data
286
+
287
+ # Extend the objects with vault-specific variables and functions.
288
+ for object in @objects
289
+ @extend object
290
+
291
+ # Reset the count of dirty objects.
292
+ @dirty_object_count = 0
293
+
294
+ # Store the collection.
295
+ @store
296
+
297
+ # Call the callback function as the reload is complete.
298
+ after_load()
299
+ error: =>
300
+ @errors.push 'Failed to list.'
301
+
302
+ # Call the callback function as the reload is complete (albeit unsuccessful).
303
+ after_load()
304
+ complete: =>
305
+ # Unlock the vault as the reload is complete.
306
+ @locked = false
307
+
308
+ # Convenience method for saving and reloading in one shot.
309
+ synchronize: (after_sync = ->) ->
310
+ # Don't bother if we're offline.
311
+ unless navigator.onLine
312
+ @errors.push 'Cannot synchronize, navigator is offline.'
313
+ return after_sync()
314
+
315
+ @save =>
316
+ # Only reload the collection if there were no save errors.
317
+ if @errors.length is 0
318
+ @reload(after_sync)
319
+ else
320
+ after_sync()
321
+
322
+ # Load the collection from offline storage.
323
+ load: ->
324
+ # Don't bother if offline support is disabled.
325
+ unless @options.offline
326
+ return false
327
+
328
+ # Try to load the collection.
329
+ if localStorage.getItem(@name)
330
+ @objects = $.parseJSON(localStorage.getItem @name)
331
+
332
+ # Calculate the number of dirty objects.
333
+ for object in @objects
334
+ unless object.status == "clean"
335
+ @dirty_object_count++
336
+
337
+ return true
338
+ else
339
+ return false
340
+
341
+ # Store the collection for offline use.
342
+ store: ->
343
+ # Don't bother if offline support is disabled.
344
+ unless @options.offline
345
+ return false
346
+
347
+ # Store the collection.
348
+ localStorage.setItem(@name, JSON.stringify(@objects))
349
+ return true
350
+
351
+ # Extend an object with vault-specific variables and functions.
352
+ extend: (object, status="clean") ->
353
+
354
+ # Add simple variables and methods.
355
+ object.status = status
356
+ object.update = (attributes) =>
357
+ @update(attributes, object.id)
358
+ object.delete = =>
359
+ @delete(object.id)
360
+ object.save = (after_save) =>
361
+ @save(object.id, after_save)
362
+
363
+ # Iterate through all of the sub-collections, and if present
364
+ # extend them with some basic functionality.
365
+ for sub_collection in @options.sub_collections
366
+ do (sub_collection) =>
367
+ if object[sub_collection]?
368
+ # Find functionality.
369
+ object[sub_collection].find = (id) =>
370
+ for sub_collection_object in object[sub_collection]
371
+ if sub_collection_object[@options.id_attribute] is parseInt(id)
372
+ return sub_collection_object
373
+
374
+ # Object with specified id couldn't be found.
375
+ return false
376
+
377
+ # Add functionality.
378
+ object[sub_collection].add = (sub_object) =>
379
+ # Don't bother if the vault is locked.
380
+ if @locked
381
+ @errors.push 'Cannot add sub-object, vault is locked.'
382
+ return false
383
+
384
+ # Set a status on the object.
385
+ sub_object.status = "new"
386
+
387
+ # If the sub-object has no id, generate a temporary one and add it to the sub-object.
388
+ unless sub_object[@options.id_attribute]? and sub_object[@options.id_attribute] isnt ''
389
+ sub_object[@options.id_attribute] = @date.getTime()
390
+
391
+ # Add a delete method to the sub-object.
392
+ sub_object.delete = =>
393
+ object[sub_collection].delete(sub_object[@options.id_attribute])
394
+
395
+ # Add an update method to the sub-object.
396
+ sub_object.update = (attributes) =>
397
+ object[sub_collection].update(attributes, sub_object[@options.id_attribute])
398
+
399
+ # Add the object to the collection.
400
+ object[sub_collection].push sub_object
401
+
402
+ # If the root object was clean, flag it and increase the count of dirty objects.
403
+ if object.status is "clean"
404
+ object.status = "dirty"
405
+ @dirty_object_count++
406
+
407
+ # Store the collection.
408
+ @store
409
+
410
+ return sub_object
411
+
412
+ # Delete functionality.
413
+ object[sub_collection].delete = (id) =>
414
+ # Don't bother if the vault is locked.
415
+ if @locked
416
+ @errors.push 'Cannot delete sub-object, vault is locked.'
417
+ return false
418
+
419
+ # Remove the sub-object from its collection.
420
+ for sub_object, index in object[sub_collection]
421
+ if sub_object[@options.id_attribute] is id
422
+ object[sub_collection].splice(index, 1)
423
+
424
+ # If the root object was clean, flag it and increase the count of dirty objects.
425
+ if object.status is "clean"
426
+ object.status = "dirty"
427
+ @dirty_object_count++
428
+
429
+ # Store the collection.
430
+ @store
431
+
432
+ # Add a delete instance method for pre-existing objects.
433
+ for sub_object, index in object[sub_collection]
434
+ sub_object.delete = =>
435
+ object[sub_collection].delete(sub_object[@options.id_attribute])
436
+
437
+ # Update functionality.
438
+ object[sub_collection].update = (attributes, id) =>
439
+ # Don't bother if the vault is locked.
440
+ if @locked
441
+ @errors.push 'Cannot update sub-object, vault is locked.'
442
+ return false
443
+
444
+ # Get the id of the sub-object from the attributes if it's not explicitly defined.
445
+ id = attributes[@options.id_attribute] unless id?
446
+
447
+ # Get the sub-object; return if it's undefined.
448
+ sub_object = object[sub_collection].find(id)
449
+ unless sub_object?
450
+ @errors.push 'Cannot update, sub-object not found.'
451
+ return false
452
+
453
+ # If the root object was clean, flag it and increase the count of dirty objects.
454
+ if object.status is "clean"
455
+ object.status = "dirty"
456
+ @dirty_object_count++
457
+
458
+ # Merge in the updated attributes, if they're specified and defined on the sub-object.
459
+ if attributes?
460
+ for attribute, value of attributes
461
+ if sub_object[attribute]? and attribute isnt @options['id_attribute']
462
+ sub_object[attribute] = value
463
+
464
+ # Store the collection.
465
+ @store
466
+
467
+ # Add an update instance method for pre-existing objects.
468
+ for sub_object in object[sub_collection]
469
+ do (sub_object) =>
470
+ sub_object.update = (attributes) =>
471
+ object[sub_collection].update(attributes, sub_object[@options.id_attribute])
472
+
473
+ return object
474
+
475
+ # Return a copy of an object with vault-specific variables and functions removed.
476
+ strip: (object) ->
477
+ # Clone the object so that we don't strip the original.
478
+ object_clone = @clone object
479
+
480
+ # Remove the temporary id given to new objects.
481
+ if object_clone.status is "new"
482
+ delete object_clone[@options.id_attribute]
483
+
484
+ delete object_clone.status
485
+ delete object_clone.update
486
+ delete object_clone.delete
487
+ delete object_clone.save
488
+
489
+ # Iterate through all of the sub-collections, and if present
490
+ # strip them of their extended functionality.
491
+ for sub_collection in @options.sub_collections
492
+ if object_clone[sub_collection]?
493
+ # Remove the sub-collection's methods.
494
+ delete object_clone[sub_collection].find
495
+ delete object_clone[sub_collection].add
496
+ delete object_clone[sub_collection].delete
497
+ delete object_clone[sub_collection].update
498
+
499
+ # Iterate through and remove the extended instances' methods.
500
+ for sub_object in object_clone[sub_collection]
501
+ if sub_object.status is "new"
502
+ delete sub_object[@options.id_attribute]
503
+ delete sub_object.status
504
+ delete sub_object.delete
505
+ delete sub_object.update
506
+ return object_clone
507
+
508
+ # Clone (deep copy) an object.
509
+ clone: (object) ->
510
+ unless object? and typeof object is 'object'
511
+ return object
512
+
513
+ new_instance = new object.constructor()
514
+
515
+ for key of object
516
+ new_instance[key] = @clone object[key]
517
+
518
+ return new_instance
519
+
520
+ # Attach the Vault class to the window so that it can be used by other scripts.
521
+ window.Vault = this
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vault-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jordan MacDonald
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-21 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Store and manage collections of objects without a connection.
15
+ email:
16
+ - jordan@wastedintelligence.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - Rakefile
25
+ - lib/vault-rails.rb
26
+ - lib/vault-rails/version.rb
27
+ - vault-rails.gemspec
28
+ - vendor/assets/javascripts/vault.js.coffee
29
+ homepage: https://github.com/cityofgreatersudbury/vault-rails
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project: vault-rails
49
+ rubygems_version: 1.8.11
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: CoffeeScript collection class for offline use.
53
+ test_files: []