marty 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +2 -2
- data/app/components/marty/base_rule_view.rb +279 -0
- data/app/components/marty/delorean_rule_view.rb +26 -0
- data/app/components/marty/extras/layout.rb +22 -7
- data/app/components/marty/log_view.rb +1 -1
- data/app/components/marty/mcfly_grid_panel.rb +53 -0
- data/app/components/marty/mcfly_grid_panel/client/dup_in_form.js +20 -0
- data/app/models/marty/base_rule.rb +126 -0
- data/app/models/marty/delorean_rule.rb +121 -0
- data/lib/marty/data_importer.rb +0 -1
- data/lib/marty/rule_script_set.rb +176 -0
- data/lib/marty/version.rb +1 -1
- data/lib/tasks/marty_tasks.rake +42 -0
- data/spec/dummy/app/components/gemini/cm_auth_app.rb +18 -0
- data/spec/dummy/app/components/gemini/my_rule_view.rb +63 -0
- data/spec/dummy/app/components/gemini/xyz_rule_view.rb +25 -0
- data/spec/dummy/app/models/gemini/guard_one.rb +5 -0
- data/spec/dummy/app/models/gemini/guard_two.rb +5 -0
- data/spec/dummy/app/models/gemini/my_rule.rb +46 -0
- data/spec/dummy/app/models/gemini/my_rule_type.rb +5 -0
- data/spec/dummy/app/models/gemini/xyz_enum.rb +5 -0
- data/spec/dummy/app/models/gemini/xyz_rule.rb +63 -0
- data/spec/dummy/app/models/gemini/xyz_rule_type.rb +5 -0
- data/spec/dummy/config/locales/en.yml +10 -0
- data/spec/dummy/db/migrate/20171220150101_add_rule_type_enums.rb +14 -0
- data/spec/dummy/db/migrate/20171221095312_create_gemini_my_rules.rb +22 -0
- data/spec/dummy/db/migrate/20171221095359_create_gemini_xyz_rules.rb +21 -0
- data/spec/dummy/db/migrate/20171222150100_add_rule_indices.rb +34 -0
- data/spec/dummy/db/seeds.rb +1 -1
- data/spec/dummy/delorean/base_code.dl +6 -0
- data/spec/dummy/lib/gemini/my_rule_script_set.rb +13 -0
- data/spec/dummy/lib/gemini/xyz_rule_script_set.rb +22 -0
- data/spec/features/rule_spec.rb +265 -0
- data/spec/fixtures/csv/rule/DataGrid.csv +6 -0
- data/spec/fixtures/csv/rule/MyRule.csv +14 -0
- data/spec/fixtures/csv/rule/XyzRule.csv +6 -0
- data/spec/models/rule_spec.rb +322 -0
- data/spec/support/integration_helpers.rb +1 -0
- metadata +29 -2
data/lib/marty/version.rb
CHANGED
data/lib/tasks/marty_tasks.rake
CHANGED
@@ -23,6 +23,48 @@ namespace :marty do
|
|
23
23
|
Marty::Script.load_scripts(load_dir)
|
24
24
|
end
|
25
25
|
|
26
|
+
# currently this is for delorean style rules only. if other types were ever
|
27
|
+
# added (eg some sort of SQL rule like apollo has), that would probably be
|
28
|
+
# a new rake task
|
29
|
+
desc 'generate rule table migration'
|
30
|
+
task :generate_rule_table_migration, [:table] => :environment do |t, args|
|
31
|
+
(puts "Usage: rake marty:generate_rule_table_migration[<table name>]"
|
32
|
+
next) unless args[:table]
|
33
|
+
table = args[:table]
|
34
|
+
filename = Rails.root.join("db/migrate",Time.zone.now.strftime(
|
35
|
+
"%Y%m%d%H%M%S_create_#{table}.rb"))
|
36
|
+
puts "creating #{filename}"
|
37
|
+
File.open(filename, "w") do |f|
|
38
|
+
f.puts <<~EOF
|
39
|
+
class Create#{table.camelize} < McflyMigration
|
40
|
+
include Marty::Migrations
|
41
|
+
def change()
|
42
|
+
create_table :#{table} do |t|
|
43
|
+
t.string :name, null: false
|
44
|
+
# set type enum
|
45
|
+
t.column :rule_type, :enum_name, null: false
|
46
|
+
t.datetime :start_dt, null: false
|
47
|
+
t.datetime :end_dt, null: true
|
48
|
+
t.string :engine, null: true # add engine default if used
|
49
|
+
# add any additional attrs here
|
50
|
+
t.jsonb :simple_guards, null: false, default: {}
|
51
|
+
t.json :computed_guards, null: false, default: {}
|
52
|
+
t.jsonb :grids, null: false, default: {}
|
53
|
+
t.json :results, null: false, default: {}
|
54
|
+
# only needed for delorean type rules
|
55
|
+
t.jsonb :fixed_results, null: false, default: {}
|
56
|
+
end
|
57
|
+
execute("CREATE OR REPLACE FUNCTION to_numrange(val text) "\\
|
58
|
+
"RETURNS numrange AS "\\
|
59
|
+
"$BODY$ select numrange(val); $BODY$ "\\
|
60
|
+
"LANGUAGE SQL IMMUTABLE;")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
EOF
|
64
|
+
end
|
65
|
+
puts "please edit the migration file to customize the rule"
|
66
|
+
end
|
67
|
+
|
26
68
|
desc 'Print out all models and their fields'
|
27
69
|
task print_schema: :environment do
|
28
70
|
Rails.application.eager_load!
|
@@ -11,6 +11,8 @@ class Gemini::CmAuthApp < Marty::MainAuthApp
|
|
11
11
|
icon: icon_hack(:database_key),
|
12
12
|
menu: [
|
13
13
|
:loan_program_view,
|
14
|
+
:my_rule_view,
|
15
|
+
:xyz_rule_view,
|
14
16
|
],
|
15
17
|
}
|
16
18
|
]
|
@@ -21,7 +23,23 @@ class Gemini::CmAuthApp < Marty::MainAuthApp
|
|
21
23
|
a.handler = :netzke_load_component_by_action
|
22
24
|
end
|
23
25
|
|
26
|
+
action :my_rule_view do |a|
|
27
|
+
a.text = a.tooltip = 'My Rules'
|
28
|
+
a.handler = :netzke_load_component_by_action
|
29
|
+
end
|
30
|
+
|
31
|
+
action :xyz_rule_view do |a|
|
32
|
+
a.text = a.tooltip = 'Xyz Rules'
|
33
|
+
a.handler = :netzke_load_component_by_action
|
34
|
+
end
|
35
|
+
|
24
36
|
component :loan_program_view do |c|
|
25
37
|
c.klass = Gemini::LoanProgramView
|
26
38
|
end
|
39
|
+
component :my_rule_view do |c|
|
40
|
+
c.klass = Gemini::MyRuleView
|
41
|
+
end
|
42
|
+
component :xyz_rule_view do |c|
|
43
|
+
c.klass = Gemini::XyzRuleView
|
44
|
+
end
|
27
45
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Gemini::MyRuleView < Marty::DeloreanRuleView
|
2
|
+
has_marty_permissions create: :admin,
|
3
|
+
read: :admin,
|
4
|
+
update: :admin,
|
5
|
+
delete: :admin
|
6
|
+
|
7
|
+
def self.base_fields
|
8
|
+
super + [:other_flag]
|
9
|
+
end
|
10
|
+
def configure(c)
|
11
|
+
super
|
12
|
+
c.title = 'My Rules'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.klass
|
16
|
+
Gemini::MyRule
|
17
|
+
end
|
18
|
+
|
19
|
+
attribute :other_flag do |c|
|
20
|
+
c.width = 75
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_items_grids
|
24
|
+
[
|
25
|
+
self.class.grid_column(:grid1),
|
26
|
+
self.class.grid_column(:grid2),
|
27
|
+
]
|
28
|
+
end
|
29
|
+
def default_form_items
|
30
|
+
[
|
31
|
+
hbox(
|
32
|
+
vbox(*form_items_attrs +
|
33
|
+
form_items_guards +
|
34
|
+
form_items_grids,
|
35
|
+
border: false,
|
36
|
+
width: "40%",
|
37
|
+
),
|
38
|
+
vbox(width: '2%', border: false),
|
39
|
+
vbox(
|
40
|
+
width: '55%', border: false),
|
41
|
+
height: '56%',
|
42
|
+
border: false,
|
43
|
+
),
|
44
|
+
hbox(
|
45
|
+
vbox(*form_items_computed_guards +
|
46
|
+
form_items_results,
|
47
|
+
width: '99%',
|
48
|
+
border: false
|
49
|
+
),
|
50
|
+
height: '35%',
|
51
|
+
border: false
|
52
|
+
)
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
self.init_fields
|
57
|
+
|
58
|
+
attribute :rule_type do |c|
|
59
|
+
c.width = 200
|
60
|
+
enum_column(c, Gemini::MyRuleType)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Gemini::XyzRuleView < Marty::DeloreanRuleView
|
2
|
+
has_marty_permissions create: :admin,
|
3
|
+
read: :admin,
|
4
|
+
update: :admin,
|
5
|
+
delete: :admin
|
6
|
+
def self.klass
|
7
|
+
Gemini::XyzRule
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(c)
|
11
|
+
super
|
12
|
+
c.title = 'Xyz Rules'
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_form_items
|
16
|
+
super
|
17
|
+
end
|
18
|
+
self.init_fields
|
19
|
+
|
20
|
+
attribute :rule_type do |c|
|
21
|
+
c.width = 200
|
22
|
+
enum_column(c, Gemini::XyzRuleType)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Gemini::MyRule < Marty::DeloreanRule
|
2
|
+
self.table_name = 'gemini_my_rules'
|
3
|
+
|
4
|
+
gen_mcfly_lookup :lookup, {
|
5
|
+
name: false,
|
6
|
+
}
|
7
|
+
|
8
|
+
cached_mcfly_lookup :lookup_id, sig: 2 do
|
9
|
+
|pt, group_id|
|
10
|
+
find_by_group_id group_id
|
11
|
+
end
|
12
|
+
|
13
|
+
mcfly_validates_uniqueness_of :name, scope: [:start_dt, :end_dt]
|
14
|
+
|
15
|
+
def self.guard_info
|
16
|
+
super + {"g_array" => { multi: true, type: :string,
|
17
|
+
enum: Gemini::GuardOne,},
|
18
|
+
"g_single" => { type: :string,
|
19
|
+
enum: Gemini::GuardTwo,
|
20
|
+
width: 100},
|
21
|
+
"g_string" => { type: :string,
|
22
|
+
values: ["Hi Mom", "abc", "def", "zzz"],
|
23
|
+
width: 100},
|
24
|
+
"g_bool" => { type: :boolean,
|
25
|
+
width: 100},
|
26
|
+
"g_range" => { type: :range,
|
27
|
+
width: 100},
|
28
|
+
"g_integer" => { type: :integer,
|
29
|
+
width: 100},
|
30
|
+
"g_has_default" => { type: :string,
|
31
|
+
default: "string default"}}
|
32
|
+
end
|
33
|
+
def self.results_cfg_var
|
34
|
+
'RULEOPTS_MYRULE'
|
35
|
+
end
|
36
|
+
|
37
|
+
mcfly_lookup :get_matches, sig: 3 do
|
38
|
+
|pt, attrs, params|
|
39
|
+
get_matches_(pt, attrs, params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def compute(*args)
|
43
|
+
base_compute(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Gemini::XyzRule < Marty::DeloreanRule
|
2
|
+
self.table_name = 'gemini_xyz_rules'
|
3
|
+
|
4
|
+
gen_mcfly_lookup :lookup, {
|
5
|
+
name: false,
|
6
|
+
}
|
7
|
+
|
8
|
+
cached_mcfly_lookup :lookup_id, sig: 2 do
|
9
|
+
|pt, group_id|
|
10
|
+
find_by_group_id group_id
|
11
|
+
end
|
12
|
+
|
13
|
+
mcfly_validates_uniqueness_of :name, scope: [:start_dt, :end_dt]
|
14
|
+
|
15
|
+
def self.results_cfg_var
|
16
|
+
'RULEOPTS_XYZ'
|
17
|
+
end
|
18
|
+
def self.guard_info
|
19
|
+
super + {"flavors" => { multi: true, type: :string,
|
20
|
+
enum: Gemini::XyzEnum,
|
21
|
+
width: 150},
|
22
|
+
"guard_two" => { type: :string,
|
23
|
+
enum: Gemini::GuardTwo,
|
24
|
+
width: 100},
|
25
|
+
"g_date" => { type: :date },
|
26
|
+
"g_datetime" => { type: :datetime },
|
27
|
+
"g_string" => { type: :string,
|
28
|
+
width: 100},
|
29
|
+
"g_bool" => { type: :boolean,
|
30
|
+
width: 100},
|
31
|
+
"g_range1" => { type: :range,
|
32
|
+
width: 100},
|
33
|
+
"g_range2" => { type: :range,
|
34
|
+
width: 100},
|
35
|
+
"g_integer" => { type: :integer,
|
36
|
+
width: 100}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
mcfly_lookup :get_matches, sig: 3 do
|
41
|
+
|pt, attrs, params|
|
42
|
+
get_matches_(pt, attrs, params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def compute(*args)
|
46
|
+
base_compute(*args)
|
47
|
+
end
|
48
|
+
def compute_xyz(pt, xyz_param)
|
49
|
+
# Given a set of parameters, compute the RULE adjustment. Returns
|
50
|
+
# {} if precondition is not met.
|
51
|
+
|
52
|
+
xyz_keys = computed_guards.select{|k,_|k.starts_with?("xyz_")}.keys
|
53
|
+
return {} unless xyz_keys.present?
|
54
|
+
|
55
|
+
eclass = engine && engine.constantize || Marty::RuleScriptSet
|
56
|
+
engine = eclass.new(pt).get_engine(self)
|
57
|
+
res = engine.evaluate("XyzNode", xyz_keys, {"xyz_param"=>xyz_param})
|
58
|
+
|
59
|
+
res.all?
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class AddRuleTypeEnums < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
[Gemini::XyzRuleType, Gemini::MyRuleType, Gemini::GuardOne,
|
4
|
+
Gemini::GuardTwo, Gemini::XyzEnum].each do |cl|
|
5
|
+
values = cl::VALUES
|
6
|
+
str_values = values.map {|v| ActiveRecord::Base.connection.quote v}.
|
7
|
+
join(',')
|
8
|
+
clstr = cl.to_s.sub('Gemini::','').underscore
|
9
|
+
execute <<-SQL
|
10
|
+
CREATE TYPE #{clstr} AS ENUM (#{str_values})
|
11
|
+
SQL
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateGeminiMyRules < McflyMigration
|
2
|
+
include Marty::Migrations
|
3
|
+
def change()
|
4
|
+
create_table :gemini_my_rules do |t|
|
5
|
+
t.string :name, null: false
|
6
|
+
t.column :rule_type, :my_rule_type, null: false
|
7
|
+
t.datetime :start_dt, null: false
|
8
|
+
t.datetime :end_dt, null: true
|
9
|
+
t.string :engine, null: false, default: 'Gemini::MyRuleScriptSet'
|
10
|
+
t.boolean :other_flag
|
11
|
+
t.jsonb :simple_guards, null: false, default: {}
|
12
|
+
t.json :computed_guards, null: false, default: {}
|
13
|
+
t.jsonb :grids, null: false, default: {}
|
14
|
+
t.json :results, null: false, default: {}
|
15
|
+
t.jsonb :fixed_results, null: false, default: {}
|
16
|
+
end
|
17
|
+
execute("CREATE OR REPLACE FUNCTION to_numrange(val text) "\
|
18
|
+
"RETURNS numrange AS "\
|
19
|
+
"$BODY$ select numrange(val); $BODY$ "\
|
20
|
+
"LANGUAGE SQL IMMUTABLE;")
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateGeminiXyzRules < McflyMigration
|
2
|
+
include Marty::Migrations
|
3
|
+
def change()
|
4
|
+
create_table :gemini_xyz_rules do |t|
|
5
|
+
t.string :name, null: false
|
6
|
+
t.column :rule_type, :xyz_rule_type, null: false
|
7
|
+
t.datetime :start_dt, null: false
|
8
|
+
t.datetime :end_dt, null: true
|
9
|
+
t.string :engine, null: false, default: 'Gemini::XyzRuleScriptSet'
|
10
|
+
t.jsonb :simple_guards, null: false, default: {}
|
11
|
+
t.json :computed_guards, null: false, default: {}
|
12
|
+
t.jsonb :grids, null: false, default: {}
|
13
|
+
t.json :results, null: false, default: {}
|
14
|
+
t.jsonb :fixed_results, null: false, default: {}
|
15
|
+
end
|
16
|
+
execute("CREATE OR REPLACE FUNCTION to_numrange(val text) "\
|
17
|
+
"RETURNS numrange AS "\
|
18
|
+
"$BODY$ select numrange(val); $BODY$ "\
|
19
|
+
"LANGUAGE SQL IMMUTABLE;")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class AddRuleIndices < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
['gemini_my_rules', 'g_array', :array,
|
4
|
+
'gemini_my_rules', 'g_single', :scalar,
|
5
|
+
'gemini_my_rules', 'g_string', :scalar,
|
6
|
+
'gemini_my_rules', 'g_bool', :scalar,
|
7
|
+
'gemini_my_rules', 'g_integer', :scalar,
|
8
|
+
'gemini_my_rules', 'g_range', :range,
|
9
|
+
'gemini_xyz_rules', 'flavors', :array,
|
10
|
+
'gemini_xyz_rules', 'guard_two', :scalar,
|
11
|
+
'gemini_xyz_rules', 'g_date', :scalar,
|
12
|
+
'gemini_xyz_rules', 'g_datetime', :scalar,
|
13
|
+
'gemini_xyz_rules', 'g_bool', :scalar,
|
14
|
+
'gemini_xyz_rules', 'g_integer', :scalar,
|
15
|
+
'gemini_xyz_rules', 'g_range1', :range,
|
16
|
+
'gemini_xyz_rules', 'g_range2', :range,
|
17
|
+
].in_groups_of(3).each do |table, field, type|
|
18
|
+
index = { array: "GIN",
|
19
|
+
scalar: "BTREE",
|
20
|
+
range: "GIST" }[type]
|
21
|
+
case type
|
22
|
+
when :array, :scalar
|
23
|
+
col="(simple_guards->'#{field}')"
|
24
|
+
when :range
|
25
|
+
col="(to_numrange(simple_guards->>'#{field}'))"
|
26
|
+
end
|
27
|
+
sql =<<-SQL
|
28
|
+
CREATE INDEX idx_#{table}_#{field} ON #{table} USING #{index}
|
29
|
+
(#{col});
|
30
|
+
SQL
|
31
|
+
execute sql
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/dummy/db/seeds.rb
CHANGED