marty 4.0.0.rc2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +1 -0
  4. data/.rubocop_todo.yml +3 -16
  5. data/.ssh-docker/.keep +0 -0
  6. data/Dockerfile.dummy +3 -0
  7. data/Gemfile +19 -15
  8. data/app/components/marty/base_rule_view.rb +104 -10
  9. data/app/components/marty/base_rule_view/client/base_rule_view.js +24 -0
  10. data/app/components/marty/data_grid_user_view.rb +39 -0
  11. data/app/components/marty/data_grid_view.rb +68 -18
  12. data/app/components/marty/extras/layout.rb +1 -1
  13. data/app/components/marty/grid.rb +1 -1
  14. data/app/components/marty/grid/client/grid.js +29 -13
  15. data/app/components/marty/import_view.rb +3 -3
  16. data/app/components/marty/main_auth_app.rb +11 -1
  17. data/app/components/marty/report_form.rb +6 -6
  18. data/app/components/marty/script_form.rb +5 -5
  19. data/app/components/marty/script_tester.rb +2 -2
  20. data/app/components/marty/user_view.rb +3 -9
  21. data/app/models/marty/base_rule.rb +92 -32
  22. data/app/models/marty/data_grid.rb +92 -22
  23. data/app/models/marty/event.rb +2 -2
  24. data/app/models/marty/promise.rb +4 -4
  25. data/app/models/marty/role_type.rb +14 -1
  26. data/app/services/marty/data_grid_view/save_grid.rb +2 -2
  27. data/app/services/marty/promises/delorean/create.rb +2 -2
  28. data/app/services/marty/promises/ruby/create.rb +2 -2
  29. data/config/locales/en.yml +11 -2
  30. data/db/migrate/108_add_data_grid_perms.rb +16 -0
  31. data/db/migrate/508_add_not_to_data_grids_tables.rb +18 -0
  32. data/db/migrate/509_update_dg_plpgsql_v1_fns.rb +13 -0
  33. data/db/sql/query_grid_dir_v1.sql +16 -2
  34. data/docker-compose.dummy.yml +1 -0
  35. data/lib/marty/content_handler.rb +2 -2
  36. data/lib/marty/data_change.rb +1 -1
  37. data/lib/marty/data_conversion.rb +3 -4
  38. data/lib/marty/data_importer.rb +4 -4
  39. data/lib/marty/mcfly_model.rb +7 -10
  40. data/lib/marty/migrations.rb +1 -1
  41. data/lib/marty/monkey.rb +2 -2
  42. data/lib/marty/promise_job.rb +5 -5
  43. data/lib/marty/promise_proxy.rb +2 -2
  44. data/lib/marty/promise_ruby_job.rb +4 -4
  45. data/lib/marty/version.rb +1 -1
  46. data/make-app.mk +1 -1
  47. data/marty.gemspec +13 -18
  48. data/other/marty/diagnostic/aws/ec2_instance.rb +17 -2
  49. data/other/marty/diagnostic/aws/error.rb +8 -0
  50. data/other/marty/diagnostic/database.rb +2 -2
  51. data/other/marty/diagnostic/delayed_job_version.rb +0 -1
  52. data/spec/dummy/app/components/gemini/my_rule_view.rb +32 -6
  53. data/spec/dummy/app/models/gemini/fannie_bup.rb +13 -20
  54. data/spec/dummy/app/models/gemini/my_rule.rb +4 -0
  55. data/spec/dummy/app/models/gemini/xyz_rule.rb +3 -1
  56. data/spec/dummy/config/application.rb +1 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +1 -1
  58. data/spec/dummy/db/migrate/20190702115241_add_simple_guards_options_to_rules.rb +37 -0
  59. data/spec/features/data_grid_spec.rb +109 -47
  60. data/spec/features/reporting_spec.rb +4 -4
  61. data/spec/features/rule_spec.rb +62 -31
  62. data/spec/features/scripting_spec.rb +3 -3
  63. data/spec/features/user_view_spec.rb +17 -8
  64. data/spec/fixtures/csv/rule/MyRule.csv +4 -1
  65. data/spec/lib/data_importer_spec.rb +8 -8
  66. data/spec/lib/mcfly_model_spec.rb +6 -6
  67. data/spec/models/data_grid_spec.rb +139 -7
  68. data/spec/models/rule_spec.rb +116 -9
  69. data/spec/spec_helper.rb +2 -2
  70. data/spec/support/netzke.rb +4 -3
  71. metadata +55 -54
  72. data/Gemfile.lock +0 -289
@@ -206,8 +206,8 @@ DELOREAN
206
206
  end
207
207
 
208
208
  wait_for_element do
209
- expect(page).to have_content('XYZ1,XYZ2,XYZ3,XYZ4 1,2,3,4 2,4,6,8 ' +
210
- '3,6,9,12 4,8,12,16')
209
+ expect(page).to have_content("XYZ1,XYZ2,XYZ3,XYZ4\n1,2,3,4\n2,4,6,8\n" +
210
+ "3,6,9,12\n4,8,12,16")
211
211
  end
212
212
  end
213
213
 
@@ -263,8 +263,8 @@ DELOREAN
263
263
  url = generate_rep_url(format: 'txt') + URI.encode('&disposition=inline')
264
264
  visit url
265
265
  wait_for_element do
266
- expect(page).to have_content('XYZ1,XYZ2,XYZ3,XYZ4 1,2,3,4 2,4,6,8 ' +
267
- '3,6,9,12 4,8,12,16')
266
+ expect(page).to have_content("XYZ1,XYZ2,XYZ3,XYZ4\n1,2,3,4\n2,4,6,8\n" +
267
+ "3,6,9,12\n4,8,12,16")
268
268
  end
269
269
  end
270
270
 
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'marty_rspec'
3
2
 
4
3
  feature 'rule view', js: true do
5
4
  before(:all) do
@@ -39,7 +38,6 @@ feature 'rule view', js: true do
39
38
  JS
40
39
  end
41
40
 
42
- # click_checkbox in marty_rspec not working here for some reason
43
41
  def click_checkbox(name)
44
42
  q = %Q(checkbox[fieldLabel="#{name}"])
45
43
  page.execute_script <<-JS
@@ -48,7 +46,6 @@ feature 'rule view', js: true do
48
46
  JS
49
47
  end
50
48
 
51
- # click_col in marty_rspec is not reliable
52
49
  def click_column(rv, name)
53
50
  cid = col_id(rv, name)
54
51
  c = find('#' + cid)
@@ -115,7 +112,7 @@ feature 'rule view', js: true do
115
112
  time_fill_in(1, '08:03:01')
116
113
  press('OK')
117
114
  wait_for_ajax
118
- expect(mrv.row_count()).to eq(9)
115
+ expect(mrv.row_count()).to eq(12)
119
116
  expect(mrv.get_row_vals(1)).to include('name' => 'abc',
120
117
  'rule_type' => 'SimpleRule',
121
118
  'start_dt' => '2013-01-01T11:03:01',
@@ -339,7 +336,7 @@ computed_value = if paramb
339
336
  else (grid2_grid_result||1) / param1
340
337
  EOL
341
338
 
342
- names = mrv.get_col_vals(:name, 9, 0)
339
+ names = mrv.get_col_vals(:name, 12, 0)
343
340
  idx = names.index { |n| n == 'Rule3' } + 1
344
341
  mrv.select_row(idx)
345
342
  press('Edit')
@@ -362,35 +359,68 @@ EOL
362
359
  'ZRule3', 'ZRule4',
363
360
  'ZRule5'])
364
361
  xrv.select_row(1)
362
+
363
+ expect(page).to_not have_content('NOT (G2V1)')
364
+
365
365
  press('Edit')
366
- fill_in('Range Guard 1', with: '[100,200)')
367
- fill_in('Range Guard 2', with: '[30,40)')
366
+
367
+ fill_in('Guard two', with: 'G2V1')
368
+ click_checkbox('Not')
369
+
370
+ fill_in('Range Guard 1', with: '[100,200)', fill_options: { clear: :backspace })
371
+ fill_in('Range Guard 2', with: '[30,40)', fill_options: { clear: :backspace })
368
372
  press('OK')
373
+
369
374
  wait_for_ajax
370
- r = Gemini::XyzRule.get_matches('infinity', {}, 'g_range1' => 150,
371
- 'g_range2' => 35)
375
+
376
+ r = Gemini::XyzRule.get_matches(
377
+ 'infinity',
378
+ {},
379
+ 'g_range1' => 150,
380
+ 'g_range2' => 35,
381
+ 'guard_two' => 'G2V2'
382
+ )
372
383
 
373
384
  expect(r.to_a.count).to eq(1)
