vault-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []