kojac 0.9.1 → 0.11.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.
Files changed (95) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +4 -0
  3. data/Gemfile +1 -1
  4. data/app/assets/javascripts/kojac.js +393 -125
  5. data/app/assets/javascripts/kojac_canjs.js +34 -34
  6. data/app/assets/javascripts/kojac_ember.js +110 -152
  7. data/app/controllers/{kojac_controller.rb → kojac_base_controller.rb} +18 -11
  8. data/app/policies/kojac_base_policy.rb +114 -0
  9. data/app/serializers/kojac_base_serializer.rb +35 -0
  10. data/kojac.gemspec +12 -10
  11. data/lib/kojac/app_serialize.rb +31 -29
  12. data/lib/kojac/concentric.rb +152 -0
  13. data/lib/kojac/kojac_policy.rb +70 -0
  14. data/lib/kojac/kojac_rails.rb +200 -49
  15. data/lib/kojac/version.rb +1 -1
  16. data/spec/can_cache_spec.js +19 -19
  17. data/spec/demo/.gitignore +16 -0
  18. data/spec/demo/.ruby-gemset +1 -0
  19. data/spec/demo/.ruby-version +1 -0
  20. data/spec/demo/Gemfile +59 -0
  21. data/spec/demo/Gemfile.lock +153 -0
  22. data/spec/demo/README.rdoc +15 -248
  23. data/spec/demo/Rakefile +25 -1
  24. data/spec/demo/app/{mailers/.gitkeep → assets/images/.keep} +0 -0
  25. data/spec/demo/app/assets/javascripts/application.js +3 -3
  26. data/spec/demo/app/controllers/application_controller.rb +6 -1
  27. data/spec/demo/app/{models/.gitkeep → controllers/concerns/.keep} +0 -0
  28. data/spec/demo/app/controllers/users_controller.rb +5 -0
  29. data/spec/demo/{lib/assets/.gitkeep → app/mailers/.keep} +0 -0
  30. data/spec/demo/{log/.gitkeep → app/models/.keep} +0 -0
  31. data/spec/demo/app/models/concerns/.keep +0 -0
  32. data/spec/demo/app/models/user.rb +36 -0
  33. data/spec/demo/app/policies/user_policy.rb +42 -0
  34. data/spec/demo/bin/bundle +3 -0
  35. data/spec/demo/bin/rails +4 -0
  36. data/spec/demo/bin/rake +4 -0
  37. data/spec/demo/config.ru +1 -1
  38. data/spec/demo/config/application.rb +14 -46
  39. data/spec/demo/config/application.yml +4 -0
  40. data/spec/demo/config/boot.rb +3 -9
  41. data/spec/demo/config/database.yml +6 -6
  42. data/spec/demo/config/environment.rb +4 -2
  43. data/spec/demo/config/environments/development.rb +11 -19
  44. data/spec/demo/config/environments/production.rb +40 -27
  45. data/spec/demo/config/environments/test.rb +13 -14
  46. data/spec/demo/config/initializers/concentric_config.rb +9 -0
  47. data/spec/demo/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/spec/demo/config/initializers/inflections.rb +6 -5
  49. data/spec/demo/config/initializers/initialize_kojac.rb +16 -0
  50. data/spec/demo/config/initializers/secret_token.rb +7 -2
  51. data/spec/demo/config/initializers/session_store.rb +0 -5
  52. data/spec/demo/config/initializers/wrap_parameters.rb +6 -6
  53. data/spec/demo/config/locales/en.yml +20 -2
  54. data/spec/demo/config/routes.rb +24 -24
  55. data/spec/demo/db/migrate/20131212034312_add_user.rb +14 -0
  56. data/spec/demo/db/migrate/20140107085351_add_owner_id.rb +5 -0
  57. data/spec/demo/db/schema.rb +28 -0
  58. data/spec/demo/db/seeds.rb +7 -0
  59. data/spec/demo/lib/assets/.keep +0 -0
  60. data/spec/demo/lib/tasks/.keep +0 -0
  61. data/spec/demo/log/.keep +0 -0
  62. data/spec/demo/public/404.html +43 -11
  63. data/spec/demo/public/422.html +43 -11
  64. data/spec/demo/public/500.html +43 -11
  65. data/spec/demo/public/robots.txt +5 -0
  66. data/spec/demo/spec/controllers/allowed_fields_spec.rb +171 -0
  67. data/spec/demo/spec/factories/users.rb +9 -0
  68. data/spec/demo/spec/features/concentric_spec.rb +63 -0
  69. data/spec/demo/spec/features/serialization_spec.rb +86 -0
  70. data/spec/demo/spec/spec_helper.rb +133 -0
  71. data/spec/demo/spec/spec_utils.rb +42 -0
  72. data/spec/demo/vendor/assets/javascripts/.keep +0 -0
  73. data/spec/demo/vendor/assets/stylesheets/.keep +0 -0
  74. data/spec/ember_factory_spec.js +1 -1
  75. data/spec/ember_model_spec.js +13 -3
  76. data/spec/ember_tojsono_spec.js +105 -0
  77. data/spec/error_handling_spec.js +90 -0
  78. data/spec/external/underscore_plus.js +318 -9
  79. data/spec/kojac_caching_spec.js +3 -1
  80. data/spec/kojac_ember_cache_spec.js +9 -0
  81. data/spec/kojac_mock_spec.js +4 -4
  82. data/spec/kojac_operations_spec.js +4 -4
  83. data/spec/local_provider_spec.js +184 -0
  84. data/spec/model_ring_spec.rb +2 -2
  85. data/spec/operation_include_spec.js +2 -2
  86. data/spec/run.html +34 -24
  87. data/spec/type_conversion_spec.js +38 -0
  88. data/vendor/assets/javascripts/jstorage.js +950 -0
  89. metadata +115 -129
  90. data/Gemfile.lock +0 -157
  91. data/app/serializers/default_kojac_serializer.rb +0 -10
  92. data/lib/kojac/ring_strong_parameters.rb +0 -195
  93. data/spec/.DS_Store +0 -0
  94. data/spec/demo/script/rails +0 -6
  95. data/spec/external/.DS_Store +0 -0
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MzlmODZlNDc1ZGE2MjY2NzM0NDk5MTVmNzhiNWMxZDQ3ZjdmM2FhYw==
5
- data.tar.gz: !binary |-
6
- ZTg1NjYzNDY0MDRiNGY4M2M0YTQxMDAzYjZjYTYwYjA1YTBkZDRiMQ==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MzljZTFhZmZiNzdkYjE1YWJmMzg5M2M5ZjQ3NTI3NmQxYzhiZThkMjYwMDgw
10
- MzdmZTNiZmU0NTFkMTEyNWRjNDRiN2ZmYjA3MGYyN2Q3ZmVkZmFhMjYzYzhm
11
- NDk0YmU3YmUzZGQwYzllZTBmZjUyYjNmNGVkYmMyMTAyNzViZDg=
12
- data.tar.gz: !binary |-
13
- ODE2MDU0YWJjMzVhMzY4M2EwNGY5YjMxODFiZTE0YWQ5N2FhZDJkY2UxZTUy
14
- MGE0Mzk0MTk0OTRkNGNkYTVmNjcxMGExZGY1ZDg1YzYwYzk2MmUwNTExZjZi
15
- MjBiODcyZTc1OWQ1MGQxNzAwNjk0OGExZDk2NThhMTM0NGM3MDI=
2
+ SHA1:
3
+ metadata.gz: b718cc81d1c3dedf9f6a4e92a627a92214a0d118
4
+ data.tar.gz: aec9fe24896b2965108f802c435861f5b7db5f0e
5
+ SHA512:
6
+ metadata.gz: 0bfff9ecf7789c0ad74e4aaf4d90062e8a565d2800199080e7d11daef67b9f0882eff16ddad1d54aef3da9ccfb0af1aa6c75379a6d1dd7d456a95b8db704bc2f
7
+ data.tar.gz: a8c65fb7708a6eddd730fecbf58984c87528fbca403260a0ca895340058868794f5850755282cd0ec7824b270f55e2926cf70f7f80a1546f38853b8c9124e91d
data/.gitignore CHANGED
@@ -5,3 +5,7 @@ spec/demo/db/*.sqlite3
5
5
  spec/demo/log/*.log
6
6
  spec/demo/tmp/
7
7
  spec/demo/.sass-cache
8
+ /Gemfile.lock
9
+ .DS_Store
10
+ .idea
11
+ *.iml
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  # jquery-rails is used by the dummy application
9
- gem "jquery-rails"
9
+ #gem "jquery-rails"
10
10
 
11
11
  # Declare any dependencies that are still in development here instead of in
12
12
  # your gemspec. These might include edge Rails or gems from your path or
@@ -159,16 +159,81 @@ Kojac.Utils = {
159
159
  return _.flatten(_.pairs(aKeyValues),true); // this style : {key1: value, key2: value}
160
160
  } else
161
161
  return null; // unrecognised input
162
- }
162
+ },
163
+
164
+ // pass a copy aPropListFn aCopyFn when you have a complex object eg. ember class. It will not be passed on to recursive calls
165
+ toJsono: function(aValue,aOptions,aPropListFn,aCopyFn) {
166
+ if (_.isObjectStrict(aValue)) {
167
+ if (!aPropListFn && !aCopyFn && ("toJsono" in aValue))
168
+ aValue = aValue.toJsono(aOptions || {});
169
+ else {
170
+ var aDest = {};
171
+ aOptions = _.clone(aOptions);
172
+ var aProperties = aPropListFn ? aPropListFn(aValue) : aValue; // may return an array of properties, or an object to use the keys from
173
+ var aInclude = (aOptions && _.removeKey(aOptions,'include')); // must be an array
174
+ if (_.isString(aInclude))
175
+ aInclude = aInclude.split(',');
176
+ if (aInclude && aInclude.length) {
177
+ if (_.isArray(aProperties)) //ensure aProperties is an array to add includes
178
+ aProperties = _.clone(aProperties);
179
+ else
180
+ aProperties = _.keys(aProperties);
181
+ for (var i=0;i<aInclude.length;i++)
182
+ aProperties.push(aInclude[i]);
183
+ }
184
+ var aExclude = (aOptions && _.removeKey(aOptions,'exclude')); // must be an array
185
+ if (_.isString(aExclude))
186
+ aExclude = aExclude.split(',');
187
+ var p;
188
+ var v;
189
+ if (_.isArray(aProperties)) {
190
+ for (var i=0;i<aProperties.length;i++) {
191
+ p = aProperties[i];
192
+ if (aExclude && (aExclude.indexOf(p)>=0))
193
+ continue;
194
+ if (aCopyFn)
195
+ aCopyFn(aDest,aValue,p,aOptions);
196
+ else {
197
+ aDest[p] = Kojac.Utils.toJsono(aValue[p],aOptions);
198
+ }
199
+ }
200
+ } else { // properties is an object to use keys from
201
+ for (p in aProperties) {
202
+ if (aExclude && (aExclude.indexOf(p)>=0))
203
+ continue;
204
+ if (aCopyFn)
205
+ aCopyFn(aDest,aValue,p,aOptions);
206
+ else {
207
+ aDest[p] = Kojac.Utils.toJsono(aValue[p],aOptions);
208
+ }
209
+ }
210
+ }
211
+ aValue = aDest;
212
+ }
213
+ } else if (_.isArray(aValue)) {
214
+ var result = [];
215
+ for (var i=0; i<aValue.length; i++)
216
+ result.push(Kojac.Utils.toJsono(aValue[i],aOptions));
217
+ aValue = result;
218
+ } else if (_.isDate(aValue)) {
219
+ aValue = Kojac.interpretValueAsType(aValue,String);
220
+ }
221
+ return aValue;
222
+ },
223
+
224
+ // returns an id above the normal 32 bit range of rails but within the range of Javascript
225
+ createId: function () {
226
+ return _.randomIntRange(4294967296,4503599627370496); // 2**32 to 2**52 see http://stackoverflow.com/questions/9389315/cross-browser-javascript-number-precision
227
+ },
228
+
229
+ timestamp: function() {
230
+ return new Date().getTime();
231
+ },
163
232
 
164
- //public static function getTrailingId(aKey: String): int {
165
- // if (!aKey)
166
- // return 0;
167
- // var parts: Array = aKey.split('__')
168
- // if (!parts.length)
169
- // return 0;
170
- // return StringUtils.toInt(parts[parts.length-1])
171
- //}
233
+ resolvedPromise: function() {
234
+ var d = jQuery.Deferred();
235
+ return d.resolve.apply(d,arguments);
236
+ }
172
237
  };
173
238
 
174
239
  /*
@@ -200,6 +265,9 @@ Kojac.getPropertyValueType = function(aValue) {
200
265
  case 'object':
201
266
  result = Object;
202
267
  break;
268
+ case 'date':
269
+ result = Date;
270
+ break;
203
271
  case 'function':
204
272
  case 'class':
205
273
  case 'instance':
@@ -233,6 +301,8 @@ Kojac.interpretValueAsType = function(aValue, aDestType) {
233
301
  case Boolean:
234
302
  return aValue.toString();
235
303
  break;
304
+ case Date:
305
+ return moment(aValue).toISOString();
236
306
  default:
237
307
  case Null:
238
308
  return null;
@@ -241,21 +311,7 @@ Kojac.interpretValueAsType = function(aValue, aDestType) {
241
311
 
242
312
  break;
243
313
  case Boolean:
244
-
245
- switch(sourceType) {
246
- case Null:
247
- default:
248
- return null;
249
- break;
250
- case Int:
251
- case Number:
252
- if (isNaN(aValue))
253
- return null;
254
- else
255
- return !!aValue;
256
- break;
257
- }
258
-
314
+ return _.toBoolean(aValue,null);
259
315
  break;
260
316
 
261
317
  case Number:
@@ -298,6 +354,20 @@ Kojac.interpretValueAsType = function(aValue, aDestType) {
298
354
  break;
299
355
  }
300
356
 
357
+ break;
358
+ case Date:
359
+ switch(sourceType) {
360
+ case String:
361
+ return moment.utc(aValue).toDate();
362
+ break;
363
+ case Number:
364
+ return new Date(aValue);
365
+ break;
366
+ case Null:
367
+ default:
368
+ return null;
369
+ break;
370
+ }
301
371
  break;
302
372
  case Object:
303
373
  return null;
@@ -432,7 +502,7 @@ Kojac.Model = Kojac.Object.extend({
432
502
  if (['__defaults','__attributes'].indexOf(p)>=0)
433
503
  continue;
434
504
  var propValue = prop[p];
435
- if (_.isArray(propValue) && propValue.length===2 && Kojac.FieldTypes.indexOf(propValue[0])>=0) { // in form property: [Type, Default Value]
505
+ if (_.isArray(propValue) && (propValue.length===2) && (Kojac.FieldTypes.indexOf(propValue[0])>=0)) { // in form property: [Type, Default Value]
436
506
  this.__attributes[p] = propValue[0];
437
507
  prop[p] = propValue[1];
438
508
  } else if (Kojac.FieldTypes.indexOf(propValue) >= 0) { // field type
@@ -659,7 +729,7 @@ Kojac.Operation = Kojac.Object.extend({
659
729
  verb: null,
660
730
  key: null,
661
731
  value: undefined,
662
- results: {},
732
+ results: null,
663
733
  result_key: null,
664
734
  result: undefined,
665
735
  error: null, // set with some truthy error if this operation fails
@@ -682,6 +752,8 @@ Kojac.Operation = Kojac.Object.extend({
682
752
  result = results[response_key];
683
753
 
684
754
  results = _.omit(results,response_key); // results now excludes primary result
755
+ if (!this.results)
756
+ this.results = {};
685
757
  _.extend(this.results,results); // store other results
686
758
  this.result_key = final_result_key;
687
759
  this.results[final_result_key] = result; // store primary result
@@ -700,8 +772,8 @@ Kojac.Request = Kojac.Object.extend({
700
772
  ops: [],
701
773
  handlers: null,
702
774
  op: null,
703
- result: undefined,
704
- results: {},
775
+ //result: undefined,
776
+ //results: null,
705
777
  error: null, // set with some truthy value if this whole request or any operation fails (will contain first error if multiple)
706
778
  newOperation: function() {
707
779
  var obj = new Kojac.Operation({request: this});
@@ -720,9 +792,9 @@ Kojac.Request = Kojac.Object.extend({
720
792
  // Can give existing keys with id, and will create a clone in database with a new id
721
793
  create: function(aKeyValues,aOptions) {
722
794
 
723
- var result_key = aOptions && _.removeKey(aOptions,'result_key');
724
- var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
725
- var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
795
+ var result_key = (aOptions && _.removeKey(aOptions,'result_key'));
796
+ var params = (aOptions && _.removeKey(aOptions,'params')); // extract specific params
797
+ var options = _.extend({cacheResults: true, manufacture: true},aOptions || {});
726
798
 
727
799
  var kvArray = Kojac.Utils.toKeyValueArray(aKeyValues);
728
800
  for (var i=0;i<kvArray.length-1;i+=2) {
@@ -731,7 +803,7 @@ Kojac.Request = Kojac.Object.extend({
731
803
  var op = this.newOperation();
732
804
  op.verb = 'CREATE';
733
805
  op.options = _.clone(options);
734
- op.params = params && _.clone(params);
806
+ op.params = (params && _.clone(params));
735
807
  var parts = keySplit(k);
736
808
  if (parts.length >= 3)
737
809
  op.key = k;
@@ -748,14 +820,14 @@ Kojac.Request = Kojac.Object.extend({
748
820
  // known options will be moved from aOptions to op.options; remaining keys will be put into params
749
821
  read: function(aKeys,aOptions) {
750
822
  var keys = Kojac.Utils.interpretKeys(aKeys);
751
- var result_key = aOptions && _.removeKey(aOptions,'result_key'); // extract result_key
752
- var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
753
- var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
823
+ var result_key = (aOptions && _.removeKey(aOptions,'result_key')); // extract result_key
824
+ var params = (aOptions && _.removeKey(aOptions,'params')); // extract specific params
825
+ var options = _.extend({cacheResults: true, manufacture: true},aOptions || {});
754
826
  var me = this;
755
827
  jQuery.each(keys,function(i,k) {
756
828
  var op = me.newOperation();
757
829
  op.options = _.clone(options);
758
- op.params = params && _.clone(params);
830
+ op.params = (params && _.clone(params));
759
831
  op.verb = 'READ';
760
832
  op.key = k;
761
833
  if (i===0)
@@ -772,9 +844,9 @@ Kojac.Request = Kojac.Object.extend({
772
844
  },
773
845
 
774
846
  update: function(aKeyValues,aOptions) {
775
- var result_key = aOptions && _.removeKey(aOptions,'result_key');
776
- var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
777
- var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
847
+ var result_key = (aOptions && _.removeKey(aOptions,'result_key'));
848
+ var options = _.extend({cacheResults: true, manufacture: true},aOptions || {});
849
+ var params = (aOptions && _.removeKey(aOptions,'params')); // extract specific params
778
850
  var first=true;
779
851
  var kvArray = Kojac.Utils.toKeyValueArray(aKeyValues);
780
852
  for (var i=0;i<kvArray.length-1;i+=2) {
@@ -783,7 +855,7 @@ Kojac.Request = Kojac.Object.extend({
783
855
  var op = this.newOperation();
784
856
  op.verb = 'UPDATE';
785
857
  op.options = _.clone(options);
786
- op.params = params && _.clone(params);
858
+ op.params = (params && _.clone(params));
787
859
  op.key = k;
788
860
  if (first) {
789
861
  op.result_key = result_key || k;
@@ -797,14 +869,14 @@ Kojac.Request = Kojac.Object.extend({
797
869
 
798
870
  destroy: function(aKeys,aOptions) {
799
871
  var keys = Kojac.Utils.interpretKeys(aKeys);
800
- var result_key = aOptions && _.removeKey(aOptions,'result_key');
801
- var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
802
- var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
872
+ var result_key = (aOptions && _.removeKey(aOptions,'result_key'));
873
+ var options = _.extend({cacheResults: true},aOptions || {});
874
+ var params = (aOptions && _.removeKey(aOptions,'params')); // extract specific params
803
875
  var me = this;
804
876
  jQuery.each(keys,function(i,k) {
805
877
  var op = me.newOperation();
806
878
  op.options = _.clone(options);
807
- op.params = params && _.clone(params);
879
+ op.params = (params && _.clone(params));
808
880
  op.verb = 'DESTROY';
809
881
  op.key = k;
810
882
  if (i===0)
@@ -819,10 +891,10 @@ Kojac.Request = Kojac.Object.extend({
819
891
  var op = this.newOperation();
820
892
  op.verb = 'EXECUTE';
821
893
 
822
- var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
823
- op.result_key = aOptions && _.removeKey(aOptions,'result_key') || aKey;
824
- op.options = aOptions ? _.extend({cacheResults: false},aOptions) : {}; // extract known options
825
- op.params = params && _.clone(params);
894
+ var params = (aOptions && _.removeKey(aOptions,'params')); // extract specific params
895
+ op.result_key = (aOptions && _.removeKey(aOptions,'result_key')) || aKey;
896
+ op.options = _.extend({cacheResults: false, manufacture: false},aOptions || {});
897
+ op.params = (params && _.clone(params));
826
898
  op.key = aKey;
827
899
  op.value = aValue;
828
900
  return this;
@@ -849,53 +921,85 @@ Kojac.Core = Kojac.Object.extend({
849
921
  return new Kojac.Request({kojac: this});
850
922
  },
851
923
 
852
- cacheResults: function(aRequest) {
924
+ // var v;
925
+ // for (var i=0;i<aRequest.ops.length;i++) {
926
+ // var op = aRequest.ops[i];
927
+ // if (op.error)
928
+ // break;
929
+ // if (op.options.cacheResults===false)
930
+ // continue;
931
+ // for (p in op.results) {
932
+ // if (p==op.result_key)
933
+ // continue;
934
+ // v = op.results[p];
935
+ // if (v===undefined)
936
+ // delete this.cache[p];
937
+ // else
938
+ // this.cache[p] = op.results[p];
939
+ // }
940
+ // v = op.results[op.result_key];
941
+ // if (v===undefined) {
942
+ // delete this.cache[op.result_key];
943
+ // } else {
944
+ // this.cache[op.result_key] = v;
945
+ // }
946
+ // console.log('end of loop');
947
+ // }
948
+
949
+ handleResults: function(aRequest) {
853
950
  if (this.cache.beginPropertyChanges)
854
951
  this.cache.beginPropertyChanges();
855
- for (var i=0;i<aRequest.ops.length;i++) {
856
- var op = aRequest.ops[i];
857
- if (op.error)
858
- break;
859
- if (op.options.cacheResults===false)
860
- continue;
861
- for (p in op.results) {
862
- if (p==op.result_key)
863
- continue;
864
- if (op.results[p]===undefined)
865
- delete this.cache[p];
866
- else
867
- this.cache[p] = op.results[p];
952
+
953
+ var updatedObjects = [];
954
+
955
+ try {
956
+ for (var i=0;i<aRequest.ops.length;i++) {
957
+ var op = aRequest.ops[i];
958
+ if (op.error)
959
+ break;
960
+
961
+ for (var key in op.results) {
962
+ var value = op.results[key];
963
+ if ((op.options.atomise!==false) && _.isObjectStrict(value)) {
964
+ var existing = this.cache.retrieve(key);
965
+ if (_.isObjectStrict(existing)) {
966
+ if (existing.beginPropertyChanges) {
967
+ existing.beginPropertyChanges();
968
+ updatedObjects.push(existing);
969
+ }
970
+ if (existing.setProperties)
971
+ existing.setProperties(value);
972
+ else
973
+ _.copyProperties(existing,value);
974
+ value = existing;
975
+ } else {
976
+ if ((op.options.manufacture!==false) && (this.objectFactory))
977
+ value = this.objectFactory.manufacture(value,key);
978
+ }
979
+ }
980
+ op.results[key] = value;
981
+ if (op.options.cacheResults!==false)
982
+ this.cache.store(key,value);
983
+ }
868
984
  }
869
- if (op.results[op.result_key]===undefined)
870
- delete this.cache[op.result_key];
871
- else
872
- this.cache[op.result_key] = op.results[op.result_key];
985
+ } finally {
986
+ for (var i=0;i<updatedObjects.length;i++)
987
+ updatedObjects[i].endPropertyChanges();
873
988
  }
874
989
  if (this.cache.endPropertyChanges)
875
990
  this.cache.endPropertyChanges();
876
991
  },
877
992
 
878
- cacheHasKeys: function(aKeysArray) {
879
- var me = this;
880
- return _.all(aKeysArray,function(k){
881
- return k in me.cache;
882
- })
883
- },
884
-
885
- cacheValues: function(aKeysArray) {
886
- var me = this;
887
- return _.map(aKeysArray,function(k){
888
- return me.cache[k];
889
- })
890
- },
891
-
892
- finaliseRequest: function(aRequest) {
993
+ finaliseResponse: function(aRequest) {
893
994
  // set convenience properties
894
995
  var results = {};
895
- for (var i=0;i<aRequest.ops.length;i++) {
996
+ if (!aRequest.error) for (var i=0;i<aRequest.ops.length;i++) {
896
997
  var op = aRequest.ops[i];
897
- if (op.error && !aRequest.error)
898
- aRequest.error = op.error;
998
+ if (op.error) {
999
+ if (!aRequest.error)
1000
+ aRequest.error = op.error;
1001
+ break;
1002
+ }
899
1003
  _.extend(results,op.results);
900
1004
  op.result = !op.error && op.results && (op.result_key || op.key) ? op.results[op.result_key || op.key] : null;
901
1005
  if (i===0) {
@@ -917,22 +1021,29 @@ Kojac.Core = Kojac.Object.extend({
917
1021
  }
918
1022
  }
919
1023
  }
920
- aRequest.results = results;
921
- aRequest.result = aRequest.op && aRequest.op.result;
1024
+ if (aRequest.error) {
1025
+ _.removeKey(aRequest,'results');
1026
+ _.removeKey(aRequest,'result');
1027
+ } else {
1028
+ aRequest.results = results;
1029
+ aRequest.result = (aRequest.op && aRequest.op.result);
1030
+ }
922
1031
  },
923
1032
 
924
1033
  performRequest: function(aRequest) {
925
1034
  for (var i=0;i<aRequest.ops.length;i++) {
926
- var op = aRequest.ops[i];
1035
+ var op = aRequest.ops[i]
1036
+ op.results = {};
927
1037
  var k = (op.result_key && (op.result_key !== op.key)) ? op.result_key : op.key;
928
- if (op.verb=='READ' && op.options.preferCache && (k in aRequest.kojac.cache)) { // resolve from cache
929
- op.results[k] = aRequest.kojac.cache[k];
1038
+ var cacheValue = aRequest.kojac.cache.retrieve(k);
1039
+ if ((op.verb=='READ') && op.options.preferCache && (cacheValue!==undefined)) { // resolve from cache
1040
+ op.results[k] = cacheValue;
930
1041
  var dep_keys = aRequest.kojac.dependentKeys[op.key];
931
1042
  if (dep_keys) {
932
1043
  for (var i=0;i<dep_keys.length;i++) {
933
1044
  var dk = dep_keys[i];
934
1045
  // what if not in cache? perhaps dump siblings in dependentKeys and index key to cause full refresh? or refuse to remove from cache if in dependentKeys
935
- op.results[dk] = aRequest.kojac.cache[dk];
1046
+ op.results[dk] = aRequest.kojac.cache.retrieve(dk);
936
1047
  }
937
1048
  }
938
1049
  op.result_key = k;
@@ -940,14 +1051,12 @@ Kojac.Core = Kojac.Object.extend({
940
1051
  op.performed = true;
941
1052
  }
942
1053
  }
943
- aRequest.handlers.add(this.remoteProvider.handleAjaxRequest,null,this.remoteProvider);
944
- if (this.objectFactory && this.objectFactory.transformResultsToValueObjects)
945
- aRequest.handlers.add(this.objectFactory.transformResultsToValueObjects,null,this.objectFactory);
946
- if (this.cache.cacheResults)
947
- aRequest.handlers.add(this.cache.cacheResults,null,this.cache);
948
- else
949
- aRequest.handlers.add(this.cacheResults,null,this);
950
- aRequest.handlers.run(aRequest).then(this.finaliseRequest);
1054
+ aRequest.handlers.add(this.remoteProvider.handleRequest,null,this.remoteProvider);
1055
+
1056
+ //if (this.objectFactory)
1057
+ aRequest.handlers.add(this.handleResults,null,this);
1058
+
1059
+ aRequest.handlers.run(aRequest).always(this.finaliseResponse);
951
1060
  return aRequest;
952
1061
  },
953
1062
 
@@ -1007,6 +1116,21 @@ Kojac.Core = Kojac.Object.extend({
1007
1116
  // END Convenience Functions
1008
1117
  });
1009
1118
 
1119
+ Kojac.Cache = Kojac.Object.extend({
1120
+ store: function(k,v) {
1121
+ if (v===undefined) {
1122
+ delete this[k];
1123
+ return v;
1124
+ } else {
1125
+ return (this[k] = v);
1126
+ }
1127
+ },
1128
+ retrieve: function(k) {
1129
+ return this[k];
1130
+ }
1131
+ });
1132
+
1133
+
1010
1134
  /**
1011
1135
  * A default RemoteProvider implementation. Your own implementation, or a subclass of this may be used instead.
1012
1136
  * @class Kojac.RemoteProvider
@@ -1018,6 +1142,7 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1018
1142
  mockFilePath: null,
1019
1143
  mockReadOperationHandler: null,
1020
1144
  serverPath: null,
1145
+ timeout: 10000,
1021
1146
 
1022
1147
  mockWriteOperationHandler: null,//function(aOp) {
1023
1148
  // Ember.Logger.log(JSON.stringify(CanUtils.copyProperties({},aOp,null,['request'])));
@@ -1032,12 +1157,9 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1032
1157
  key: op.key
1033
1158
  };
1034
1159
  if ((op.verb==='CREATE') || (op.verb==='UPDATE') || (op.verb==='EXECUTE')) {
1035
- if (op.value && ("toObject" in op.value))
1036
- jsonOp.value = op.value.toObject(op.options);
1037
- else
1038
- jsonOp.value = op.value;
1160
+ jsonOp.value = Kojac.Utils.toJsono(op.value,op.options);
1039
1161
  }
1040
- var options = op.options && _.omit(op.options,['cacheResults','preferCache']);
1162
+ var options = (op.options && _.omit(op.options,['cacheResults','preferCache']));
1041
1163
  if (options && !_.isEmpty(options))
1042
1164
  jsonOp.options = options; // omit local keys
1043
1165
  jsonOp.params = op.params;
@@ -1046,9 +1168,10 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1046
1168
  return result
1047
1169
  },
1048
1170
 
1049
- handleAjaxRequest: function(aRequest) {
1171
+ handleRequest: function(aRequest) {
1050
1172
  var result;
1051
1173
  var op;
1174
+ var me = this;
1052
1175
  for (var i=0;i<aRequest.ops.length;i++) {
1053
1176
  op = aRequest.ops[i];
1054
1177
  if (op.performed)
@@ -1076,16 +1199,15 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1076
1199
  return;
1077
1200
  if (this.useMockFileValues) {
1078
1201
  aRequest.handlers.waitForCallNext = true;
1079
- var me = this;
1080
1202
  var getMockFile = function(aOp) {
1081
1203
  var fp = me.mockFilePath+aOp.key+'.js';
1082
1204
  var data = null;
1083
- return jQuery.ajax({url: fp, dataType: 'json', cache: false, data: data}).done(
1205
+ return jQuery.ajax({url: fp, dataType: 'json', cache: false, data: data, timeout: me.timeout}).done(
1084
1206
  function( aData ) {
1085
1207
  for (p in aData) {
1086
1208
  if (p==='results') {
1087
1209
  for (k in aData.results) {
1088
- if (k===aOp.key && (aOp.result_key!=aOp.key))
1210
+ if ((k===aOp.key) && (aOp.result_key!=aOp.key))
1089
1211
  aOp.results[aOp.result_key] = aData.results[k];
1090
1212
  else
1091
1213
  aOp.results[k] = aData.results[k];
@@ -1119,6 +1241,7 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1119
1241
  }
1120
1242
  };
1121
1243
  aRequest.handlers.waitForCallNext = true;
1244
+ // !!! might need to include X-CSRF-Token see http://stackoverflow.com/questions/8511695/rails-render-json-session-lost?rq=1
1122
1245
  var ajaxpars = {
1123
1246
  type: 'POST',
1124
1247
  data: JSON.stringify(dataToSend),
@@ -1131,25 +1254,183 @@ Kojac.RemoteProvider = Kojac.Object.extend({
1131
1254
  for (var i=0;i<server_ops.length;i++) {
1132
1255
  var opRequest = server_ops[i]; //aRequest.ops[request_op_index[i]];
1133
1256
  var opResult = (_.isArray(aResult.ops) && (i<aResult.ops.length) && aResult.ops[i]);
1134
- opRequest.receiveResult(opResult);
1135
1257
  opRequest.fromCache = false;
1136
1258
  opRequest.performed = true;
1259
+ if (opResult.error) {
1260
+ opRequest.error = opResult.error;
1261
+ aRequest.handlers.handleError(opResult.error);
1262
+ break;
1263
+ } else {
1264
+ opRequest.receiveResult(opResult);
1265
+ }
1137
1266
  }
1138
1267
  aRequest.handlers.callNext();
1139
1268
  }).fail(function(aXhr,aStatus,aError){
1269
+ aRequest.error = me.interpretXhrError(aXhr);
1270
+ //_.removeKey(aRequest,'results');
1140
1271
  for (var i=0;i<server_ops.length;i++) {
1141
1272
  var opRequest = server_ops[i]; //aRequest.ops[request_op_index[i]];
1142
1273
  opRequest.fromCache = false;
1143
1274
  opRequest.performed = true;
1275
+ //if (opRequest.error)
1276
+ // _.removeKey(opRequest,'results');
1144
1277
  }
1145
- aRequest.error = aXhr;
1146
- aRequest.handlers.handleError(aXhr);
1278
+ aRequest.handlers.handleError(aRequest.error);
1147
1279
  aRequest.handlers.callNext();
1148
1280
  });
1149
1281
  }
1282
+ },
1283
+
1284
+ interpretXhrError: function(aXhr) {
1285
+ var http_code = null;
1286
+ var kind = null;
1287
+ var message = null;
1288
+ var debug_message = null;
1289
+ var response = null;
1290
+ var headers = null;
1291
+ if (http_code = (aXhr && aXhr.status)) {
1292
+ kind = (aXhr.statusText && aXhr.statusText.replace(' ',''));
1293
+ message = debug_message = aXhr.statusText;
1294
+ headers = aXhr.getAllResponseHeaders();
1295
+ response = aXhr.responseText;
1296
+ } else {
1297
+ http_code = null;
1298
+ kind = "NetworkError";
1299
+ message = "Failed to connect. Please check network or try again";
1300
+ debug_message = "Network connection failed";
1301
+ }
1302
+ return {
1303
+ format: 'KojacError',
1304
+ http_code: http_code, // a valid HTTP status code, or null
1305
+ kind: kind, // CamelCase text name of error, for conditional code handling
1306
+ message: message, // an explanation for normal humans
1307
+ debug_message: debug_message, // an explanation for developers
1308
+ xhr: aXhr, // the original XHR object from jQuery
1309
+ headers: headers, // all response headers
1310
+ response: response // the response body
1311
+ }
1150
1312
  }
1151
1313
  });
1152
1314
 
1315
+
1316
+ Kojac.LocalStorageRemoteProvider = Kojac.Object.extend({
1317
+ operationsToJson: function(aOps) {
1318
+ var result = [];
1319
+ for (var i=0;i<aOps.length;i++) {
1320
+ var op = aOps[i];
1321
+ var jsonOp = {
1322
+ verb: op.verb,
1323
+ key: op.key
1324
+ };
1325
+ if ((op.verb==='CREATE') || (op.verb==='UPDATE') || (op.verb==='EXECUTE')) {
1326
+ jsonOp.value = Kojac.Utils.toJsono(op.value,op.options);
1327
+ }
1328
+ var options = (op.options && _.omit(op.options,['cacheResults','preferCache']));
1329
+ if (options && !_.isEmpty(options))
1330
+ jsonOp.options = options; // omit local keys
1331
+ jsonOp.params = op.params;
1332
+ result.push(jsonOp);
1333
+ }
1334
+ return result
1335
+ },
1336
+
1337
+ handleRequest: function(aRequest) {
1338
+ var aRequestOp;
1339
+ if (!aRequest.ops.length)
1340
+ return;
1341
+ var ops = this.operationsToJson(aRequest.ops);
1342
+ var op_output;
1343
+ var v,op,id,key,value,parts,results,result_key;
1344
+ for (var i=0;i<ops.length;i++) {
1345
+ op = ops[i];
1346
+ aRequestOp = aRequest.ops[i];
1347
+ if (op.verb=='CREATE') {
1348
+ id = Kojac.Utils.createId();
1349
+ key = keyJoin(op.key,id);
1350
+ result_key = (op.result_key || key);
1351
+ value = _.clone(op.value,true,true);
1352
+ value.id = id;
1353
+
1354
+ $.jStorage.set(key,value);
1355
+ results = {};
1356
+ results[result_key] = value;
1357
+ op_output = {
1358
+ key: op.key,
1359
+ verb: op.verb,
1360
+ result_key: result_key,
1361
+ results: results
1362
+ };
1363
+ } else if (op.verb=='READ') {
1364
+ result_key = (op.result_key || op.key);
1365
+ results = {};
1366
+ parts = keySplit(op.key);
1367
+ if (parts[1]) { // item
1368
+ value = $.jStorage.get(op.key,Boolean);
1369
+ if (value===Boolean)
1370
+ value = undefined;
1371
+ results[result_key] = value;
1372
+ } else { // collection
1373
+ var keys = $.jStorage.index();
1374
+ var ids = [];
1375
+ _.each(keys,function(k){
1376
+ parts = keySplit(k);
1377
+ id = parts[1];
1378
+ if (parts[0]!=op.key || !id)
1379
+ return;
1380
+ ids.push(id);
1381
+ v = $.jStorage.get(k,Boolean);
1382
+ if (value===Boolean)
1383
+ value = undefined;
1384
+ results[k] = v;
1385
+ });
1386
+ results[result_key] = ids;
1387
+ }
1388
+ op_output = {
1389
+ key: op.key,
1390
+ verb: op.verb,
1391
+ result_key: result_key,
1392
+ results: results
1393
+ };
1394
+ } else if (op.verb=='UPDATE') {
1395
+ value = $.jStorage.get(op.key,Boolean);
1396
+ if (value===Boolean)
1397
+ value = undefined;
1398
+ result_key = (op.result_key || op.key);
1399
+ if (_.isObjectStrict(value))
1400
+ _.extend(value,op.value);
1401
+ else
1402
+ value = op.value;
1403
+ $.jStorage.set(op.key,value);
1404
+ results = {};
1405
+ results[result_key] = value;
1406
+ op_output = {
1407
+ key: op.key,
1408
+ verb: op.verb,
1409
+ result_key: result_key,
1410
+ results: results
1411
+ };
1412
+ } else if (op.verb=='DESTROY') {
1413
+ $.jStorage.deleteKey(op.key);
1414
+ result_key = (op.result_key || op.key);
1415
+ results = {};
1416
+ //results[result_key] = undefined;
1417
+ op_output = {
1418
+ key: op.key,
1419
+ verb: op.verb,
1420
+ result_key: result_key,
1421
+ results: results
1422
+ };
1423
+ } else {
1424
+ throw "verb not implemented";
1425
+ }
1426
+ aRequestOp.receiveResult(op_output);
1427
+ aRequestOp.fromCache = false;
1428
+ aRequestOp.performed = true;
1429
+ }
1430
+ }
1431
+ });
1432
+
1433
+
1153
1434
  /**
1154
1435
  * A default ObjectFactory implementation. Your own implementation, or a subclass of this may be used instead.
1155
1436
  * @class Kojac.ObjectFactory
@@ -1173,7 +1454,7 @@ Kojac.ObjectFactory = Kojac.Object.extend({
1173
1454
  var pair;
1174
1455
  var re;
1175
1456
  var newClass;
1176
- for (var i = 0; i < this.matchers.length; i++) {
1457
+ if (this.matchers) for (var i = 0; i < this.matchers.length; i++) {
1177
1458
  pair = this.matchers[i];
1178
1459
  re = pair[0];
1179
1460
  if (!re.test(aKey))
@@ -1209,21 +1490,8 @@ Kojac.ObjectFactory = Kojac.Object.extend({
1209
1490
  var newClass = this.classFromKey(aKey);
1210
1491
  result = me.createInstance(newClass,aObject);
1211
1492
  }
1493
+ console.log('END manufacture');
1212
1494
  return result;
1213
- },
1214
-
1215
- transformResultsToValueObjects: function(aRequest) {
1216
- for (var i=0;i<aRequest.ops.length;i++) {
1217
- var op = aRequest.ops[i];
1218
- if (op.error)
1219
- break;
1220
- for (var k in op.results) {
1221
- var v = op.results[k];
1222
- if (!jQuery.isPlainObject(v))
1223
- continue;
1224
- op.results[k] = this.manufacture(v,k);
1225
- }
1226
- }
1227
1495
  }
1228
1496
 
1229
1497
  });