Class: Marty::BaseRuleView

Inherits:
McflyGridPanel show all
Includes:
Extras::Layout
Defined in:
app/components/marty/base_rule_view.rb

Direct Known Subclasses

DeloreanRuleView

Defined Under Namespace

Classes: DupKeyError

Constant Summary

Constants included from Extras::Layout

Extras::Layout::BOOL_MAP, Extras::Layout::MAP_BOOL

Constants included from Permissions

Permissions::NETZKE_ENDPOINTS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extras::Layout

#bool_getter, #bool_setter, #dispfield, #enum_array, #enum_column, #enum_setter, #fieldset, #get_sorter, #hbox, #hspacer, #jsonb_field, #nullable_bool_column, #range_column, #range_field, #range_getter, #range_setter, #textarea_field, #tooltip, #vbox, #vspacer

Methods inherited from McflyGridPanel

#augment_attribute_config, #get_records

Methods inherited from Grid

#child_components, #class_can?, #configure_form_window, #get_json_sorter, #has_search_action?, #initialize, #linked_components

Methods included from Permissions

#can_call_endpoint?, #can_perform_action?, #can_perform_actions, #current_user_roles, extended, #has_any_perm?, #has_marty_permissions, #has_perm?

Constructor Details

This class inherits a constructor from Marty::Grid

Class Method Details

.base_fieldsObject



12
13
14
# File 'app/components/marty/base_rule_view.rb', line 12

def self.base_fields
  [:name]
end

.computed_fieldsObject



16
17
18
# File 'app/components/marty/base_rule_view.rb', line 16

def self.computed_fields
  [:computed_guards, :grids, :results]
end

.field_maker(namestr, h, meth) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'app/components/marty/base_rule_view.rb', line 334

def self.field_maker(namestr, h, meth)
  name = namestr.to_sym
  nullbool = h[:type] == :boolean && (h[:null] == true || !h.include?(:null))
  attribute name do |c|
    c.width = h[:width] || 150
    if h[:type] == :datetime
      c.format = 'Y-m-d H:i'
    elsif h[:type] == :date
      c.format = 'Y-m-d'
    elsif nullbool
        c.type = :string
        enum_column(c, ['True', 'False'])
    else
      c.type = h[:type] || :string
    end
    c.label = h[:label] if h[:label]
    if h[:enum] || (h[:type] == :string && h[:values].present?)
      vals = h[:enum] || h[:values]
      if h[:multi]
        enum_array(c, vals)
      else
        enum_column(c, vals)
      end
    end
    # for some unexplained reason the getter/setter need the full
    # class qualification
    if h[:type] != :range
      c.getter = Marty::BaseRuleView.jsonb_field_getter(meth, namestr, nullbool)
      c.setter = Marty::BaseRuleView.jsonb_field_setter(meth, namestr,
                                                        h[:type] == :boolean)
      c.filter_with = lambda do |rel, value, _op|
        v = ActiveRecord::Base.connection.quote(value)[1..-2]
        rel.where("#{meth}->>'#{namestr}' like '%#{v}%'")
      end
    else
      c.getter = range_getter(namestr, meth)
      c.setter = range_setter(namestr, meth)

      c.filterable = false
    end

    c.column_config = {
      renderer: 'simpleGuardColumnRenderer'
    }
    c.sorting_scope = get_json_sorter(meth, namestr)
  end

  # Checkbox and hidden column for "NOT" option for simpleguards
  attribute "#{name}_not" do |c|
    c.width = 30
    c.label = 'Not'
    c.type = :boolean
    c.filterable = false
    c.column_config = { hidden: true }

    c.field_config = {
      label_align: 'right',
      max_width: 100,
      label_pad: 2,
      label_width: 30
    }
    c.getter = lambda do |record|
      record.simple_guards_options.dig(name.to_s, 'not')
    end

    c.setter = lambda do |record, value|
      options = record.simple_guards_options.fetch(name.to_s, {})
      new_options = options.merge('not' => value)
      record.simple_guards_options = record.simple_guards_options.merge(
        name.to_s => new_options
      )
    end
  end
end

.grid_column(c, label = nil) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'app/components/marty/base_rule_view.rb', line 208

def self.grid_column(c, label = nil)
  editor_config = {
    trigger_action: :all,
    xtype:          :combo,
    store:          Marty::DataGrid.where(obsoleted_dt: 'infinity').
      pluck(:name) + ['---'],
    forceSelection: true,
  }
  {
    name: label || c,
    width: 200,
    column_config: { editor: editor_config },
    field_config:  editor_config,
    type:          :string,
    getter: jsonb_field_getter(:grids, c.to_s),
    setter: jsonb_field_setter(:grids, c.to_s),
    #      getter: lambda { |r| r.grids[c.to_s] },
    #      setter: lambda { |r, v| r.grids[c.to_s] = v },
  }
