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
@@ -0,0 +1,42 @@
1
+ def setup_common_data
2
+ end
3
+
4
+ def stub_login_user(aValues=nil)
5
+ aValues ||= {}
6
+ aValues[:ring] ||= USER_RING
7
+ user = FactoryGirl.create(:user,aValues)
8
+ ApplicationController.any_instance.stub(:current_user).and_return(user)
9
+ user
10
+ end
11
+
12
+ def do_op(read_op)
13
+ content = {
14
+ options: {},
15
+ ops: [
16
+ read_op
17
+ ]
18
+ }
19
+ request.accept = "application/json"
20
+ post :receive, format: :json, kojac: content
21
+
22
+ output = JSON.parse response.body
23
+ output['ops'].should be_is_a Array
24
+ output['ops'].length.should == 1
25
+ op = output['ops'].first
26
+ result = op['results'][op['result_key']]
27
+ end
28
+
29
+ # from http://openhood.com/rails/rails%203/2010/07/20/add-routes-at-runtime-rails-3/
30
+ def draw_routes(&block)
31
+ begin
32
+ app = Rails::Application.subclasses.first
33
+ _routes = app.routes
34
+ _routes.disable_clear_and_finalize = true
35
+ _routes.clear!
36
+ app.routes_reloader.paths.each{ |path| load(path) }
37
+ _routes.draw(&block)
38
+ ActiveSupport.on_load(:action_controller) { _routes.finalize! }
39
+ ensure
40
+ _routes.disable_clear_and_finalize = false
41
+ end
42
+ end
File without changes
File without changes
@@ -27,7 +27,7 @@ describe("Ember Factory", function() {
27
27
  nothing: null
28
28
  };
29
29
 
30
- var emberObject = factory.emberObjectFactory(source,'order_item__51');
30
+ var emberObject = factory.manufacture(source,'order_item__51');
31
31
  expect(emberObject).toBeDefined();
32
32
  expect(emberObject.constructor).toBe(OrderItem);
33
33
  expect(emberObject.id).toBe(source.id);
@@ -4,23 +4,26 @@ describe("Kojac Ember Model", function() {
4
4
  name: String,
5
5
  purchases: Int,
6
6
  weight: Number,
7
- isMember: Boolean
7
+ isMember: Boolean,
8
+ start: Date
8
9
  };
9
10
  var Product1 = Kojac.EmberModel.extend(product1Values);
10
11
 
12
+ // can only include static values here ie not a Date or other object
11
13
  var product2Values = {
12
14
  name: 'John',
13
15
  purchases: 56,
14
16
  weight: 12.3,
15
17
  isMember: true
16
- }
18
+ };
17
19
  var Product2 = Kojac.EmberModel.extend(product2Values);
18
20
 
19
21
  var wrongProductValues = {
20
22
  name: 123,
21
23
  purchases: 2.34,
22
24
  weight: [],
23
- isMember: 1
25
+ isMember: 1,
26
+ start: "2010-06-01T23:59:56+08:00"
24
27
  };
25
28
 
26
29
  it("extend and check null values", function() {
@@ -29,6 +32,7 @@ describe("Kojac Ember Model", function() {
29
32
  expect(product.purchases).toBeNull();
30
33
  expect(product.weight).toBeNull();
31
34
  expect(product.isMember).toBeNull();
35
+ expect(product.start).toBeNull();
32
36
  expect(Product1.getDefinitions()).toEqual(product1Values);
33
37
  });
34
38
 
@@ -79,14 +83,17 @@ describe("Kojac Ember Model", function() {
79
83
  });
80
84
 
81
85
  it("check create values requiring conversion", function() {
86
+ moment().zone(8);
82
87
  var product = Product1.create(wrongProductValues);
83
88
  expect(product.get('name')).toBe('123');
84
89
  expect(product.get('purchases')).toBe(2);
85
90
  expect(product.get('weight')).toBe(null);
86
91
  expect(product.get('isMember')).toBe(true);
92
+ expect(product.get('start')).toEqual(new Date(2010,5,1,23,59,56));
87
93
  });
88
94
 
89
95
  it("check set values requiring conversion", function() {
96
+ moment().zone(8);
90
97
  var product = Product1.create();
91
98
  for (p in wrongProductValues)
92
99
  product.set(p,wrongProductValues[p]);
@@ -95,15 +102,18 @@ describe("Kojac Ember Model", function() {
95
102
  expect(product.purchases).toBe(2);
96
103
  expect(product.weight).toBe(null);
97
104
  expect(product.isMember).toBe(true);
105
+ expect(product.start).toEqual(new Date(2010,5,1,23,59,56));
98
106
  });
99
107
 
100
108
  it("check setProperties values requiring conversion", function() {
109
+ moment().zone(8);
101
110
  var product = Product1.create();
102
111
  product.setProperties(wrongProductValues);
103
112
  expect(product.name).toBe('123');
104
113
  expect(product.purchases).toBe(2);
105
114
  expect(product.weight).toBe(null);
106
115
  expect(product.isMember).toBe(true);
116
+ expect(product.start).toEqual(new Date(2010,5,1,23,59,56));
107
117
  });
108
118
 
109
119
  it("check init", function() {
@@ -0,0 +1,105 @@
1
+ describe("EmberModel toJsono", function() {
2
+
3
+ var productProperties = {
4
+ name: String,
5
+ purchases: Int,
6
+ weight: Number,
7
+ isMember: Boolean,
8
+ embedded: null
9
+ };
10
+ var Product = Kojac.EmberModel.extend(productProperties);
11
+
12
+ var Summer = Kojac.EmberModel.extend({
13
+ a: Int,
14
+ b: Int,
15
+ sum: function() {
16
+ return this.get('a') + this.get('b');
17
+ }.property('a','b')
18
+ });
19
+
20
+ var productValues1 = {
21
+ name: 'John',
22
+ purchases: 56,
23
+ weight: 12.3,
24
+ isMember: true,
25
+ embedded: null
26
+ };
27
+
28
+ var values2 = {
29
+ colour: 'crimson',
30
+ content: 23,
31
+ explosive: true,
32
+ embedded: null
33
+ };
34
+
35
+ var simpleArray = [3,4,5,true,false,"efg",3.4,null];
36
+
37
+ it("simple values", function() {
38
+ _.each(simpleArray,function(v){
39
+ expect(Kojac.Utils.toJsono(v)).toEqual(v);
40
+ })
41
+ });
42
+
43
+ it("array of simple values", function() {
44
+ var jsono = Kojac.Utils.toJsono(simpleArray);
45
+ expect(jsono).toEqual(simpleArray);
46
+ });
47
+
48
+ it("simple object", function() {
49
+ var jsono = Kojac.Utils.toJsono(productValues1);
50
+ expect(jsono).toEqual(productValues1);
51
+ });
52
+
53
+ it("simple object with embedded model", function() {
54
+ var input = _.clone(values2);
55
+ input.embedded = Product.create(productValues1);
56
+
57
+ var actual = Kojac.Utils.toJsono(input);
58
+
59
+ var expected = _.clone(values2);
60
+ expected.embedded = productValues1;
61
+
62
+ expect(actual).toEqual(expected);
63
+ });
64
+
65
+ it("simple model with embedded object", function() {
66
+ var input = Product.create(productValues1);
67
+ input.embedded = values2;
68
+
69
+ var actual = Kojac.Utils.toJsono(input);
70
+
71
+ var expected = _.clone(productValues1);
72
+ expected.embedded = values2;
73
+
74
+ expect(actual).toEqual(expected);
75
+ });
76
+
77
+ it("simple model", function() {
78
+ var product1 = Product.create(productValues1);
79
+ var jsono = Kojac.Utils.toJsono(product1);
80
+ expect(jsono).toEqual(productValues1)
81
+ });
82
+
83
+ it("model with include", function(){
84
+ var input = Summer.create({a: 17,b:28});
85
+ var actual = Kojac.Utils.toJsono(input,{include: ['sum']});
86
+ expect(actual).toEqual({a: 17,b:28,sum:45});
87
+ });
88
+
89
+ it("date toJsono", function() {
90
+ var actual = Kojac.Utils.toJsono(new Date(2011,0,1));
91
+ expect(actual).toEqual("2010-12-31T16:00:00.000Z");
92
+ });
93
+
94
+ it("date serialization", function() {
95
+ var Product = Kojac.EmberModel.extend({
96
+ dob: Date
97
+ });
98
+ var product = Product.create({dob: new Date(2011,0,1)});
99
+ var jsono = Kojac.Utils.toJsono(product);
100
+ expect(jsono).toEqual({
101
+ dob: "2010-12-31T16:00:00.000Z"
102
+ })
103
+ });
104
+
105
+ });
@@ -0,0 +1,90 @@
1
+ describe("Error Handling (requires web server)", function() {
2
+
3
+ beforeEach(function () {
4
+ });
5
+
6
+ it("handles not found", function() {
7
+ var App = {};
8
+ App.cache = new Kojac.Cache();
9
+ App.kojac = new Kojac.Core({
10
+ cache: App.cache,
11
+ remoteProvider: new Kojac.RemoteProvider({
12
+ serverPath: '/sdfhjdfsdfhsjkhjdfskhjdfsjhkdfsdsf',
13
+ timeout: 2000
14
+ })
15
+ });
16
+ var op;
17
+ var req;
18
+ runs(function() {
19
+ req = App.kojac.readRequest(['order_item']).done(function(aKR){
20
+ console.log(aKR);
21
+ }).fail(function(aKR){
22
+ console.log(aKR);
23
+ });
24
+ });
25
+ waitsFor(function() {
26
+ return req.isResolved() || req.isRejected()
27
+ }, "request done", 3000);
28
+ runs(function() {
29
+ var e = req.error;
30
+ expect(e).toBeDefined();
31
+ expect(e.format).toEqual('KojacError');
32
+ expect(e.http_code).toEqual(404);
33
+ expect(e.kind).toEqual('NotFound');
34
+ expect(e.message).toBeTruthy();
35
+ expect(e.debug_message).toBeTruthy();
36
+ expect(e.xhr).toBeTruthy();
37
+ expect(e.headers).toBeTruthy();
38
+ expect(e.response).toBeTruthy();
39
+ });
40
+ });
41
+
42
+ it("Handles application Error", function(){
43
+ var App = {};
44
+ App.cache = new Kojac.Cache();
45
+ App.kojac = new Kojac.Core({
46
+ cache: App.cache,
47
+ remoteProvider: new Kojac.RemoteProvider({
48
+ //serverPath: '/sdfhjdfsdfhsjkhjdfskhjdfsjhkdfsdsf',
49
+ timeout: 2000
50
+ })
51
+ });
52
+ App.kojac.remoteProvider.mockReadOperationHandler = function(aOp) {
53
+ aOp.error = {
54
+ format: 'AppError',
55
+ kind: 'ValidationError',
56
+ errors: [{
57
+ field: 'name',
58
+ message: 'must be given'
59
+ }]
60
+ }
61
+ };
62
+ var op;
63
+ var req;
64
+ runs(function() {
65
+ req = App.kojac.executeRequest(['calc']).done(function(aKR){
66
+ console.log(aKR);
67
+ }).fail(function(aKR){
68
+ console.log(aKR);
69
+ });
70
+ });
71
+ waitsFor(function() {
72
+ return req.isResolved() || req.isRejected()
73
+ }, "request done", 3000);
74
+ runs(function() {
75
+ expect(req.error).toBeDefined();
76
+ expect(req.error).toEqual(req.ops[0].error);
77
+ expect(req.results).toBeUndefined();
78
+ expect(req.error).toEqual(
79
+ {
80
+ format: 'AppError',
81
+ kind: 'ValidationError',
82
+ errors: [{
83
+ field: 'name',
84
+ message: 'must be given'
85
+ }]
86
+ }
87
+ );
88
+ });
89
+ });
90
+ });
@@ -1,5 +1,5 @@
1
1
  // underscore_plus.js
2
- // (c) 2012 Gary McGhee, Buzzware Solutions
2
+ // (c) 2013 Gary McGhee, Buzzware Solutions
3
3
  // https://github.com/buzzware/underscore_plus
4
4
  // Underscore may be freely distributed under the MIT license.
5
5
 
@@ -120,6 +120,24 @@ _.removeKey = function(aObject,aKey) {
120
120
  return result;
121
121
  };
122
122
 
123
+ // for Twitter Typeahead. See http://code.whatcould.com/2013/02/28/underscore-templates-with-twitter-typeahead.html
124
+ _.templateTypeaheadCompatible = {
125
+ compile: function(str) {
126
+ return new _.templateTypeaheadCompiled(str);
127
+ }
128
+ }
129
+ _.templateTypeaheadCompiled = (function() {
130
+ function templateTypeaheadCompiled(str) {
131
+ this.compiledTemplate = _.template(str);
132
+ }
133
+
134
+ templateTypeaheadCompiled.prototype.render = function(context) {
135
+ return this.compiledTemplate(context);
136
+ };
137
+
138
+ return templateTypeaheadCompiled;
139
+ })();
140
+
123
141
  // // find all dynamic objects in an array that have matching values for the contents of aAndCriteria
124
142
  // public static function ObjectArrayFind(aArray:Array, aAndCriteria:Object):Array {
125
143
  // return aArray.filter(
@@ -202,6 +220,7 @@ _.bite = function(aString, aPrefix) {
202
220
  return (i===0) ? aString.substring(aPrefix.length) : aString;
203
221
  }
204
222
 
223
+ // undefined null function arguments string number date regexp object
205
224
  _.typeOf = function(aSomething) {
206
225
  if (aSomething===undefined)
207
226
  return 'undefined';
@@ -237,25 +256,315 @@ _.cloneComplexValues = function (aDestination, aSource) {
237
256
 
238
257
  _.originalClone = _.originalClone || _.clone;
239
258
  // Create a copy (shallow or deep) of an object from https://github.com/cederberg/underscore/commits/feature-clone
240
- _.clone = function(obj, deep) {
241
- if (!deep)
259
+ _.clone = function(obj, deep, ignoreFns) {
260
+ if (!deep && !ignoreFns)
242
261
  return _.originalClone(obj);
243
262
  if (!_.isObject(obj) || _.isFunction(obj)) return obj;
244
263
  if (_.isDate(obj)) return new Date(obj.getTime());
245
264
  if (_.isRegExp(obj)) return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
246
265
  var isArr = (_.isArray(obj) || _.isArguments(obj));
247
266
  if (deep) {
248
- var func = function (memo, value, key) {
249
- if (isArr)
250
- memo.push(_.clone(value, true));
251
- else
252
- memo[key] = _.clone(value, true);
267
+ var func = function(memo, value, key) {
268
+ if (!ignoreFns || !_.isFunction(value)) {
269
+ if (isArr)
270
+ memo.push(_.clone(value, true));
271
+ else
272
+ memo[key] = _.clone(value, true);
273
+ }
253
274
  return memo;
254
275
  };
255
276
  return _.reduce(obj, func, isArr ? [] : {});
256
277
  } else {
257
- return isArr ? slice.call(obj) : _.extend({}, obj);
278
+ var result;
279
+ if (isArr)
280
+ result = slice.call(obj);
281
+ else {
282
+ result = {};
283
+ var v;
284
+ for (var p in obj) {
285
+ v = obj[p];
286
+ if (ignoreFns && _.isFunction(v))
287
+ continue;
288
+ result[p] = v;
289
+ }
290
+ }
291
+ return result;
292
+ }
293
+ };
294
+
295
+ _.randomString = function (aLength, aCharSet) {
296
+ var result = [];
297
+
298
+ aLength = aLength || 5;
299
+ aCharSet = aCharSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
300
+ aCharSet = aCharSet.split('');
301
+
302
+ while (--aLength) {
303
+ result.push(aCharSet[Math.floor(Math.random() * aCharSet.length)]);
304
+ }
305
+
306
+ return result.join('');
307
+ };
308
+
309
+ _.formatNumber = function(aValue,aDecimals) {
310
+ if (aValue===null)
311
+ return '';
312
+ if (!aDecimals && aDecimals!==0)
313
+ aDecimals = 2;
314
+ return aValue.toFixed(aDecimals);
315
+ };
316
+
317
+ _.groupDigits = function(x) {
318
+ var parts = x.toString().split(".");
319
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
320
+ return parts.join(".");
321
+ };
322
+
323
+ _.sum = function() {
324
+ var result = NaN;
325
+ for (var i=0;i<arguments.length;i++) {
326
+ var v = arguments[i];
327
+ if (!_.isNumber(v))
328
+ return NaN;
329
+ if (i==0)
330
+ result = 0;
331
+ result += v;
332
+ }
333
+ return result;
334
+ };
335
+
336
+ _.formatCurrency = function(aNumber,aPrecision,aSymbol) {
337
+ if (!aPrecision)
338
+ aPrecision = 0;
339
+ if (!aSymbol)
340
+ aSymbol = '$';
341
+ return _.isFinite(aNumber) ? aSymbol+_.groupDigits(_.formatNumber(aNumber,aPrecision)) : '';
342
+ };
343
+
344
+ _.formatPercent = function(aNumber,aPrecision,aSymbol) {
345
+ if (!aPrecision)
346
+ aPrecision = 0;
347
+ if (!aSymbol)
348
+ aSymbol = '%';
349
+ return _.isFinite(aNumber) ? _.groupDigits(_.formatNumber(aNumber*100.0,aPrecision))+aSymbol : '';
350
+ };
351
+
352
+ _.toNull = function(aValue,aDefault) {
353
+ if (arguments.length==1)
354
+ aDefault = null;
355
+ return ((aValue===null) || (aValue===undefined) || (aValue===[]) || (aValue===0) || (aValue==={}) || _.isNaN(aValue)) ? aDefault : aValue;
356
+ }
357
+
358
+ _.toFinite = function(aValue,aDefault) {
359
+ return _.toNumber(aValue,aDefault);
360
+ }
361
+
362
+ _.toInt = function(aValue,aDefault) {
363
+ if (arguments.length==1)
364
+ aDefault = null;
365
+ var t = _.typeOf(aValue);
366
+ var result;
367
+ switch(t) {
368
+ case 'undefined':
369
+ case 'null':
370
+ case 'array':
371
+ case 'object':
372
+ case 'function':
373
+ case 'class':
374
+ case 'instance':
375
+ case 'error':
376
+ result = aDefault;
377
+ break;
378
+ case 'string':
379
+ case 'number':
380
+ aValue = Number(aValue);
381
+ if (!_.isFinite(aValue))
382
+ result = aDefault;
383
+ else
384
+ result = Math.round(aValue);
385
+ break;
386
+ case 'boolean':
387
+ result = aValue ? 1 : 0;
388
+ break;
389
+ default:
390
+ result = aDefault;
391
+ break;
392
+ }
393
+ return result;
394
+ };
395
+
396
+ _.toNumber = function(aValue,aDefault) {
397
+ if (arguments.length==1)
398
+ aDefault = null;
399
+ var t = _.typeOf(aValue);
400
+ var result;
401
+ switch(t) {
402
+ case 'undefined':
403
+ case 'null':
404
+ case 'array':
405
+ case 'object':
406
+ case 'function':
407
+ case 'class':
408
+ case 'instance':
409
+ case 'error':
410
+ result = aDefault;
411
+ break;
412
+ case 'string':
413
+ case 'number':
414
+ aValue = Number(aValue);
415
+ if (!_.isFinite(aValue))
416
+ result = aDefault;
417
+ else
418
+ result = aValue;
419
+ break;
420
+ case 'boolean':
421
+ result = aValue ? 1 : 0;
422
+ break;
423
+ default:
424
+ result = aDefault;
425
+ break;
426
+ }
427
+ return result;
428
+ };
429
+
430
+ _.toBoolean = function(aValue,aDefault) {
431
+ if (arguments.length==1)
432
+ aDefault = false;
433
+ if (aValue===true || aValue===false)
434
+ return aValue;
435
+ if (aValue===0)
436
+ return false;
437
+ if (_.isString(aValue)) {
438
+ var t = Number(aValue);
439
+ if (_.isFinite(t)) {
440
+ return !!t;
441
+ } else {
442
+ t = aValue.toLowerCase();
443
+ if (t==="true" || t==="yes" || t==="on")
444
+ return true;
445
+ if (t==="false" || t==="no" || t==="off")
446
+ return false;
447
+ }
448
+ } else if (_.isNumber(aValue)) {
449
+ return !!aValue;
450
+ }
451
+ return aDefault;
452
+ };
453
+
454
+ // converts simple object to array of object with id and name fields
455
+ // eg.
456
+ // _.expand_options({"cash": "Cash","consumer_mortgage": "Consumer Mortgage")
457
+ // => [{id: "cash", name:"Cash"}, {id: "consumer_mortgage",name: "Consumer Mortgage"}]
458
+ // _.expand_options(["cash","consumer_mortgage"])
459
+ // => [{id: "cash", name:"cash"}, {id: "consumer_mortgage",name: "consumer_mortgage"}]
460
+ _.expand_options = function(aObject,aIdField,aNameField) {
461
+ if (!aIdField)
462
+ aIdField = 'id';
463
+ if (!aNameField)
464
+ aNameField = 'name';
465
+ if (_.isArray(aObject)) {
466
+ return _.map(aObject,function(v){
467
+ var result = {};
468
+ result[aIdField] = v;
469
+ result[aNameField] = String(v);
470
+ return result;
471
+ });
472
+ } else {
473
+ return _.map(aObject,function(v,k){
474
+ var result = {};
475
+ result[aIdField] = k;
476
+ result[aNameField] = v;
477
+ return result;
478
+ });
479
+ }
480
+ };
481
+
482
+ // returns array of keys on object beginning with aPrefix
483
+ _.keysWithPrefix = function(aObject,aPrefix) {
484
+ var results = [];
485
+ var keys = _.keys(aObject);
486
+ for (var i=0;i<keys.length;i++) {
487
+ var k = keys[i];
488
+ if (!_.beginsWith(k,aPrefix))
489
+ continue;
490
+ results.push(k);
258
491
  }
492
+ return results;
493
+ };
494
+
495
+ // returns copy of object containing only properties beginning with aPrefix
496
+ _.pickWithPrefix = function(aObject,aPrefix) {
497
+ var result = {};
498
+ _.each(aObject,function(v, k){
499
+ if (!_.beginsWith(k,aPrefix))
500
+ return;
501
+ result[k] = v;
502
+ });
503
+ return result;
504
+ };
505
+
506
+ _.round = function(aNumber,aDecimals) {
507
+ var mult = Math.pow(10, aDecimals);
508
+ return Math.round(aNumber*mult)/mult;
509
+ };
510
+
511
+ // copies properties, excluding functions
512
+ _.copyProperties = function(aDest,aSource,aProperties,aExclude) {
513
+ var p;
514
+ var v;
515
+ if (!aDest)
516
+ aDest = {};
517
+ if (aProperties && !_.isArray(aProperties))
518
+ aProperties = [aProperties];
519
+ if (aExclude && !_.isArray(aExclude))
520
+ aExclude = [aExclude];
521
+ if (aProperties) {
522
+ for (var i=0;i<aProperties.length;i++) {
523
+ p = aProperties[i];
524
+ v = aSource[p];
525
+ if (_.isFunction(v))
526
+ continue;
527
+ if (aExclude && aExclude.indexOf(p)>=0)
528
+ continue;
529
+ if (p in aSource) aDest[p] = v;
530
+ }
531
+ } else {
532
+ for (p in aSource) {
533
+ v = aSource[p];
534
+ if (_.isFunction(v))
535
+ continue;
536
+ if (aExclude && aExclude.indexOf(p)>=0)
537
+ continue;
538
+ if (p in aSource) aDest[p] = v;
539
+ }
540
+ }
541
+ return aDest;
542
+ };
543
+
544
+
545
+ var formatRegexes = {};
546
+ // format a string
547
+ // Usage: _.format("{0} i{2}a night{1}", "This", "mare", "s ");
548
+ // Could support _.format("{number} {street}, {suburb}", {number: 27, street: "Constant St", suburb: "Highgate"}); - might already
549
+ // see http://stackoverflow.com/questions/2534803/string-format-in-javascript/2534870#2534870
550
+ _.format = function(aFormat, aValues) {
551
+ var src,v;
552
+ if (_.isObject(aValues)) {
553
+ for (p in aValues)
554
+ aFormat = aFormat.replace(formatRegexes[p] || (formatRegexes[p] = RegExp("\\{" + p + "\\}", "gm")), aValues[p]);
555
+ } else {
556
+ for (var args = arguments, i = args.length; --i;)
557
+ aFormat = aFormat.replace(formatRegexes[i - 1] || (formatRegexes[i - 1] = RegExp("\\{" + (i - 1) + "\\}", "gm")), args[i]);
558
+ }
559
+ return aFormat;
560
+ };
561
+
562
+ _.randomInt = function(aValues) {
563
+ return Math.floor(Math.random()*aValues);
564
+ };
565
+
566
+ _.randomIntRange = function(aMin,aMax) {
567
+ return aMin + Math.floor(Math.random()*(aMax-aMin+1));
259
568
  };
260
569
 
261
570
  }).call(this);