374
- exp = { 'user_id' => 1,
375
- 'o_user_id' => nil,
376
- 'name' => 'ZRule1',
377
- 'engine' => 'Gemini::XyzRuleScriptSet',
378
- 'rule_type' => 'ZRule',
379
- 'start_dt' => DateTime.parse('2017-1-1 08:01:00'),
380
- 'simple_guards' => { 'g_bool' => false,
381
- 'g_date' => '2017-1-1',
382
- 'g_range1' => '[100,200)',
383
- 'g_range2' => '[30,40)',
384
- 'g_string' => 'aaa',
385
- 'g_integer' => '5',
386
- 'g_datetime' => '2017-1-1 12:00:01' },
387
- 'computed_guards' => {},
388
- 'grids' => { 'grid1' => 'DataGrid1' },
389
- 'results' =>
390
- { 'bvlen' => 'base_value.length',
391
- 'bv' => 'base_value' } }
385
+ exp = {
386
+ 'user_id' => 1,
387
+ 'o_user_id' => nil,
388
+ 'name' => 'ZRule1',
389
+ 'engine' => 'Gemini::XyzRuleScriptSet',
390
+ 'rule_type' => 'ZRule',
391
+ 'start_dt' => DateTime.parse('2017-1-1 08:01:00'),
392
+ 'simple_guards' => {
393
+ 'g_bool' => false,
394
+ 'g_date' => '2017-1-1',
395
+ 'guard_two' => 'G2V1',
396
+ 'g_range1' => '[100,200)',
397
+ 'g_range2' => '[30,40)',
398
+ 'g_string' => 'aaa',
399
+ 'g_integer' => '5',
400
+ 'g_datetime' => '2017-1-1 12:00:01'
401
+ },
402
+ 'simple_guards_options' => {
403
+ 'g_bool' => { 'not' => false },
404
+ 'g_date' => { 'not' => false },
405
+ 'g_datetime' => { 'not' => false },
406
+ 'g_integer' => { 'not' => false },
407
+ 'g_range1' => { 'not' => false },
408
+ 'g_range2' => { 'not' => false },
409
+ 'g_string' => { 'not' => false },
410
+ 'guard_two' => { 'not' => true }
411
+ },
412
+ 'computed_guards' => {},
413
+ 'grids' => { 'grid1' => 'DataGrid1' },
414
+ 'results' => {
415
+ 'bvlen' => 'base_value.length',
416
+ 'bv' => 'base_value'
417
+ }
418
+ }
392
419
 
393
420
  expect(r.first.as_json).to include(exp)
421
+
422
+ expect(page).to have_content('NOT (G2V1)')
423
+
394
424
  expect(xrv.get_col_vals(:g_string, 8, 0)).to eq(['aaa', 'bbb', 'ccc', 'ddd',
395
425
  'eee', 'eee', 'eee', 'eee'])
396
426
  click_column(xrv, 'String list Guard')
@@ -418,8 +448,9 @@ EOL
418
448
  %Q({"grid1":"DataGrid2"}),
419
449
  %Q({"grid1":"DataGrid1"}),
420
450
  %Q({"grid1":"DataGrid1"})])
451
+
421
452
  press('Applications')
422
- press('Data Grids')
453
+ press('Data Grids Admin')
423
454
  dgv = netzke_find('data_grid_view')
424
455
  cvs = dgv.get_col_vals(:name, 4, 0)
425
456
  ind1 = cvs.index('DataGrid1') + 1
@@ -447,9 +478,9 @@ EOL
447
478
  go_to_my_rules
448
479
  wait_for_ajax
449
480
 
450
- names = mrv.get_col_vals(:name, 9, 0)
451
- gvs = mrv.get_col_vals(:grids, 9, 0)
452
- rvs = mrv.get_col_vals(:results, 9, 0)
481
+ names = mrv.get_col_vals(:name, 12, 0)
482
+ gvs = mrv.get_col_vals(:grids, 12, 0)
483
+ rvs = mrv.get_col_vals(:results, 12, 0)
453
484
  expect(JSON.parse(gvs[names.index('abc')])).to eq(g1h)
454
485
  expect(JSON.parse(gvs[names.index('Rule2b')])).to eq(g1h +
455
486
  { 'grid2' => 'DataGrid2' })
@@ -221,7 +221,7 @@ feature 'under Applications menu, Scripting workflows', js: true do
221
221
 
222
222
  and_by 'form displays correct body' do
223
223
  wait_for_ajax
224
- expect(page).to have_content '1 #5 2 #1 3 #2 4 #3'
224
+ expect(page).to have_content "1\n#5\n2\n#1\n3\n#2\n4\n#3"
225
225
  end
226
226
 
227
227
  and_by 'select different tag' do
@@ -235,7 +235,7 @@ feature 'under Applications menu, Scripting workflows', js: true do
235
235
 
236
236
  and_by 'form displays updated body' do
237
237
  wait_for_ajax
238
- expect(page).to have_content '1 #5 2 #1'
238
+ expect(page).to have_content "1\n#5\n2\n#1"
239
239
  end
240
240
  end
241
241
 
@@ -288,7 +288,7 @@ feature 'under Applications menu, Scripting workflows', js: true do
288
288
  tag_grid.select_row(2)
289
289
  wait_for_ajax
290
290
  script_grid.select_row(5)
291
- expect(page).to have_content '1 #123 2 #456'
291
+ expect(page).to have_content "1\n#123\n2\n#456"
292
292
  expect(tag_grid.get_row_vals(2)).to netzke_include(comment: 'ABCD')
293
293
  end
