persistence-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/README.md +36 -0
  4. data/Rakefile +2 -0
  5. data/lib/generators/persistence/install_generator.rb +22 -0
  6. data/lib/generators/persistence/templates/application.js +10 -0
  7. data/lib/persistence-rails.rb +1 -0
  8. data/lib/persistence/rails.rb +8 -0
  9. data/lib/persistence/rails/engine.rb +6 -0
  10. data/lib/persistence/rails/version.rb +5 -0
  11. data/persistence-rails.gemspec +17 -0
  12. data/vendor/assets/javascript/persistence.all.js +16 -0
  13. data/vendor/assets/javascript/persistence.core.js +2419 -0
  14. data/vendor/assets/javascript/persistence.jquery.js +103 -0
  15. data/vendor/assets/javascript/persistence.jquery.mobile.js +256 -0
  16. data/vendor/assets/javascript/persistence.js +5 -0
  17. data/vendor/assets/javascript/persistence.migrations.js +303 -0
  18. data/vendor/assets/javascript/persistence.pool.js +47 -0
  19. data/vendor/assets/javascript/persistence.search.js +293 -0
  20. data/vendor/assets/javascript/persistence.store.appengine.js +412 -0
  21. data/vendor/assets/javascript/persistence.store.config.js +29 -0
  22. data/vendor/assets/javascript/persistence.store.memory.js +239 -0
  23. data/vendor/assets/javascript/persistence.store.mysql.js +127 -0
  24. data/vendor/assets/javascript/persistence.store.sql.js +900 -0
  25. data/vendor/assets/javascript/persistence.store.sqlite.js +123 -0
  26. data/vendor/assets/javascript/persistence.store.sqlite3.js +124 -0
  27. data/vendor/assets/javascript/persistence.store.titanium.js +193 -0
  28. data/vendor/assets/javascript/persistence.store.websql.js +218 -0
  29. data/vendor/assets/javascript/persistence.sync.js +353 -0
  30. metadata +76 -0
