kojac 0.9.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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
  });