294
294
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- feature 'under Sytem menu, User Management worflows', js: true do
3
+ feature 'under System menu, User Management worflows', js: true do
4
4
  def go_to_user_view
5
5
  press('System')
6
6
  press('User Management')
@@ -8,7 +8,7 @@ feature 'under Sytem menu, User Management worflows', js: true do
8
8
  end
9
9
 
10
10
  def go_to_user_view_backdoor
11
- sys_btn = first(:btn, 'System')
11
+ sys_btn = first(:btn, 'System') rescue nil
12
12
  if sys_btn
13
13
  sys_btn.click
14
14
  expect(page).not_to have_content 'User Management'
@@ -77,6 +77,7 @@ feature 'under Sytem menu, User Management worflows', js: true do
77
77
 
78
78
  and_by 'check row got edited' do
79
79
  wait_for_ajax
80
+ r2 = user_view.get_row_vals(2)
80
81
  expect(user_view.get_row_vals(2)).to netzke_include(
81
82
  login: 'new_login',
82
83
  firstname: 'new_fname',
@@ -114,6 +115,9 @@ feature 'under Sytem menu, User Management worflows', js: true do
114
115
  by 'check buttons' do
115
116
  find(:btn, 'New User', match: :first)
116
117
  user_view.select_row(1)
118
+ expect { find(:btn, 'New User') }.not_to raise_error
119
+ expect { find(:btn, 'Edit') }.not_to raise_error
120
+ expect { find(:btn, 'Delete') }.not_to raise_error
117
121
  expect(btn_disabled?('New User')).to be_falsy
118
122
  expect(btn_disabled?('Edit')).to be_falsy
119
123
  expect(btn_disabled?('Delete')).to be_falsy
@@ -127,6 +131,9 @@ feature 'under Sytem menu, User Management worflows', js: true do
127
131
  by 'check buttons' do
128
132
  find(:btn, 'New User', match: :first)
129
133
  user_view.select_row(1)
134
+ expect { find(:btn, 'New User') }.not_to raise_error
135
+ expect { find(:btn, 'Edit') }.not_to raise_error
136
+ expect { find(:btn, 'Delete') }.not_to raise_error
130
137
  expect(btn_disabled?('New User')).to be_falsy
131
138
  expect(btn_disabled?('Edit')).to be_falsy
132
139
  expect(btn_disabled?('Delete')).to be_falsy
@@ -140,9 +147,10 @@ feature 'under Sytem menu, User Management worflows', js: true do
140
147
  user_view = netzke_find('user_view')
141
148
  by 'check buttons' do
142
149
  user_view.select_row(1)
143
- expect(page).not_to have_content('New User')
144
- expect(page).not_to have_content('Edit')
145
- expect(page).not_to have_content('Delete')
150
+ err = /Unable to find btn/
151
+ ['New User', 'Edit', 'Delete'].each do |btn_name|
152
+ expect { find(:btn, btn_name) }.to raise_error(err)
153
+ end
146
154
  end
147
155
  end
148
156
 
@@ -153,9 +161,10 @@ feature 'under Sytem menu, User Management worflows', js: true do
153
161
  user_view = netzke_find('user_view')
154
162
  by 'check buttons' do
155
163
  user_view.select_row(1)
156
- expect(page).not_to have_content('New User')
157
- expect(page).not_to have_content('Edit')
158
- expect(page).not_to have_content('Delete')
164
+ err = /Unable to find btn/
165
+ ['New User', 'Edit', 'Delete'].each do |btn_name|
166
+ expect { find(:btn, btn_name) }.to raise_error(err)
167
+ end
159
168
  end
160
169
  end
161
170
  end
@@ -1,4 +1,4 @@
1
- name,rule_type,start_dt,end_dt,other_flag,simple_guards,computed_guards,grids,results
1
+ name,rule_type,start_dt,end_dt,other_flag,simple_guards,computed_guards,grids,results,simple_guards_options
2
2
  Rule1,SimpleRule,2017-1-1 12:00:00,2017-4-1,,"{""g_has_default"":""different"",""g_array"":[""G1V1"",""G1V3""],""g_single"":""G2V2"",""g_string"":""Hi Mom"",""g_bool"":true,""g_bool_def"":false,""g_nbool_def"":true,""g_range"":""[50,)"",""g_integer"":10}",,,"{""simple_result"":""\""a value\""""}"
3
3
  Rule2,SimpleRule,2017-2-1 14:00:00,2017-4-1,true,"{""g_array"":[""G1V2""],""g_single"":""G2V3"",""g_string"":""abc"",""g_bool"":true,""g_range"":""(,50]"",""g_integer"":11}",,,"{
4
4
  ""simple_result"":""\""b value\"" # with comment "",
@@ -13,3 +13,6 @@ Rule2c,SimpleRule,2017-2-1 14:00:00,2017-4-1,true,"{""g_array"":[""G1V2""],""g_s
13
13
  Rule3,ComplexRule,2017-3-1 00:00:00,2017-4-1,false,"{""g_array"":[""G1V2"",""G1V3""],""g_string"":""def"",""g_integer"":11}","{""cguard1"":""1==1"",""cguard2"":""[param2 == 'abc', \""a string\""]""}","{""grid1"":""DataGrid1"",""grid2"":""DataGrid2""}","{""simple_result"":""\""c value\"""",""computed_value"":""if paramb\n then param1 / (grid1_grid_result||1)\n else (grid2_grid_result||1) / param1""}"