@@ -0,0 +1,353 @@
1
+ //= require persistence.core
2
+
3
+ /**
4
+ * @license
5
+ * Copyright (c) 2010 Zef Hemel <zef@zef.me>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person
8
+ * obtaining a copy of this software and associated documentation
9
+ * files (the "Software"), to deal in the Software without
10
+ * restriction, including without limitation the rights to use,
11
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the
13
+ * Software is furnished to do so, subject to the following
14
+ * conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be
17
+ * included in all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26
+ * OTHER DEALINGS IN THE SOFTWARE.
27
+ */
28
+
29
+ if(!window.persistence) { // persistence.js not loaded!
30
+ throw new Error("persistence.js should be loaded before persistence.sync.js");
31
+ }
32
+
33
+ persistence.sync = {};
34
+
35
+ persistence.sync.getJSON = function(uri, callback, errorCallback) {
36
+ var xmlHttp = new XMLHttpRequest();
37
+ xmlHttp.open("GET", uri, true);
38
+ xmlHttp.send();
39
+ xmlHttp.onreadystatechange = function() {
40
+ if(xmlHttp.readyState==4) {
41
+ if(xmlHttp.status==200) {
42
+ callback(JSON.parse(xmlHttp.responseText));
43
+ } else if(typeof errorCallback === 'function') {
44
+ errorCallback(xmlHttp);
45
+ }
46
+ }
47
+ };
48
+ };
49
+
50
+ persistence.sync.postJSON = function(uri, data, callback) {
51
+ var xmlHttp = new XMLHttpRequest();
52
+ xmlHttp.open("POST", uri, true);
53
+ xmlHttp.setRequestHeader('Content-Type', 'application/json');
54
+ xmlHttp.send(data);
55
+ xmlHttp.onreadystatechange = function() {
56
+ if(xmlHttp.readyState==4 && xmlHttp.status==200) {
57
+ callback(JSON.parse(xmlHttp.responseText));
58
+ }
59
+ };
60
+ };
61
+
62
+
63
+ (function() {
64
+
65
+ var argspec = persistence.argspec;
66
+
67
+ persistence.sync.Sync = persistence.define('_Sync', {
68
+ entity: "VARCHAR(255)",
69
+ localDate: "BIGINT",
70
+ serverDate: "BIGINT",
71
+ serverPushDate: "BIGINT"
72
+ });
73
+
74
+ persistence.sync.RemovedObject = persistence.define('_SyncRemovedObject', {
75
+ entity: "VARCHAR(255)",
76
+ objectId: "VARCHAR(32)"
77
+ });
78
+
79
+
80
+ function getEpoch(date) {
81
+ return date.getTime();
82
+ }
83
+
84
+ persistence.sync.preferLocalConflictHandler = function(conflicts, updatesToPush, callback) {
85
+ conflicts.forEach(function(conflict) {
86
+ var update = {id: conflict.local.id};
87
+ conflict.properties.forEach(function(p) {
88
+ update[p] = conflict.local._data[p];
89
+ });
90
+ updatesToPush.push(update);
91
+ });
92
+ callback();
93
+ };
94
+
95
+ persistence.sync.preferRemoteConflictHandler = function(conflicts, updatesToPush, callback) {
96
+ conflicts.forEach(function(conflict) {
97
+ conflict.properties.forEach(function(p) {
98
+ conflict.local[p] = conflict.remote[p];
99
+ });
100
+ });
101
+ persistence.flush(callback);
102
+ };
103
+
104
+ function encodeUrlObj(obj) {
105
+ var parts = [];
106
+ for(var k in obj) {
107
+ if(obj.hasOwnProperty(k)) {
108
+ parts.push(encodeURI(k)+"="+encodeURI(obj[k]));
109
+ }
110
+ }
111
+ return "?" + parts.join("&");
112
+ }
113
+
114
+ /**
115
+ * The main caching and updating function, would be nice to refactor this
116
+ */
117
+ function cacheAndFindUpdates(session, Entity, objects, lastLocalSyncTime, lastServerPushTime, conflictCallback, callback) {
118
+ var ids = [];
119
+ var lookupTbl = {};
120
+
121
+ var conflicts = [];
122
+ var updatesToPush = [];
123
+ var meta = Entity.meta;
124
+ var fieldSpec = meta.fields;
125
+
126
+ var objectsToRemove = [];
127
+
128
+ objects.forEach(function(item) {
129
+ if(item._removed) { // special marker
130
+ objectsToRemove.push(item.id);
131
+ } else {
132
+ ids.push(item.id);
133
+ lookupTbl[item.id] = item;
134
+ }
135
+ });
136
+ // Step 1: Look at local versions of remotely updated entities
137
+ var existingItems = [], groupedIds = [];
138
+ for (var i=0,l=Math.floor((ids.length/100)+1);i<l;i++) {
139
+ groupedIds.push(ids.slice(i*100, i*100+100));
140
+ }
141
+ persistence.asyncForEach(groupedIds, function(idGroup, next) {
142
+ Entity.all(session).filter('id', 'in', idGroup).list(function(groupOfExistingItems) {
143
+ existingItems.concat(groupOfExistingItems);
144
+ next();
145
+ });
146
+ }, function() {
147
+ existingItems.forEach(function(localItem) {
148
+ var remoteItem = lookupTbl[localItem.id];
149
+ delete remoteItem.id;
150
+ delete lookupTbl[localItem.id];
151
+
152
+ var localChangedSinceSync = lastLocalSyncTime < localItem._lastChange;
153
+ var itemUpdatedFields = { id: localItem.id };
154
+ var itemUpdated = false;
155
+ var conflictingProperties = [];
156
+ for(var p in remoteItem) {
157
+ if(remoteItem.hasOwnProperty(p) && p !== '_lastChange') {
158
+ if(localItem._data[p] !== remoteItem[p]) {
159
+ if(localChangedSinceSync && remoteItem._lastChange === lastServerPushTime) {
160
+ // Unchanged at server, but changed locally
161
+ itemUpdatedFields[p] = localItem._data[p];
162
+ itemUpdated = true;
163
+ } else if(localChangedSinceSync) { // Conflict!
164
+ conflictingProperties.push(p);
165
+ } else {
166
+ localItem[p] = persistence.jsonToEntityVal(remoteItem[p], fieldSpec[p]);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ if(itemUpdated) {
172
+ updatesToPush.push(itemUpdatedFields);
173
+ }
174
+ if(conflictingProperties.length > 0) {
175
+ conflicts.push({local: localItem, remote: remoteItem, properties: conflictingProperties});
176
+ }
177
+ });
178
+ // Step 2: Remove all remotely removed objects
179
+ var groupedObjectsToRemove = [];
180
+ for (var i=0,l=Math.floor((objectsToRemove.length/100)+1);i<l;i++) {
181
+ groupedObjectsToRemove.push(objectsToRemove.slice(i*100, i*100+100));
182
+ }
183
+ persistence.asyncForEach(groupedObjectsToRemove, function(group, next) {
184
+ Entity.all(session).filter('id', 'in', group).destroyAll(next);
185
+ }, function() {
186
+ // Step 3: store new remote items locally
187
+ // NOTE: all that's left in lookupTbl is new, we deleted the existing items
188
+ for(var id in lookupTbl) {
189
+ if(lookupTbl.hasOwnProperty(id)) {
190
+ var remoteItem = lookupTbl[id];
191
+ delete remoteItem.id;
192
+ var localItem = new Entity(remoteItem);
193
+ localItem.id = id;
194
+ localItem._lastChange = getEpoch(new Date());
195
+ session.add(localItem);
196
+ }
197
+ }
198
+ // Step 4: Find local new/updated/removed items (not part of the remote change set)
199
+ Entity.all(session).filter("_lastChange", ">", lastLocalSyncTime).list(function(allNewItems) {
200
+ var newItems = [];
201
+ for (var i=0,l=allNewItems.length;i<l;i++) {
202
+ if (ids.indexOf(allNewItems[i].id)===-1) {
203
+ newItems.push(allNewItems[i]);
204
+ }
205
+ }
206
+ console.log("New items: ", newItems);
207
+ newItems.forEach(function(newItem) {
208
+ var update = { id: newItem.id };
209
+ for(var p in fieldSpec) {
210
+ if(fieldSpec.hasOwnProperty(p) && p != '_lastChange') {
211
+ update[p] = persistence.entityValToJson(newItem._data[p], fieldSpec[p]);
212
+ }
213
+ }
214
+ for(var p in meta.hasOne) {
215
+ if(meta.hasOne.hasOwnProperty(p)) {
216
+ update[p] = persistence.entityValToJson(newItem._data[p], fieldSpec[p]);
217
+ }
218
+ }
219
+ updatesToPush.push(update);
220
+ });
221
+ var removedObjColl = persistence.sync.RemovedObject.all(session).filter("entity", "=", meta.name);
222
+ removedObjColl.list(function(objs) {
223
+ objs.forEach(function(obj) {
224
+ updatesToPush.push({id: obj.objectId, _removed: true});
225
+ });
226
+ function next() {
227
+ removedObjColl.destroyAll(function() {
228
+ callback(updatesToPush);
229
+ });
230
+ }
231
+ if(conflicts.length > 0) {
232
+ conflictCallback(conflicts, updatesToPush, next);
233
+ } else {
234
+ next();
235
+ }
236
+ })
237
+ });
238
+ });
239
+ });
240
+ }
241
+
242
+ persistence.sync.serviceSync = function(session, uri, args, conflictCallback, callback) {
243
+ persistence.sync.Sync.findBy(session, 'service', uri, function(sync) {
244
+ var lastServerSyncTime = sync ? persistence.get(sync, 'serverDate') : 0;
245
+ var lastServerPushTime = sync ? persistence.get(sync, 'serverPushDate') : 0;
246
+ var lastLocalSyncTime = sync ? persistence.get(sync, 'localDate') : 0;
247
+ if(!sync) {
248
+ sync = new persistence.sync.Sync(session, {service: uri});
249
+ session.add(sync);
250
+ }
251
+ if(!args.since) args.since = lastServerSyncTime;
252
+ persistence.sync.getJSON(uri + encodeUrlObj(args), function(result) {
253
+ var allUpdates = [];
254
+ cacheAndFindUpdates(session, Entity, result.updates, lastLocalSyncTime, lastServerPushTime, conflictCallback, function(updatesToPush) {
255
+ persistence.sync.postJSON(uri, JSON.stringify(updatesToPush), function(pushData) {
256
+ session.flush(function() {
257
+ sync.localDate = getEpoch(new Date());
258
+ sync.serverDate = result.now;
259
+ sync.serverPushDate = pushData.now;
260
+ session.flush(callback);
261
+ });
262
+ });
263
+ });
264
+ });
265
+ });
266
+ session.flush(function() {
267
+ persistence.sync.getJSON(uri + encodeUrlObj(args), function(result) {
268
+ });
269
+ });
270
+ };
271
+
272
+
273
+ function synchronize(session, uri, Entity, conflictCallback, callback, errorCallback) {
274
+ persistence.sync.Sync.findBy(session, 'entity', Entity.meta.name, function(sync) {
275
+ var lastServerSyncTime = sync ? persistence.get(sync, 'serverDate') : 0;
276
+ var lastServerPushTime = sync ? persistence.get(sync, 'serverPushDate') : 0;
277
+ var lastLocalSyncTime = sync ? persistence.get(sync, 'localDate') : 0;
278
+ if(!sync) {
279
+ sync = new persistence.sync.Sync(session, {entity: Entity.meta.name});
280
+ session.add(sync);
281
+ }
282
+ persistence.sync.getJSON(uri + '?since=' + lastServerSyncTime, function(result) {
283
+ cacheAndFindUpdates(session, Entity, result.updates, lastLocalSyncTime, lastServerPushTime, conflictCallback, function(updatesToPush) {
284
+ persistence.sync.postJSON(uri, JSON.stringify(updatesToPush), function(pushData) {
285
+ session.flush(function() {
286
+ sync.localDate = getEpoch(new Date());
287
+ sync.serverDate = result.now;
288
+ sync.serverPushDate = pushData.now;
289
+ session.flush(callback);
290
+ });
291
+ });
292
+ });
293
+ },
294
+ errorCallback);
295
+ });
296
+ }
297
+
298
+ persistence.entityDecoratorHooks.push(function(Entity) {
299
+ /**
300
+ * Declares an entity to be tracked for changes
301
+ */
302
+ Entity.enableSync = function(uri) {
303
+ Entity.meta.enableSync = true;
304
+ Entity.meta.syncUri = uri;
305
+ Entity.meta.fields['_lastChange'] = 'BIGINT';
306
+ };
307
+
308
+ Entity.syncAll = function(session, uri, conflictCallback, callback, errorCallback) {
309
+ var args = argspec.getArgs(arguments, [
310
+ { name: 'session', optional: true, check: function(obj) { return obj && obj.flush; }, defaultValue: persistence },
311
+ { name: 'uri', optional: true, check: argspec.hasType('string'), defaultValue: this.meta.syncUri },
312
+ { name: 'conflictCallback', check: argspec.isCallback() },
313
+ { name: 'callback', check: argspec.isCallback() },
314
+ { name: 'errorCallback', optional: true, check: argspec.isCallback() },
315
+ ]);
316
+ synchronize(args.session, args.uri, this, args.conflictCallback, args.callback, args.errorCallback);
317
+ };
318
+ });
319
+
320
+ /**
321
+ * Resets _lastChange property if the object has dirty project (i.e. the object has changed)
322
+ */
323
+ persistence.flushHooks.push(function(session, tx, callback) {
324
+ var queries = [];
325
+ for (var id in session.getTrackedObjects()) {
326
+ if (session.getTrackedObjects().hasOwnProperty(id)) {
327
+ var obj = session.getTrackedObjects()[id];
328
+ var meta = persistence.getEntityMeta()[obj._type];
329
+ if(meta.enableSync) {
330
+ var isDirty = obj._new;
331
+ for ( var p in obj._dirtyProperties) {
332
+ if (obj._dirtyProperties.hasOwnProperty(p)) {
333
+ isDirty = true;
334
+ }
335
+ }
336
+ if(isDirty) {
337
+ obj._lastChange = getEpoch(new Date());
338
+ }
339
+ }
340
+ }
341
+ }
342
+ session.objectsRemoved.forEach(function(rec) {
343
+ var meta = session.getMeta(rec.entity);
344
+ if(meta.enableSync) {
345
+ session.add(new persistence.sync.RemovedObject({entity: rec.entity, objectId: rec.id}));
346
+ }
347
+ });
348
+ session.objectsRemoved=[];
349
+ callback();
350
+ });
351
+
352
+ }());
353
+
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: persistence-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alessandro Mencarini
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Rails integration for Persistence.js
15
+ email:
16
+ - a.mencarini@freegoweb.it
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - README.md
24
+ - Rakefile
25
+ - lib/generators/persistence/install_generator.rb
26
+ - lib/generators/persistence/templates/application.js
27
+ - lib/persistence-rails.rb
28
+ - lib/persistence/rails.rb
29
+ - lib/persistence/rails/engine.rb
30
+ - lib/persistence/rails/version.rb
31
+ - persistence-rails.gemspec
32
+ - vendor/assets/javascript/persistence.all.js
33
+ - vendor/assets/javascript/persistence.core.js
34
+ - vendor/assets/javascript/persistence.jquery.js
35
+ - vendor/assets/javascript/persistence.jquery.mobile.js
36
+ - vendor/assets/javascript/persistence.js
37
+ - vendor/assets/javascript/persistence.migrations.js
38
+ - vendor/assets/javascript/persistence.pool.js
39
+ - vendor/assets/javascript/persistence.search.js
40
+ - vendor/assets/javascript/persistence.store.appengine.js
41
+ - vendor/assets/javascript/persistence.store.config.js
42
+ - vendor/assets/javascript/persistence.store.memory.js
43
+ - vendor/assets/javascript/persistence.store.mysql.js
44
+ - vendor/assets/javascript/persistence.store.sql.js
45
+ - vendor/assets/javascript/persistence.store.sqlite.js
46
+ - vendor/assets/javascript/persistence.store.sqlite3.js
47
+ - vendor/assets/javascript/persistence.store.titanium.js
48
+ - vendor/assets/javascript/persistence.store.websql.js
49
+ - vendor/assets/javascript/persistence.sync.js
50
+ homepage: ''
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.17
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: persistence-rails integrates client-side database ORM Persistence.js with
74
+ Rails 3.1 asset pipeline
75
+ test_files: []
76
+ has_rdoc: