marty 1.2.0 → 1.2.1
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.
- 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
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
                // copied from basepack grid's onEditInForm
         | 
| 3 | 
            +
                netzkeOnDupInForm: function(){
         | 
| 4 | 
            +
                    var selModel = this.getSelectionModel();
         | 
| 5 | 
            +
                    var recordId = selModel.selected.first().getId();
         | 
| 6 | 
            +
                    this.netzkeLoadComponent("edit_window", {
         | 
| 7 | 
            +
                        title: "Duplicate in Form",
         | 
| 8 | 
            +
                        serverConfig: {record_id: recordId},
         | 
| 9 | 
            +
                        callback: function(w){
         | 
| 10 | 
            +
                            w.show();
         | 
| 11 | 
            +
                            var form = w.items.first();
         | 
| 12 | 
            +
                            form.baseParams = {dup: true};
         | 
| 13 | 
            +
                            w.on('close', function(){
         | 
| 14 | 
            +
                                if (w.closeRes === "ok") {
         | 
| 15 | 
            +
                                    this.store.load();
         | 
| 16 | 
            +
                                }
         | 
| 17 | 
            +
                            }, this);
         | 
| 18 | 
            +
                        }, scope: this});
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
            }
         | 
| @@ -0,0 +1,126 @@ | |
| 1 | 
            +
            class Marty::BaseRule < Marty::Base
         | 
| 2 | 
            +
              self.abstract_class = true
         | 
| 3 | 
            +
              has_mcfly
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def self.guard_info
         | 
| 6 | 
            +
                {}
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def chkrange(v)
         | 
| 10 | 
            +
                v.match(/\A(\[|\()([0-9\.-]*),([0-9\.-]*)(\]|\))\z/)
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              def gettypes(v)
         | 
| 13 | 
            +
                types = []
         | 
| 14 | 
            +
                types << :string if v.is_a?(String)
         | 
| 15 | 
            +
                types += [:int, :integer] if Integer(v) rescue nil
         | 
| 16 | 
            +
                types << :float if Float(v) rescue nil
         | 
| 17 | 
            +
                types << :date if Date.parse(v) rescue nil
         | 
| 18 | 
            +
                types << :datetime if DateTime.parse(v) rescue nil
         | 
| 19 | 
            +
                types << :range if chkrange(v) rescue nil
         | 
| 20 | 
            +
                types << :boolean if [true, false].include?(v)
         | 
| 21 | 
            +
                types
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
              def check(name, h)
         | 
| 24 | 
            +
                multi, type, enum, values, req = h.values_at(:multi, :type, :enum, :values,
         | 
| 25 | 
            +
                                                             :required)
         | 
| 26 | 
            +
                ns = "'#{name}'"
         | 
| 27 | 
            +
                expmulti = multi ? 'multi' : 'single'
         | 
| 28 | 
            +
                errtype = :guards
         | 
| 29 | 
            +
                v = simple_guards[name]
         | 
| 30 | 
            +
                type ||= :string
         | 
| 31 | 
            +
                return errors[errtype] << "- Required field #{ns} is missing" if
         | 
| 32 | 
            +
                  v.blank? && req
         | 
| 33 | 
            +
                return if v.blank?
         | 
| 34 | 
            +
                gotmulti = v.is_a?(Array) ? 'multi' : 'single'
         | 
| 35 | 
            +
                return errors[errtype] << "- Wrong arity for #{ns} (expected #{expmulti} "\
         | 
| 36 | 
            +
                                          "got #{gotmulti})" if expmulti != gotmulti
         | 
| 37 | 
            +
                vs = [v].flatten.to_set
         | 
| 38 | 
            +
                vs.each do |vv|
         | 
| 39 | 
            +
                  return errors[errtype] << "- Wrong type for #{ns}" unless
         | 
| 40 | 
            +
                    gettypes(vv).member?(type)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                return unless enum || values
         | 
| 43 | 
            +
                vals = enum && enum::VALUES || values.to_set
         | 
