Class: Tiler::UserWidget

Inherits:
ApplicationRecord show all
Defined in:
app/models/tiler/user_widget.rb

Overview

Runtime, no-code widget definition. Each row builds an anonymous Tiler::Widget subclass on boot (and after_save) and registers it under its slug. The Widget renders a partial that evaluates the row’s Liquid template against ‘panel`/`data` — sandboxed, no Ruby execution.

Constant Summary collapse

DATA_KINDS =
%w[config_only query].freeze
SLUG_RE =
/\A[a-z][a-z0-9_]{2,39}\z/
TEMPLATE_MAX =
10_000
AGGS =
%w[count sum avg min max last].freeze
SLUG_PREFIX =
"user_".freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.register_all!Object

Iterate every persisted row and (re-)register their widget classes. Called from the engine boot initializer.



95
96
97
98
99
100
# File 'app/models/tiler/user_widget.rb', line 95

def self.register_all!
  return unless table_exists?
  find_each(&:register!)
rescue ActiveRecord::StatementInvalid
  # Table doesn't exist yet (pre-migration boot). Skip silently.
end

Instance Method Details

#parsed_default_configObject



47
48
49
50
51
# File 'app/models/tiler/user_widget.rb', line 47

def parsed_default_config
  JSON.parse(default_config.presence || "{}")
rescue JSON::ParserError
  {}
end

#parsed_query_definitionObject



41
42
43
44
45
# File 'app/models/tiler/user_widget.rb', line 41

def parsed_query_definition
  JSON.parse(query_definition.presence || "{}")
rescue JSON::ParserError
  {}
end

#register!Object

Build + register the anonymous Widget subclass for this row. Called on save and once at boot via .register_all!



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/tiler/user_widget.rb', line 69

def register!
  uw = self
  query_kls = build_query_class

  widget_kls = Class.new(::Tiler::Widget) do
    self.type        = uw.registry_slug
    self.partial     = "tiler/widgets/user_widget"
    self.label       = uw.label
    self.query_class = query_kls
    self.default_config = uw.parsed_default_config
    self.default_size   = { w: uw.default_w, h: uw.default_h }
    self.min_size       = { w: 1, h: 1 }
    self.max_size       = { w: 12, h: 12 }
  end

  ::Tiler.widgets.register(uw.registry_slug, klass: widget_kls)
  uw.instance_variable_set(:@widget_class, widget_kls)
  widget_kls
end

#registry_slugObject

Slug used inside Tiler.widgets — namespaced so it can’t collide with built-in Ruby widgets (e.g. “user_my_thing”, not “my_thing”).



37
38
39
# File 'app/models/tiler/user_widget.rb', line 37

def registry_slug
  "#{SLUG_PREFIX}#{slug}"
end

#render_template(panel:, data:) ⇒ Object

Renders the Liquid template against the ‘data` hash + panel info. Returns the rendered HTML string. Errors are surfaced inline so a broken template doesn’t crash the whole dashboard.



56
57
58
59
60
61
62
63
64
65
# File 'app/models/tiler/user_widget.rb', line 56

def render_template(panel:, data:)
  tpl = Liquid::Template.parse(template, error_mode: :strict)
  tpl.render!(
    "panel" => liquid_panel(panel),
    "data"  => stringify(data),
    "config" => panel ? stringify(panel.parsed_config) : {}
  )
rescue Liquid::Error, StandardError => e
  "<pre class=\"tiler-widget-error\">#{ERB::Util.h(e.message)}</pre>".html_safe
end

#unregister!Object



89
90
91
# File 'app/models/tiler/user_widget.rb', line 89

def unregister!
  ::Tiler.widgets.unregister(registry_slug) if ::Tiler.widgets.respond_to?(:unregister)
end