slender_data 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1deea5d8aa969cdc2e213cb39e50b526a00615c0
4
+ data.tar.gz: 5b89a6bd314958eef91ddf872db9d10763f609d9
5
+ SHA512:
6
+ metadata.gz: b3df0aa35a568a60aa088320e254183d632357f5ecee53b0f47a2ba900113a3fb546e6220f3b50890388f8e67e87c71c5e71db3a2f55563dd3c1c7e4ab14ccf1
7
+ data.tar.gz: 916850fc041b71ae70fc6c9d97308a7713f78d4ac41f6d4a7ac324f120db9f604f5c5fe6bb20f1c25da2b023822759b42bb745eacd6e5f4e2f665608324fe38c
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SlenderData'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ Bundler::GemHelper.install_tasks
22
+
23
+ task :default => 'app:konacha:run'
@@ -0,0 +1,76 @@
1
+ #= require slender_data/cache
2
+
3
+ # A generic ajax loader for parallel GET requests.
4
+ # Prevents duplicate requests, caches the responses
5
+ # to answer subsequent requests immediately
6
+ # without additional server requests.
7
+ #
8
+ # Warning: Caches the responses, so once a request is cached,
9
+ # any new content on the same URL will not be visible!
10
+ class slender_data.AjaxLoader
11
+
12
+ constructor: (params = {}) ->
13
+
14
+ # Caches the callbacks.
15
+ @cb_cache = new slender_data.Cache()
16
+
17
+ # Whether to perform caching of data.
18
+ # Default: no.
19
+ @caching = !!params.caching
20
+
21
+ # Caches the data loaded.
22
+ @data_cache = new slender_data.Cache()
23
+
24
+ # The number of currently running loading operations.
25
+ @loader_count = 0
26
+
27
+
28
+ # The different events that this class can fire.
29
+ @events =
30
+ AJAX_LOADING: 'AJAX_LOADING'
31
+ AJAX_LOADED: 'AJAX_LOADED'
32
+
33
+
34
+ # Loads the data from the given URL asynchronously.
35
+ get: (url, callback) ->
36
+
37
+ # Return immediately if we have cached data.
38
+ if @caching
39
+ cached_data = @data_cache.get url
40
+ return callback(cached_data) if cached_data?
41
+
42
+ # Here, we have no cached data. Check
43
+
44
+ # If a GET call to this URL is already in progress -->
45
+ # add the given callback to the list of waiting callbacks.
46
+ request_in_progress = @cb_cache.get url
47
+ return request_in_progress.push(callback) if request_in_progress?
48
+
49
+ # Here, no request is currently in progress --> start a new one.
50
+
51
+ # Add callback to callback list.
52
+ @cb_cache.add url, [callback]
53
+
54
+ # Fire 'loading' event if this is the start of a loading operation.
55
+ @loader_count++
56
+ if @loader_count == 1
57
+ $('body').trigger slender_data.AjaxLoader.events.AJAX_LOADING
58
+
59
+ # Perform the request.
60
+ jQuery.get url, (data) =>
61
+
62
+ # Add result to cache.
63
+ if @caching
64
+ @data_cache.add url, data
65
+
66
+ # Call callbacks.
67
+ cb(data) for cb in @cb_cache.get url
68
+
69
+ # Remove request from callback list.
70
+ @cb_cache.remove url
71
+
72
+ # Fire 'loaded' event.
73
+ @loader_count--
74
+ if @loader_count == 0
75
+ $('body').trigger slender_data.AjaxLoader.events.AJAX_LOADED
76
+
@@ -0,0 +1,63 @@
1
+ # A generic cache.
2
+ # Stores key-value pairs.
3
+ class slender_data.Cache
4
+
5
+ constructor: ->
6
+
7
+ # Container for the cached objects.
8
+ @cache = {}
9
+
10
+
11
+ # Adds the given entry to the cache.
12
+ # Overwrites existing entries.
13
+ add: (key, value) ->
14
+ @cache[key] = value
15
+
16
+
17
+ # Returns the entry with the given key from the cache, or NULL if no entry exists.
18
+ get: (key) ->
19
+ @cache[key]
20
+
21
+
22
+ # Looks up several entries at once.
23
+ # Returns a hash of found entries, and a list of missing entries.
24
+ get_many: (keys) ->
25
+ result = { found: {}, missing: [] }
26
+ for key in keys
27
+ do (key) =>
28
+ value = @cache[key]
29
+ if value
30
+ result.found[key] = value
31
+ else
32
+ result.missing.push key
33
+ result
34
+ getMany: Cache::get_many
35
+
36
+
37
+ # Returns the number of cached objects.
38
+ length: () ->
39
+ slender_data.object_length @cache
40
+
41
+
42
+ # Removes the entry with the given key.
43
+ remove: (key) =>
44
+ delete @cache[key]
45
+
46
+
47
+ # Removes all entries with the given keys.
48
+ remove_many: (keys) ->
49
+ @remove(key) for key in keys
50
+
51
+
52
+ # Replaces the cache with the given data.
53
+ # When 'key' is given, treats 'data' as an array of objects, and indexes each element by the given key.
54
+ # When 'key' is not given, treats 'data' as an already indexed hash object.
55
+ replace_all: (data, key) ->
56
+ if key
57
+ # Key given --> index the data array.
58
+ @add(entry[key], entry) for entry in data
59
+ else
60
+ # Key not given --> use data as the new cache.
61
+ @cache = data
62
+ replaceAll: Cache::replace_all
63
+
@@ -0,0 +1,32 @@
1
+ #= require slender_data/cache
2
+
3
+ # Provides fast and convenient retrieval of hash objects
4
+ # by indexing them on a given key column.
5
+ class slender_data.IndexedCache
6
+
7
+ constructor: (@key = 'id') ->
8
+ @cache = new slender_data.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_id) ->
20
+ @cache.remove entry_id
21
+
22
+
23
+ remove_many: (entry_ids) ->
24
+ @cache.remove_many entry_ids
25
+
26
+
27
+ get: (key) ->
28
+ @cache.get key
29
+
30
+
31
+ length: => @cache.length()
32
+
@@ -0,0 +1,239 @@
1
+ #= require slender_data/ajax_loader
2
+ #= require slender_data/indexed_cache
3
+ #= require slender_data/tools
4
+
5
+ # Provides persistence services for data models.
6
+ #
7
+ # This class operates against a standard RESTful data API, whole path is given in params.url.
8
+ # In addition to the standard RESTful routes, the data API is expected to support these methods for performance reasons:
9
+ # * DELETE /entries: Delete a whole batch of entries at once. The ids of the entries to delete are given as a JSON hash in the body of the request.
10
+ # *
11
+ class slender_data.PersistenceManager
12
+
13
+ constructor: (params) ->
14
+
15
+ # Copy of the data as it is on the server.
16
+ @server_data = new slender_data.IndexedCache
17
+
18
+ # Copy of the data as it is on the client.
19
+ @client_data = new slender_data.IndexedCache
20
+
21
+ # The base url on the server. Expected to be a fully RESTful API.
22
+ @base_url = params.url
23
+
24
+ # Name of the 'id' column for models.
25
+ @key = params.key or 'id'
26
+
27
+ # Name of the model class (e.g. "user").
28
+ @model_name = params.model_name
29
+
30
+ # For handling parallel requests to the server.
31
+ @loader = new slender_data.AjaxLoader { cache: no }
32
+
33
+
34
+ # Adds the given data objects to the server cache.
35
+ add_all: (data) ->
36
+ @server_data.add_all data
37
+
38
+
39
+ # Returns an array containing only the changed objects.
40
+ # Each object contains only the changed columns + key column.
41
+ changed_objects_columns: (objects) ->
42
+ slender_data.tap [], (result) =>
43
+ for object in objects
44
+ diff = slender_data.object_diff @server_data.get(object[@key]), object
45
+ continue if slender_data.object_length(diff) == 0
46
+ diff[@key] = object[@key]
47
+ result.push(diff)
48
+
49
+
50
+ # Returns the URL to access the collection of objects.
51
+ collection_url: ->
52
+ "#{@base_url}.json"
53
+
54
+
55
+ # Creates the given object on the server.
56
+ create: (obj, success_callback, error_callback) ->
57
+ data = {}
58
+ data[@model_name] = obj
59
+
60
+ jQuery.ajax
61
+ url: @collection_url()
62
+ type: 'POST'
63
+ data: data
64
+ success: (server_response) =>
65
+ server_obj = server_response[@model_name]
66
+ @server_data.add server_obj
67
+ success_callback server_obj
68
+ error: (xhr) =>
69
+ error_callback(xhr.responseText) if error_callback
70
+
71
+
72
+ # Deletes the given object from the server.
73
+ delete: (obj, success_callback, error_callback) ->
74
+ @client_data.remove obj
75
+ @server_data.remove obj
76
+ jQuery.ajax
77
+ url: @entry_url(obj)
78
+ type: 'DELETE'
79
+ success: ->
80
+ success_callback() if success_callback?
81
+ error: (xhr) ->
82
+ error_callback(xhr.responseText) if error_callback
83
+
84
+
85
+ # Deletes the given objects from the server.
86
+ delete_many: (object_ids, success_callback, error_callback) ->
87
+ @client_data.remove_many object_ids
88
+ @server_data.remove_many object_ids
89
+ jQuery.ajax
90
+ url: @base_url
91
+ type: 'DELETE'
92
+ data: {ids: object_ids}
93
+ success: ->
94
+ success_callback() if success_callback?
95
+ error: (xhr) ->
96
+ error_callback(xhr.responseText) if error_callback
97
+
98
+
99
+ # Returns the url to access a single entry.
100
+ entry_url: (entry) ->
101
+ "#{@base_url}/#{entry[@key]}.json"
102
+
103
+
104
+ # Returns the url to access the collection of entries.
105
+ entries_url: ->
106
+ "#{@base_url}.json"
107
+
108
+
109
+ # Returns the cached data object, or undefined.
110
+ get_cached: (key) ->
111
+
112
+ # Try to use client_data cache.
113
+ client_obj = @client_data.get key
114
+ return client_obj if client_obj
115
+
116
+ # No data in client cache --> try to use server cache.
117
+ server_obj = @server_data.get key
118
+ if server_obj
119
+ client_obj = slender_data.clone_hash server_obj
120
+ @client_data.add client_obj
121
+ return client_obj
122
+
123
+ # Object not found in client or server cache.
124
+ return undefined
125
+
126
+
127
+ # Returns the entry with the given key.
128
+ load: (key, success_callback) ->
129
+
130
+ # Try to load from cache.
131
+ return success_callback(entry) if entry = @get_cached key
132
+
133
+ # No data on client at all --> load data from server.
134
+ @loader.get "#{@base_url}/#{key}", (server_entry) =>
135
+ @server_data.add server_entry
136
+ client_entry = slender_data.clone_hash server_entry
137
+ @client_data.add client_entry
138
+ success_callback client_entry
139
+
140
+
141
+ # Loads all objects from the server.
142
+ # Provides the given params as parameters to the GET request.
143
+ load_all: (params, success_callback, error_callback) ->
144
+ jQuery.ajax
145
+ url: @collection_url()
146
+ cache: no
147
+ data: params
148
+ success: (data) =>
149
+ @server_data.add_all data
150
+ success_callback() if success_callback
151
+ error: (xhr) ->
152
+ error_callback(xhr.responseText) if error_callback
153
+
154
+
155
+ # Loads all entries with the given ids.
156
+ load_many: (ids, success_callback) ->
157
+ missing_ids = []
158
+ entries = []
159
+ $.each ids, (pos, id) =>
160
+ entry = @get_cached id
161
+ if entry
162
+ entries.push entry
163
+ else
164
+ missing_ids.push id
165
+
166
+ if missing_ids.length == 0
167
+ return success_callback(entries)
168
+
169
+ alert "uncached entries found: #{missing_ids}"
170
+
171
+
172
+ # Saves the given object.
173
+ # Does the right thing (create or update) dependent on
174
+ # whether the object already has a key parameter.
175
+ save: (obj, success_callback, error_callback) ->
176
+ if obj[@key]?
177
+ @update obj, success_callback, error_callback
178
+ else
179
+ @create obj, success_callback, error_callback
180
+
181
+
182
+ # Updates the given object.
183
+ #
184
+ # The given object must exist on the server already,
185
+ # and have a proper value in the key attribute.
186
+ update: (obj, success_callback, error_callback) ->
187
+
188
+ # Handle updating several objects.
189
+ return @update_many(obj, success_callback, error_callback) if $.type(obj) == 'array'
190
+
191
+ # Create a new hash, containing only the changed attributes between obj and it's replica in @server_data.
192
+ diff_obj = slender_data.object_diff @server_data.get(obj[@key]), obj
193
+ if slender_data.object_length(diff_obj) == 0
194
+ success_callback obj if success_callback
195
+ return
196
+
197
+ # Send to server
198
+ data = {}
199
+ data[@model_name] = diff_obj
200
+ jQuery.ajax
201
+ url: @entry_url(obj)
202
+ type: 'PUT'
203
+ data: data
204
+ success: (server_response) =>
205
+ server_obj = server_response[@model_name]
206
+ @server_data.add server_obj
207
+ client_obj = slender_data.clone_hash server_obj
208
+ @client_data.add client_obj
209
+ success_callback client_obj if success_callback
210
+ error: (xhr) ->
211
+ error_callback(xhr.responseText) if error_callback
212
+
213
+
214
+ # Bulk-updates the given objects.
215
+ # All the existing objects must exist on the server already,
216
+ # and have a value in the key attribute.
217
+ update_many: (objects, success_callback, error_callback) ->
218
+
219
+ # Find all objects that have been changed on the client.
220
+ diff_objects = @changed_objects_columns objects
221
+
222
+ # Send to server
223
+ data = {}
224
+ data["#{@model_name}s"] = diff_objects
225
+ jQuery.ajax
226
+ url: @entries_url()
227
+ type: 'PUT'
228
+ data: data
229
+ success: (server_response) =>
230
+ server_objects = server_response["#{@model_name}s"]
231
+ client_objects = (for server_object in server_objects
232
+ @server_data.add server_object
233
+ client_object = slender_data.clone_hash server_object
234
+ @client_data.add client_object
235
+ client_object)
236
+ success_callback(client_objects) if success_callback
237
+ error: (xhr) ->
238
+ error_callback(xhr.responseText) if error_callback
239
+
@@ -0,0 +1,31 @@
1
+ # Returns a replica of the given hash.
2
+ # Don't use this method for real objects with superclasses, prototypes, and stuff.
3
+ slender_data.clone_hash = (obj) ->
4
+ result = {}
5
+ result[key] = value for own key, value of obj
6
+ result
7
+
8
+
9
+ # Returns an object that contains only the attributes
10
+ # that are different between obj_1 and obj_2.
11
+ # Only looks for changed attributes, not missing attributes.
12
+ slender_data.object_diff = (obj_1, obj_2) ->
13
+ result = {}
14
+ for own key, value_2 of obj_2
15
+ do (key, value_2) ->
16
+ value_1 = obj_1[key]
17
+ result[key] = value_2 if value_1 != value_2
18
+ result
19
+
20
+
21
+ # Returns the number of attributes of the given object.
22
+ # NOTE(KG): This doesn't work in IE8.
23
+ slender_data.object_length = (obj) ->
24
+ Object.keys(obj).length
25
+
26
+
27
+ # Allows to populate the given object through a callback.
28
+ slender_data.tap = (obj, callback) ->
29
+ callback obj
30
+ obj
31
+
@@ -0,0 +1,8 @@
1
+ //= require_self
2
+ //= require ./slender_data/tools
3
+ //= require ./slender_data/cache
4
+ //= require ./slender_data/indexed_cache
5
+ //= require ./slender_data/ajax_loader
6
+ //= require ./slender_data/persistence_manager
7
+
8
+ window.slender_data = window.slender_data || {}
@@ -0,0 +1,5 @@
1
+ module SlenderData
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SlenderData
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module SlenderData
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "slender_data/engine"
2
+
3
+ module SlenderData
4
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :slender_data do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slender_data
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Goslar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: coffee-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-livereload
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: selenium-webdriver
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: uglifier
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Provides simple and extensible data management services to JavaScript
112
+ applications.
113
+ email:
114
+ - kevin.goslar@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - app/assets/javascripts/slender_data/ajax_loader.coffee
120
+ - app/assets/javascripts/slender_data/cache.coffee
121
+ - app/assets/javascripts/slender_data/indexed_cache.coffee
122
+ - app/assets/javascripts/slender_data/persistence_manager.coffee
123
+ - app/assets/javascripts/slender_data/tools.coffee
124
+ - app/assets/javascripts/slender_data.js
125
+ - lib/slender_data/engine.rb
126
+ - lib/slender_data/version.rb
127
+ - lib/slender_data.rb
128
+ - lib/tasks/slender_data_tasks.rake
129
+ - MIT-LICENSE
130
+ - Rakefile
131
+ homepage: http://github.com/kevgo/slender_data
132
+ licenses: []
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.0.3
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: A JS micro-framework for lightweight AJAX data management against RESTful
154
+ APIs.
155
+ test_files: []