| 44 | 
            +
                bad = (vs - vals)
         | 
| 45 | 
            +
                p = bad.count > 1 ? 's' : ''
         | 
| 46 | 
            +
                return errors[errtype] <<
         | 
| 47 | 
            +
                       %Q(- Bad value#{p} '#{bad.to_a.join("', '")}' for #{ns}) if bad.present?
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
              def validate
         | 
| 50 | 
            +
                self.class.guard_info.each { |name, h| check(name, h) }
         | 
| 51 | 
            +
                grids.each do |vn, gn|
         | 
| 52 | 
            +
                  return errors[:grids] << "- Bad grid name '#{gn}' for '#{vn}'" unless
         | 
| 53 | 
            +
                    gn.blank? || Marty::DataGrid.lookup('infinity', gn)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                cg_err = computed_guards.delete("~~ERROR~~")
         | 
| 56 | 
            +
                errors[:computed] <<
         | 
| 57 | 
            +
                  "- Error in rule '#{name}' field 'computed_guards': #{cg_err.capitalize}" if cg_err
         | 
| 58 | 
            +
                res_err = results.delete("~~ERROR~~")
         | 
| 59 | 
            +
                errors[:computed] <<
         | 
| 60 | 
            +
                  "- Error in rule '#{name}' field 'results': #{res_err.capitalize}" if res_err
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                same_name_diff_guards = self.class.
         | 
| 63 | 
            +
                    where(obsoleted_dt: 'infinity', name: self.name).
         | 
| 64 | 
            +
                    # id is nil on new rules
         | 
| 65 | 
            +
                    where.not(id: self.id).
         | 
| 66 | 
            +
                    where("simple_guards != '#{self.simple_guards.to_json}'")
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                errors[:base] =
         | 
| 69 | 
            +
                  "Can't have rule with same name and different type/guards" +
         | 
| 70 | 
            +
                  " - #{self.name}" if same_name_diff_guards.exists?
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              validates_presence_of :name
         | 
| 75 | 
            +
              validate :validate
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              before_validation(on: [:create, :update]) do
         | 
| 78 | 
            +
                self.simple_guards     ||= {}
         | 
| 79 | 
            +
                self.computed_guards   ||= {}
         | 
| 80 | 
            +
                self.grids             ||= {}
         | 
| 81 | 
            +
                self.results           ||= {}
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              before_create do
         | 
| 85 | 
            +
                self.class.guard_info.each do |k,v|
         | 
| 86 | 
            +
                  next if v[:default].blank? || self.simple_guards.include?(k)
         | 
| 87 | 
            +
                  self.simple_guards[k] = v[:default]
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              def self.get_subq(field, subfield, multi, type, vraw)
         | 
| 92 | 
            +
                arrow = multi || ![:range, :string, :date, :datetime].include?(type) ?
         | 
| 93 | 
            +
                          "->" : "->>"
         | 
| 94 | 
            +
                op = multi || type == :range ? "@>" : "="
         | 
| 95 | 
            +
                value0 = [:string, :date, :datetime].include?(type) ?
         | 
| 96 | 
            +
                           ActiveRecord::Base.connection.quote(vraw) :
         | 
| 97 | 
            +
                           type == :range ? vraw.to_f :
         | 
| 98 | 
            +
                             "'#{vraw}'::jsonb"
         | 
| 99 | 
            +
                value = multi ? %Q('["%s"]') % value0[1..-2] : value0
         | 
| 100 | 
            +
                fieldcast = type == :range ? "::numrange" : ''
         | 
| 101 | 
            +
                "(#{field}#{arrow}'#{subfield}')#{fieldcast} #{op} #{value}"
         | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
              def self.get_matches_(pt, attrs, params)
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                q = select("DISTINCT ON (name) *").where(attrs)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                params.each do |k, vraw|
         | 
| 109 | 
            +
                  h = guard_info
         | 
| 110 | 
            +
                  use_k = (h[k] && k) ||
         | 
| 111 | 
            +
                          (h[k+"_array"] && k+"_array") ||
         | 
| 112 | 
            +
                          (h[k+"_range"] && k+"_range")
         | 
| 113 | 
            +
                  next unless use_k
         | 
| 114 | 
            +
                  multi, type = h[use_k].values_at(:multi, :type)
         | 
| 115 | 
            +
                  filts = [vraw].flatten.map do |v|
         | 
| 116 | 
            +
                    qstr = get_subq('simple_guards', use_k, multi, type, v)
         | 
| 117 | 
            +
                  end.join(" OR ")
         | 
| 118 | 
            +
                  isn = "simple_guards->'#{use_k}' IS NULL OR"
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  q = q.where("(#{isn} #{filts})")
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
               # print q.to_sql
         | 
| 123 | 
            +
                q.order(:name)
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            end
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            class Marty::DeloreanRule < Marty::BaseRule
         | 
| 2 | 
            +
              self.abstract_class = true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              validates_presence_of :rule_type, :start_dt
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def validate
         | 
| 7 | 
            +
                super
         | 
| 8 | 
            +
                if self.class.where(obsoleted_dt: 'infinity', name: name).
         | 
| 9 | 
            +
                    where.not(id: id).
         | 
| 10 | 
            +
                    where("(start_dt, coalesce(end_dt, 'infinity')) OVERLAPS (?, ?)",
         | 
| 11 | 
            +
                          start_dt, end_dt || 'infinity').exists?
         | 
| 12 | 
            +
                  return errors[:base] <<
         | 
| 13 | 
            +
                         "Can't have rule with same name and overlapping start/end dates"\
         | 
| 14 | 
            +
                         " - #{name}"
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                return errors[:base] = "Start date must be before end date" if
         | 
| 18 | 
            +
                  start_dt && end_dt && start_dt >= end_dt
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                if computed_guards.present? || results.present?
         | 
| 21 | 
            +
                  begin
         | 
| 22 | 
            +
                    eclass = engine && engine.constantize || Marty::RuleScriptSet
         | 
| 23 | 
            +
                    eng = eclass.new('infinity').get_engine(self)
         | 
| 24 | 
            +
                  rescue => e
         | 
| 25 | 
            +
                    return errors[:computed] = "- " + e.message
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def self.find_fixed(results)
         | 
| 31 | 
            +
                results.each_with_object({}) do |(k, v), h|
         | 
| 32 | 
            +
                  v_wo_comment = /\A([^#]+)/.match(v)[1] if v.include?("#")
         | 
| 33 | 
            +
                  # if v contains a #, try cut it there and attempt parse that way first
         | 
| 34 | 
            +
                  jp = (v_wo_comment && JSON.parse("[#{v_wo_comment}]") rescue nil) ||
         | 
| 35 | 
            +
                       (JSON.parse("[#{v}]") rescue nil)
         | 
| 36 | 
            +
                  next h[k] = jp[0] if jp
         | 
| 37 | 
            +
                  # json doesn't like single quotes, so handle those manually
         | 
| 38 | 
            +
                  m = %r{\A'(.*)'\z}.match(v)
         | 
| 39 | 
            +
                  next unless m
         | 
| 40 | 
            +
                  h[k] = m[1]
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              before_validation(on: [:create, :update]) do
         | 
| 45 | 
            +
                # identify result values that are fixed, stash them (removing quotes)
         | 
| 46 | 
            +
                self.fixed_results = self.class.find_fixed(self.results)
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def self.results_cfg_var
         | 
| 50 | 
            +
                "NOT DEFINED"
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def compg_keys
         | 
| 54 | 
            +
                computed_guards.keys
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def compres_keys
         | 
| 58 | 
            +
                defkeys = (Marty::Config[self.class.results_cfg_var] || {}).keys +
         | 
| 59 | 
            +
                          ["adjustment", "breakeven"]
         | 
| 60 | 
            +
                results.keys.select{|k| defkeys.include?(k)} + grid_keys
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
              def grid_keys
         | 
| 63 | 
            +
                  grids.keys.map{|k|k.ends_with?("_grid") ? k : k + "_grid"}
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
              def base_compute(params, dgparams=params)
         | 
| 66 | 
            +
                eclass = engine && engine.constantize || Marty::RuleScriptSet
         | 
| 67 | 
            +
                engine = eclass.new(params["pt"]).get_engine(self) if
         | 
| 68 | 
            +
                  computed_guards.present? || results.present?
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                if computed_guards.present?
         | 
| 71 | 
            +
                  begin
         | 
| 72 | 
            +
                    res = engine.evaluate(eclass.node_name,
         | 
| 73 | 
            +
                                          compg_keys,
         | 
| 74 | 
            +
                                          params.clone)
         | 
| 75 | 
            +
                  rescue => e
         | 
| 76 | 
            +
                    raise e, "Error (guard) in rule '#{id}:#{name}': #{e}", e.backtrace
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  return Hash[compg_keys.zip(res).select{|k,v| !v}] unless res.all?
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
                grids_computed = false
         | 
| 81 | 
            +
                grid_results = {}
         | 
| 82 | 
            +
                if (results.keys - fixed_results.keys).present?
         | 
| 83 | 
            +
                    begin
         | 
| 84 | 
            +
                      eval_result = engine.evaluate(
         | 
| 85 | 
            +
                        eclass.node_name,
         | 
| 86 | 
            +
                        compres_keys,
         | 
| 87 | 
            +
                        params + {
         | 
| 88 | 
            +
                          "dgparams__" => dgparams,
         | 
| 89 | 
            +
                        })
         | 
| 90 | 
            +
                      grids_computed = true
         | 
| 91 | 
            +
                    rescue => e
         | 
| 92 | 
            +
                      raise e, "Error (results) in rule '#{id}:#{name}': #{e}", e.backtrace
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                    result = Hash[compres_keys.zip(eval_result)]
         | 
| 95 | 
            +
                elsif fixed_results.keys.sort == results.keys.sort
         | 
| 96 | 
            +
                  result = fixed_results
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
                if grids.present? && !grids_computed
         | 
| 99 | 
            +
                  pt = params['pt']
         | 
| 100 | 
            +
                  gres = {}
         | 
| 101 | 
            +
                  grid_results = grids.each_with_object({}) do |(gvar, gname), h|
         | 
| 102 | 
            +
                    usename = gvar.ends_with?("_grid") ? gvar : gvar + "_grid"
         | 
| 103 | 
            +
                    next h[usename] = gres[gname] if gres[gname]
         | 
| 104 | 
            +
                    dg = Marty::DataGrid.lookup(pt,gname)
         | 
| 105 | 
            +
                    dgr = dg && dg.lookup_grid_distinct_entry(pt, dgparams)
         | 
| 106 | 
            +
                    h[usename] = gres[gname] = dgr["result"] if dgr
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
                result + grid_results
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              def self.get_matches_(pt, attrs, params)
         | 
| 113 | 
            +
                q = super(pt, attrs.except("rule_dt"), params)
         | 
| 114 | 
            +
                rule_dt = attrs["rule_dt"]
         | 
| 115 | 
            +
                q=q.where("start_dt <= ?", rule_dt).
         | 
| 116 | 
            +
                   where("end_dt >= ? OR end_dt IS NULL", rule_dt) if rule_dt
         | 
| 117 | 
            +
                #puts q.to_sql
         | 
| 118 | 
            +
                q
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            end
         | 
    
        data/lib/marty/data_importer.rb
    CHANGED
    
    
| @@ -0,0 +1,176 @@ | |
| 1 | 
            +
            class Marty::RuleScriptSet < Delorean::AbstractContainer
         | 
| 2 | 
            +
              attr_reader :sset, :pt
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              def self.clear_cache
         | 
| 5 | 
            +
                @@engines, @@dengines, @@dengines_dt = {}, {}, nil
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              clear_cache
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def self.node_name
         | 
| 11 | 
            +
                "Node"
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              def self.body_start
         | 
| 14 | 
            +
                "#{node_name}:\n"
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
              def self.body_lines
         | 
| 17 | 
            +
                self.body_start.count("\n")
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def initialize(pt)
         | 
| 21 | 
            +
                @pt = Mcfly.normalize_infinity(pt)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                # if pt is Infinity, we get a DEV Tag
         | 
| 24 | 
            +
                tag = Marty::Tag.cached_find_match(pt)
         | 
| 25 | 
            +
                @sset = Marty::ScriptSet.new(tag)
         | 
| 26 | 
            +
                super()
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def parse_check(sname, body)
         | 
| 30 | 
            +
                sset.parse_check(sname, body)
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def write_attr(k, v)
         | 
| 34 | 
            +
                k + (v == :parameter ? " =?" : " = #{v}")
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def paramify_h(h)
         | 
| 38 | 
            +
                "{" + h.keys.reject{|k|k.ends_with?("__")}.
         | 
| 39 | 
            +
                                     map {|k| %Q("#{k}": #{k}) }.join(",\n") + "}"
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def expand_grid_code(h, dgid, dgname, cache, extra_params)
         | 
| 43 | 
            +
                final_name = dgid.ends_with?("_grid") ?  dgid : dgid + "_grid"
         | 
| 44 | 
            +
                if cache[dgname]
         | 
| 45 | 
            +
                  h[final_name] = "#{cache[dgname]}"
         | 
| 46 | 
            +
                else
         | 
| 47 | 
            +
                  h["#{dgid}_dg__"] = "Marty::DataGrid.lookup(pt,'#{dgname}')"
         | 
| 48 | 
            +
                  h["#{dgid}_dgp__"] = "dgparams__ + \n" + self.class.indent(paramify_h(h))
         | 
| 49 | 
            +
                  lgde = "lookup_grid_distinct_entry"
         | 
| 50 | 
            +
                  h["#{dgid}_h__"] = "#{dgid}_dg__.#{lgde}(pt,#{dgid}_dgp__)"
         | 
| 51 | 
            +
                  h[final_name] = "#{dgid}_h__ && #{dgid}_h__.result"
         | 
| 52 | 
            +
                  cache[dgname] = final_name
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              def write_code(attrs)
         | 
| 57 | 
            +
                return '' if attrs.blank?
         | 
| 58 | 
            +
                newh = attrs.each_with_object({}) do |(k, v), h|
         | 
| 59 | 
            +
                  if k.ends_with?("_grid")
         | 
| 60 | 
            +
                    expand_grid_code(h, k, v, {}, h)
         | 
| 61 | 
            +
                  else
         | 
| 62 | 
            +
                    h[k] = v
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
                newh.map { |k, v| write_attr(k, v) }.join("\n") + "\n"
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              def grid_code(rule)
         | 
| 69 | 
            +
                dgcache = {}
         | 
| 70 | 
            +
                h = {}
         | 
| 71 | 
            +
                rule.grids.each do |k, v|
         | 
| 72 | 
            +
                  expand_grid_code(h, k, v, dgcache, {})
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
                h.map { |k, v| write_attr(k, v) }.join("\n") + "\n"
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              def guard_code(rule)
         | 
| 78 | 
            +
                write_code(rule.computed_guards)
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              def result_code(rule)
         | 
| 82 | 
            +
                write_code(rule.results)
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              def grid_init(rule)
         | 
| 86 | 
            +
                if rule.grids.present? || rule.results.keys.any?{|k|k.ends_with?("_grid")}
         | 
| 87 | 
            +
                  write_code({ "pt" => :parameter,
         | 
| 88 | 
            +
                               "dgparams__" => :parameter,
         | 
| 89 | 
            +
                             })
         | 
| 90 | 
            +
                else
         | 
| 91 | 
            +
                  ''
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
              def get_code(rule)
         | 
| 95 | 
            +
                grid_i = grid_init(rule)
         | 
| 96 | 
            +
                grid_c = grid_code(rule)
         | 
| 97 | 
            +
                result_c = result_code(rule)
         | 
| 98 | 
            +
                guard_c = guard_code(rule)
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                code = self.class.body_start + self.class.indent(grid_i +
         | 
| 101 | 
            +
                                                                 guard_c +
         | 
| 102 | 
            +
                                                                 grid_c +
         | 
| 103 | 
            +
                                                                 result_c)
         | 
| 104 | 
            +
                #puts '='*40
         | 
| 105 | 
            +
                #puts rule.name
         | 
| 106 | 
            +
                #puts '-'
         | 
| 107 | 
            +
                #puts code
         | 
| 108 | 
            +
                #puts '-'*10
         | 
| 109 | 
            +
                code
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              def code_section_counts(rule)
         | 
| 113 | 
            +
                errs = {}
         | 
| 114 | 
            +
                errs[:grid_params] = grid_init(rule).count("\n")
         | 
| 115 | 
            +
                errs[:computed_guards] = guard_code(rule).count("\n")
         | 
| 116 | 
            +
                errs[:grids] = grid_code(rule).count("\n")
         | 
| 117 | 
            +
                errs[:results] = result_code(rule).count("\n")
         | 
| 118 | 
            +
                errs
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
              def get_parse_error_field(rule, exc)
         | 
| 121 | 
            +
                line = exc.line ? exc.line - self.class.body_lines : 0
         | 
| 122 | 
            +
                errs = code_section_counts(rule)
         | 
| 123 | 
            +
                line_count = 0
         | 
| 124 | 
            +
                errs.each do |k,v|
         | 
| 125 | 
            +
                  line_count += v
         | 
| 126 | 
            +
                  return k if line <= line_count
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
                errs.keys.last
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
              def get_engine(rule)
         | 
| 132 | 
            +
                begin
         | 
| 133 | 
            +
                  # if rule is a str => importing a regular Script (i.e. not rule)
         | 
| 134 | 
            +
                  return sset.get_engine(rule) if rule.is_a? String
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  # on create rule doesn't have an id => don't cache
         | 
| 137 | 
            +
                  return sset.parse_check("New RULE #{rule.name}", get_code(rule)) unless
         | 
| 138 | 
            +
                    rule.id
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  rule_pfx = rule.class.name.demodulize
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  # unique name for specific version of rule
         | 
| 143 | 
            +
                  sname = "#{rule_pfx}_#{rule.group_id}_#{rule.created_dt.to_f}"
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  # is it a dev posting?
         | 
| 146 | 
            +
                  if Mcfly.is_infinity(pt)
         | 
| 147 | 
            +
                    max_dt = rule.class.
         | 
| 148 | 
            +
                             order("created_dt DESC").limit(1).pluck(:created_dt).first
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                    @@dengines_dt ||= max_dt
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    # reset dengine cache if an rule has changed
         | 
| 153 | 
            +
                    @@dengines = {} if max_dt > @@dengines_dt
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    engine = @@dengines[sname]
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    return engine if engine
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    @@dengines[sname] = sset.parse_check(sname, get_code(rule))
         | 
| 160 | 
            +
                  else
         | 
| 161 | 
            +
                    engine = @@engines[[pt, sname]]
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    return engine if engine
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    @@engines[[pt, sname]] = sset.parse_check(sname, get_code(rule))
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
                rescue Delorean::ParseError => e
         | 
| 168 | 
            +
                  f = get_parse_error_field(rule, e)
         | 
| 169 | 
            +
                  raise "Error in rule '#{rule.name}' field '#{f}': #{e}"
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
              end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
              def self.indent(s)
         | 
| 174 | 
            +
                s.gsub(/^/, ' '*4)
         | 
| 175 | 
            +
              end
         | 
| 176 | 
            +
            end
         |