14
14
  Rule4,ComplexRule,2017-4-1 15:00:01,2017-5-1,,"{""g_array"":[""G1V2"",""G1V3""],""g_string"":""Hi Mom"",""g_integer"":11}","{""cguard1"":""1==1"",""cguard2"":""param2 == \""abc\""""}","{""grid1"":""DataGrid1"",""grid2"":""DataGrid2""}","{""computed_name_grid"":""\""DataGrid\""+\""X\"""", ""simple_result"":""computed_name_grid"",""grid_sum"":""computed_name_grid_result + (grid1_grid_result||0)+(grid2_grid_result||0)""}"
15
15
  Rule5,ComplexRule,2017-4-2 15:00:01,2017-5-1,,"{""g_string"":""zzz"",""g_integer"":3757,""g_has_default"":""foo""}","{""cguard1"":""[1==1, \""a string\""]""}",,"{""flavor"": ""[\""cherry\"",\""lemon\""][param2]"",""other_grid"": ""\""DataGrid4\"""",""final_value"": ""other_grid_result * 3""}"
16
+ NotRule1,SimpleRule,2017-4-2 15:00:01,2017-5-1,,"{""g_string"":""zzz"",""g_range"":""(,20]"",""g_integer"":3757,""g_has_default"":""foo"",""g_bool"":false,""g_array"":[""G1V1""]}","{""cguard1"":""[1==1, \""a string\""]""}",,"{""flavor"": ""[\""cherry\"",\""lemon\""][param2]"",""other_grid"": ""\""DataGrid4\"""",""final_value"": ""other_grid_result * 3""}","{""g_string"":{""not"":true},""g_integer"":{""not"":false}}"
17
+ NotRule2,SimpleRule,2017-4-2 15:00:01,2017-5-1,,"{""g_string"":""zzz"",""g_range"":""(,20]"",""g_integer"":3757,""g_has_default"":""foo"",""g_bool"":false,""g_array"":[""G1V1""]}","{""cguard1"":""[1==1, \""a string\""]""}",,"{""flavor"": ""[\""cherry\"",\""lemon\""][param2]"",""other_grid"": ""\""DataGrid4\"""",""final_value"": ""other_grid_result * 3""}","{""g_string"":{""not"":true},""g_integer"":{""not"":true}}"
18
+ NotRule3,SimpleRule,2017-4-2 15:00:01,2017-5-1,,"{""g_string"":""zzz"",""g_range"":""(,200]"",""g_integer"":3757,""g_has_default"":""foo"",""g_bool"":false,""g_array"":[""G1V1""]}","{""cguard1"":""[1==1, \""a string\""]""}",,"{""flavor"": ""[\""cherry\"",\""lemon\""][param2]"",""other_grid"": ""\""DataGrid4\"""",""final_value"": ""other_grid_result * 3""}","{""g_string"":{""not"":true},""g_integer"":{""not"":true}, ""g_range"":{""not"":true}}"
@@ -349,8 +349,8 @@ describe DataImporter do
349
349
 
350
350
  begin
351
351
  Marty::DataImporter.do_import_summary(Gemini::FannieBup, fannie_bup4)
352
- rescue Marty::DataImporter::Error => exc
353
- exc.lines.should == [0]
352
+ rescue Marty::DataImporter::Error => e
353
+ e.lines.should == [0]
354
354
  else
355
355
  raise 'should have had an exception'
356
356
  end
@@ -390,9 +390,9 @@ describe DataImporter do
390
390
 
391
391
  begin
392
392
  Marty::DataImporter.do_import_summary(Gemini::FannieBup, fannie_bup5)
393
- rescue Marty::DataImporter::Error => exc
394
- exc.lines.should == [1]
395
- exc.message.should =~ /Conv Fixed XX/
393
+ rescue Marty::DataImporter::Error => e
394
+ e.lines.should == [1]
395
+ e.message.should =~ /Conv Fixed XX/
396
396
  else
397
397
  raise 'should have had an exception'
398
398
  end
@@ -409,9 +409,9 @@ describe DataImporter do
409
409
  begin
410
410
  res = Marty::DataImporter.