end

.hash_to_ruletext(h) ⇒ Object



134
135
136
137
138
# File 'app/components/marty/base_rule_view.rb', line 134

def self.hash_to_ruletext(h)
  h.each_with_object('') do |(k, v), out|
    out << k + ' = ' + v + "\n"
  end
end

.init_fieldsObject



419
420
421
422
423
# File 'app/components/marty/base_rule_view.rb', line 419

def self.init_fields
  klass.guard_info.each do |namestr, h|
    field_maker(namestr, h, :simple_guards)
  end
end

.jsonb_field_getter(j, c, nullbool = nil) ⇒ Object



166
167
168
169
170
171
172
173
# File 'app/components/marty/base_rule_view.rb', line 166

def self.jsonb_field_getter(j, c, nullbool = nil)
  lambda do |r|
    rv = r.send(j)[c]
    v = nullbool ? (rv == true ? 'True' :
                      rv == false ? 'False' : rv) : rv
    v || ''
  end
end

.jsonb_field_setter(j, c, bool = nil) ⇒ Object



175
176
177
178
179
180
# File 'app/components/marty/base_rule_view.rb', line 175

def self.jsonb_field_setter(j, c, bool = nil)
  lambda do |r, rv|
    v = bool ? rv.to_s.downcase == 'true' : rv
    rv == '' || rv == '---' ? r.send(j).delete(c) : r.send(j)[c] = v
  end
end

.klassObject



4
5
6
# File 'app/components/marty/base_rule_view.rb', line 4

def self.klass
  Marty::BaseRule
end

.ruletext_to_hash(s) ⇒ Object

FSM to parse rule text into json



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'app/components/marty/base_rule_view.rb', line 64

def self.ruletext_to_hash(s)
  # states are
  #  :start   - before any attr is defined
  #  :in_attr - defining an attr
  #  :end     - end of input
  state = :start
  result = {}
  cur_attr = nil
  idx = 0
  input = s.lines

  # events are
  #  :attr    - starting with <identifier>\s*=
  #  :normal  - line not starting with ident =
  #  :end     - no more lines
  # get_event returns [event, data]
  get_event = lambda {
    line = input.try(&:shift)
    next [:end] unless line

    line.chomp!
    idx += 1
    m = /\A\s*([a-z][a-z0-9_]*)\s* = (.*)\z/.match(line)
    next [:attr, m[1..-1]] if m

    [:normal, line]
  }

  # start a new attribute
  # data is [ attr_name, everything after = ]
  new_attr = lambda { |data|
    cur_attr = data.shift
    raise DupKeyError.new(cur_attr, idx) if result[cur_attr]

    result[cur_attr] = data[0]
  }

  begin
    while state != :end
      event, extra = get_event.call
      case state
      when :start
        case event
        when :attr
          new_attr.call(extra)
          state = :in_attr
        when :normal
          raise
        when :end
          state = :end
        end
      when :in_attr
        case event
        when :attr
          new_attr.call(extra)
        when :normal
          result[cur_attr] += "\n" + extra
        when :end
          state = :end
        end
      end
    end
  rescue DupKeyError => e
    raise
  rescue StandardError => e
    raise "syntax error on line #{idx}"
  end
  result
end

Instance Method Details

#configure(c) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
# File 'app/components/marty/base_rule_view.rb', line 20

def configure(c)
  super
  c.model = self.class.klass
  c.title = I18n.t('rule')
  c.attributes = self.class.base_fields +
                 guard_info_attributes + self.class.computed_fields
  c.store_config.merge!(sorters: [{ property: :name, direction: 'ASC' }])
  c.editing      = :in_form
  c.paging       = :pagination
  c.multi_select = false
end

#default_bbarObject



44
45
46
# File 'app/components/marty/base_rule_view.rb', line 44

def default_bbar
  super + [:dup_in_form]
end

#default_context_menuObject



48
49
50
# File 'app/components/marty/base_rule_view.rb', line 48

def default_context_menu
  []
end

#default_form_itemsObject



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'app/components/marty/base_rule_view.rb', line 290

def default_form_items
  [
    hbox(
      vbox(
        hbox(
          vbox(
            *form_items_attrs,
            width: '100%',
            border: false
          ),
          width: '100%',
          border: false
        ),
        hbox(
          vbox(
            *default_form_items_guards,
            width: '100%',
            border: false
          ),
          width: '100%',
          border: false
        ),
        width: '40%',
        border: false
      ),
      vbox(width: '2%', border: false),
      vbox(
        width: '5%', border: false),
      height: '40%',
      border: false,
    ),
    hbox(
      vbox(*form_items_computed_guards +
           form_items_grids +
           form_items_results,
           width: '99%',
           border: false
          ),
      height: '40%',
     border: false
    )
  ]
end

#default_form_items_guardsObject



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'app/components/marty/base_rule_view.rb', line 268

def default_form_items_guards
  with_not_fields = form_items_guards.any? { |h| h.key?(:not_field) }

  guards = form_items_guards.map do |h|
    if with_not_fields
      hbox(
        vbox(h.fetch(:field), width: '78%', border: false),
        vbox(width: '2%', border: false),
        vbox(h.fetch(:not_field, nil), width: '20%', border: false),
        width: '100%',
        border: false
      )
    else
      hbox(
        vbox(h.fetch(:field), width: '100%', border: false),
        width: '100%',
        border: false
      )
    end
  end
end

#form_items_attrsObject



229
230
231
# File 'app/components/marty/base_rule_view.rb', line 229

def form_items_attrs
  self.class.base_fields
end

#form_items_computed_guardsObject



248
249
250
251
252
253
# File 'app/components/marty/base_rule_view.rb', line 248

def form_items_computed_guards
  [jsonb_field(:computed_guards,
               getter: jsonb_simple_getter(:computed_guards),
               setter: jsonb_simple_setter(:computed_guards),
               height: 100)]
end

#form_items_gridsObject



241
242
243
244
245
246
# File 'app/components/marty/base_rule_view.rb', line 241

def form_items_grids
  [jsonb_field(:grids,
               getter: jsonb_simple_getter(:grids),
               setter: jsonb_simple_setter(:grids),
               height: 75)]
end

#form_items_guardsObject



233
234
235
236
237
238
239
# File 'app/components/marty/base_rule_view.rb', line 233

def form_items_guards
  klass.guard_info.reject { |_, h| h[:hidden] }.map do |field, opts|
    next { field: field.to_sym } unless opts.fetch(:allow_not, true)

    { field: field.to_sym, not_field: "#{field.to_sym}_not".to_sym }
  end
end

#form_items_resultsObject



255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/components/marty/base_rule_view.rb', line 255

def form_items_results
  [
    jsonb_field(
      :results,
      getter: jsonb_simple_getter(:results),
      setter: jsonb_simple_setter(:results),
      min_height: 150,
      height: nil, # must be nil to allow the field to resize automatically
      grow: true,
    )
  ]
end

#guard_info_attributesObject



32
33
34
35
36
37
38
39
40
41
42
# File 'app/components/marty/base_rule_view.rb', line 32

def guard_info_attributes
  res = klass.guard_info.
    sort_by { |_, h| h[:order] || 0 }.
    reject { |_, h| h[:hidden] }.
    map do |name, opts|
      next name.to_sym unless opts.fetch(:allow_not, true)

      [name.to_sym, "#{name}_not".to_sym]
    end
  res.flatten
end

#json_sort_scope(c) ⇒ Object



182
183
184
# File 'app/components/marty/base_rule_view.rb', line 182

def json_sort_scope(c)
  lambda { |r, dir| r.order("#{c}::text " + dir.to_s) }
end

#jsonb_getter(c) ⇒ Object



140
141
142
# File 'app/components/marty/base_rule_view.rb', line 140

def jsonb_getter(c)
  lambda { |r| md = r.send(c); md.present? && md.to_json || '' }
end

#jsonb_simple_getter(c) ⇒ Object



144
145
146
# File 'app/components/marty/base_rule_view.rb', line 144

def jsonb_simple_getter(c)
  lambda { |r| Marty::BaseRuleView.hash_to_ruletext(r.send(c)) }
end

#jsonb_simple_setter(c) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'app/components/marty/base_rule_view.rb', line 148

def jsonb_simple_setter(c)
  msg = "#{c}="
  lambda { |r, v|
    return r.send(msg, nil) if v.blank?

    begin
      final = Marty::BaseRuleView.ruletext_to_hash(v)
    rescue StandardError => e
      final = { "~~ERROR~~": e.message }
    end

    # ActiveRecord ignores change in json key order
    r.send("#{c}_will_change!") if r[c.to_s].to_a != final.to_a

    r.send(msg, final)
  }
end

#klassObject



8
9
10
# File 'app/components/marty/base_rule_view.rb', line 8

def klass
  self.class.klass
end