411
411
  do_import_summary(Gemini::FannieBup, fannie_bup6)
412
- rescue Marty::DataImporter::Error => exc
413
- exc.lines.should == [1]
414
- exc.message.should =~ /bad float/
412
+ rescue Marty::DataImporter::Error => e
413
+ e.lines.should == [1]
414
+ e.message.should =~ /bad float/
415
415
  else
416
416
  raise 'should have had an exception'
417
417
  end
@@ -113,16 +113,16 @@ describe 'McflyModel' do
113
113
  a1 = @engine.evaluate('A', 'lookup', params)
114
114
  a2 = @engine.evaluate('A', 'clookup', params)
115
115
  expect(a1).to eq(a2) # cache/non return same
116
- expect(a1.class).to eq(Hash) # mode default so return hash
117
- expect(a2.class).to eq(Hash)
116
+ expect(a1.class).to eq(OpenStruct) # mode default so return OS
117
+ expect(a2.class).to eq(OpenStruct)
118
118
 
119
119
  # check that keys are non mcfly non uniqueness
120
- expect(a1.to_h.keys.to_set).to eq(Set['buy_up', 'buy_down'])
120
+ expect(a1.to_h.keys.to_set).to eq(Set[:buy_up, :buy_down])
121
121
  end
122
122
 
123
123
  it 'lookup non generated' do
124
124
  # a1 will be AR Relations
125
- # b1 will be hash because the b fns return #first
125
+ # b1 will be OpenStructs because the b fns return #first
126
126
  e_id = Gemini::Entity.where(name: 'PLS').first.id
127
127
  bc_id = Gemini::BudCategory.where(name: 'Conv Fixed 20').first.id
128
128
  p = { 'e_id' => e_id, 'bc_id' => bc_id }
@@ -141,10 +141,10 @@ describe 'McflyModel' do
141
141
  # a1 is AR but still missing the FK entity_id so will raise
142
142
  expect { a1.first.entity }.to raise_error(/missing attribute: entity_id/)
143
143
 
144
- expect(b1.class).to eq(Hash)
144
+ expect(b1.class).to eq(OpenStruct)
145
145
 
146
146
  # make sure b1 has correct keys
147
- expect(b1.to_h.keys.to_set).to eq(Set['buy_up', 'buy_down'])
147
+ expect(b1.to_h.keys.to_set).to eq(Set[:buy_up, :buy_down])
148
148
  end
149
149
 
150
150
  it 'lookup mode nil' do
@@ -177,6 +177,16 @@ Investor Services\t-0.625
177
177
  Admin Services\t-1.0
178
178
  Admin Services Plus\t-1.625
179
179
  Investor Services Acadamy\t-0.5
180
+ EOS
181
+
182
+ Gl = <<EOS
183
+ lenient
184
+ fha_203k_option2\tstring\tv\tfha_203k_option2
185
+
186
+ Investor Services\t-0.625
187
+ NOT (Admin Premium Services|Admin Services|Admin Services Plus)\t-1.0
188
+ Admin Services Plus\t-1.625
189
+ Investor Services Acadamy\t-0.5
180
190
  EOS
181
191
 
182
192
  before(:each) do
@@ -295,8 +305,8 @@ EOS
295
305
  let(:pt) { 'infinity' }
296
306
 
297
307
  before(:each) do
298
- ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'Ga', 'Gb',
299
- 'Gc', 'Gd', 'Ge', 'Gf', 'Gg', 'Gh', 'Gj'].each do |g|
308
+ %w[G1 G2 G3 G4 G5 G6 G7 G8 Ga Gb
309
+ Gc Gd Ge Gf Gg Gh Gj Gl].each do |g|
300
310
  dg_from_import(g, "Marty::DataGridSpec::#{g}".constantize)
301
311
  end
302
312
  end
@@ -624,18 +634,22 @@ EOS
624
634
  expected_metadata = [{ 'dir' => 'v',
625
635
  'attr' => 'units',
626
636
  'keys' => [[1, 2], [1, 2], [3, 4], [3, 4]],
637
+ 'nots' => [false, false, false, false],
627
638
  'type' => 'integer' },
628
639
  { 'dir' => 'v',
629
640
  'attr' => 'ltv',
630
641
  'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]'],
642
+ 'nots' => [false, false, false, false],
631
643
  'type' => 'numrange' },
632
644
  { 'dir' => 'h',
633
645
  'attr' => 'cltv',
634
646
  'keys' => ['[100,110)', '[110,120)', '[120,]'],
647
+ 'nots' => [false, false, false],
635
648
  'type' => 'numrange' },
636
649
  { 'dir' => 'h',
637
650
  'attr' => 'fico',
638
651
  'keys' => ['[600,700)', '[700,750)', '[750,]'],
652
+ 'nots' => [false, false, false],
639
653
  'type' => 'numrange' }]
640
654
 
641
655
  dgh = Marty::DataGrid.lookup_h(pt, 'G2')
@@ -651,15 +665,18 @@ EOS
651
665
  expected_metadata = [{ 'dir' => 'v',
652
666
  'attr' => 'state',
653
667
  'keys' => [['CA'], ['HI', 'TX'], ['NM'], ['MA'], nil],
668
+ 'nots' => [false, false, false, false, false],
654
669
  'type' => 'string' },
655
670
  { 'dir' => 'v',
656
671
  'attr' => 'ltv',
657
672
  'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]',
658
673
  '[,80]'],
674
+ 'nots' => [false, false, false, false, false],
659
675
  'type' => 'numrange' },
660
676
  { 'dir' => 'h',
661
677
  'attr' => 'fico',
662
678
  'keys' => ['[600,700)', '[700,750)', '[750,]'],
679
+ 'nots' => [false, false, false],
663
680
  'type' => 'numrange' }]
664
681
  dgh = Marty::DataGrid.lookup_h(pt, 'G8')
665
682
  res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
@@ -676,6 +693,7 @@ EOS
676
693
  expected_metadata = [{ 'dir' => 'v',
677
694
  'attr' => 'ltv',
678
695
  'keys' => ['[,115]', '(115,135]', '(135,140]'],
696
+ 'nots' => [false, false, false],
679
697
  'type' => 'numrange' }]
680
698
  dgh = Marty::DataGrid.lookup_h(pt, 'G8')
681
699
  res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
@@ -698,6 +716,7 @@ EOS
698
716
  dgh, nil, false, true)
699
717
  end
700
718
  end
719
+
701
720
  it 'should handle all quote chars in grid inputs' do
702
721
  dgh = Marty::DataGrid.lookup_h(pt, 'G1')
703
722
  # single, double, backslash, grave, acute, unicode quotes: left single,
@@ -710,12 +729,70 @@ EOS
710
729
  pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
711
730
  end
712
731
  end
732
+
713
733
  it 'should handle quote chars in object name' do
714
734
  dgh = Marty::DataGrid.lookup_h(pt, 'G1')
715
735
  st = Gemini::State.new(name: "'\\")
716
736
  res = Marty::DataGrid.lookup_grid_distinct_entry_h(
717
737
  pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
718
738
  end
739
+
740
+ it 'Should handle NOT condition in lookups' do
741
+ dgh = Marty::DataGrid.lookup_h(pt, 'Gl')
742
+
743
+ g1_res = lookup_grid_helper(
744
+ 'infinity',
745
+ 'Gl',
746
+ { 'fha_203k_option2' => 'Admin Services Plus' },
747
+ false,
748
+ true
749
+ )
750
+ expect(g1_res).to eq([-1.625, 'Gl'])
751
+
752
+ g1_res = lookup_grid_helper(
753
+ 'infinity',
754
+ 'Gl',
755
+ { 'fha_203k_option2' => 'Not Existing Services' },
756
+ false,
757
+ true
758
+ )
759
+ expect(g1_res).to eq([-1.0, 'Gl'])
760
+
761
+ g1_res = lookup_grid_helper(
762
+ 'infinity',
763
+ 'Gl',
764
+ { 'fha_203k_option2' => 'Admin Services' },
765
+ false,
766
+ true
767
+ )
768
+ expect(g1_res).to eq([nil, 'Gl'])
769
+ end
770
+
771
+ it 'Should handle NOT condition in import' do
772
+ dg = dg_from_import('Gl0', Gl)
773
+ expect(dg.id).to be_present
774
+
775
+ expect(dg.metadata.first['nots']).to eq([false, true, false, false])
776
+ expect(dg.metadata.first['keys']).to eq(
777
+ [
778
+ ['Investor Services'],
779
+ ['Admin Premium Services', 'Admin Services', 'Admin Services Plus'],
780
+ ['Admin Services Plus'],
781
+ ['Investor Services Acadamy']
782
+ ]
783
+ )
784
+
785
+ indexes = Marty::GridIndexString.where(data_grid_id: dg.id)
786
+
787
+ expect(indexes.size).to eq 4
788
+ expect(indexes.where(not: true).size).to eq 1
789
+
790
+ not_index = indexes.find_by(not: true)
791
+ expect(not_index.key).to eq(
792
+ ['Admin Premium Services', 'Admin Services', 'Admin Services Plus']
793
+ )
794
+ expect(not_index.index).to eq(1)
795
+ end
719
796
  end
720
797
 
721
798
  describe 'exports' do
@@ -725,6 +802,11 @@ EOS
725
802
 
726
803
  expect(dg.export).to eq(dg2.export)
727
804
  end
805
+
806
+ it 'Should handle NOT condition in export' do
807
+ dg = dg_from_import('Gl0', Gl)
808
+ expect(dg.export.delete("\r")).to eq(Gl)
809
+ end
728
810
  end
729
811
 
730
812
  describe 'updates' do
@@ -757,15 +839,50 @@ EOS
757
839
  end
758
840
 
759
841
  it 'should be able to export and import back grids' do
760
- [G1, G2, G3, G4, G5, G6, G7, G8, G9, Ga, Gb].each_with_index do |grid, i|
842
+ [G1, G2, G3, G4, G5, G6, G7, G8, G9, Ga, Gb, Gl].each_with_index do |grid, i|
761
843
  dg = dg_from_import("G#{i}", grid)
762
844
  g1 = dg.export
845
+
763
846
  dg = dg_from_import("Gx#{i}", g1)
764
847
  g2 = dg.export
848
+
849
+ dg1 = Marty::DataGrid.lookup_h('infinity', "G#{i}").except(
850
+ 'id',
851
+ 'group_id',
852
+ 'created_dt',
853
+ 'name',
854
+ )
855
+
856
+ dg2 = Marty::DataGrid.lookup_h('infinity', "Gx#{i}").except(
857
+ 'id',
858
+ 'group_id',
859
+ 'created_dt',
860
+ 'name',
861
+ )
862
+
765
863
  expect(g1).to eq g2
864
+ expect(dg1).to eq dg2
766
865
  end
767
866
  end
768
867
 
868
+ it 'Should handle NOT condition in update' do
869
+ dgb = dg_from_import('Gl', Gl, '1/1/2014')
870
+ new_gl = Gl.sub(/-1.0/, '-3.45').sub('Investor Services', 'NOT (Investor Services)')
871
+ dgb.update_from_import('Gl', new_gl, '1/1/2015')
872
+
873
+ grids = Marty::DataGrid.where(name: 'Gl')
874
+ expect(grids.size).to eq 2
875
+
876
+ old_dg = grids.where.not(obsoleted_dt: 'infinity').first
877
+ new_dg = grids.where(obsoleted_dt: 'infinity').first
878
+
879
+ expect(old_dg.metadata.first['nots']).to eq [false, true, false, false]
880
+ expect(old_dg.data).to eq [[-0.625], [-1.0], [-1.625], [-0.5]]
881
+
882
+ expect(new_dg.metadata.first['nots']).to eq [true, true, false, false]
883
+ expect(new_dg.data).to eq [[-0.625], [-3.45], [-1.625], [-0.5]]
884
+ end
885
+
769
886
  it 'should be able to externally export/import grids' do
770
887
  load_scripts(nil, Date.today)
771
888
 
@@ -814,7 +931,8 @@ EOS
814
931
  # rubocop:disable Style/NestedTernaryOperator
815
932
  type_str = type == 'float' ? (explicit_float ? 'float' : nil) : type
816
933
  # rubocop:enable Style/NestedTernaryOperator
817
- top = [lenient_str, type_str].compact.join(' ') + "\t" + constraint + "\n"
934
+ con_part = constraint.present? ? "\t" + constraint : ''
935
+ top = [lenient_str, type_str].compact.join(' ') + con_part + "\n"
818
936
  (top =~ /\A\s*\z/ ? '' : top) +
819
937
  <<~EOS
820
938
  b\tboolean\tv
@@ -836,8 +954,8 @@ EOS
836
954
  tests = JSON.parse(File.read('spec/fixtures/json/data_grid.json'))
837
955
  aggregate_failures do
838
956
  tests.each do |test|
839
- keys = %w[id type constraint values error]
840
- id, type, constraint, values, error = test.values_at(*keys)
957
+ keys = %w[id type constraint values error line1]
958
+ id, type, constraint, values, error, line1 = test.values_at(*keys)
841
959
  err_re = Regexp.new(error) if error
842
960
  # for float, do both ex- and implicit declaration
843
961
  exfls = type == 'float' ? [true, false] : [true]
@@ -848,7 +966,21 @@ EOS
848
966
  got = nil
849
967
  tnam = "Test #{id} lenient=#{lenient} exfl=#{exfl}"
850
968
  begin
851
- dg_from_import(tnam, grid)
969
+ dg = dg_from_import(tnam, grid)
970
+
971
+ # make sure export of line1 works correctly
972
+ # when dg is lenient and/or has constraint and/or
973
+ # not float
974
+ next unless lenient || constraint.present? ||
975
+ type != 'float'
976
+
977
+ # also skip grids where we included float explicitly
978
+ # because export will convert back to implicit
979
+ next if type == 'float' && exfl
980
+
981
+ dga = dg.export_array
982
+ line1 = dga.first.first.join("\t") + "\n"
983
+ expect(line1).to eq(grid.lines.first)
852
984
  rescue StandardError => e
853
985
  got = e.message
854
986
  end