Class: LcpRuby::Metadata::PageDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/lcp_ruby/metadata/page_definition.rb

Constant Summary collapse

VALID_LAYOUTS =
%i[semantic grid].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}) ⇒ PageDefinition

Returns a new instance of PageDefinition.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 12

def initialize(attrs = {})
  @name = attrs[:name].to_s
  @model = attrs[:model]&.to_s.presence
  @slug = attrs[:slug]
  @dialog_config = HashUtils.stringify_deep(attrs[:dialog_config] || {})
  @zones = attrs[:zones] || []
  @auto_generated = !!attrs[:auto_generated]
  @layout = (attrs[:layout] || :semantic).to_sym
  @title_literal = attrs[:title].presence
  @title_key = attrs[:title_key]&.to_s
  @index_presenter = attrs[:index_presenter]&.to_s.presence
  @raw_hash = attrs[:raw_hash]
  @filter_form_definition = attrs[:filter_form_definition] || []
  @scope_filter_definitions = attrs[:scope_filter_definitions] || []
  @auto_submit = attrs.key?(:auto_submit) ? attrs[:auto_submit] : "derive"
  # Page-level visibility gate. Reuses the same shape as
  # ZoneDefinition#visible_when. Validated by the boot-time
  # RuntimeInvariantValidator (AUTH-002/003); the runtime gate
  # itself ships in v3 (PageAuthorization concern).
  @visible_when = attrs[:visible_when].is_a?(Hash) ?
                    HashUtils.stringify_deep(attrs[:visible_when]) :
                    nil
  @source_path = attrs[:source_path]
  @source_type = attrs[:source_type]

  validate!
end

Instance Attribute Details

#auto_generatedObject (readonly)

Returns the value of attribute auto_generated.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def auto_generated
  @auto_generated
end

#auto_submitObject (readonly)

Returns the value of attribute auto_submit.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def auto_submit
  @auto_submit
end

#dialog_configObject (readonly)

Returns the value of attribute dialog_config.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def dialog_config
  @dialog_config
end

#filter_form_definitionObject (readonly)

Returns the value of attribute filter_form_definition.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def filter_form_definition
  @filter_form_definition
end

#index_presenterObject (readonly)

Returns the value of attribute index_presenter.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def index_presenter
  @index_presenter
end

#layoutObject (readonly)

Returns the value of attribute layout.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def layout
  @layout
end

#modelObject (readonly)

Returns the value of attribute model.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def model
  @model
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def name
  @name
end

#raw_hashObject (readonly)

Returns the value of attribute raw_hash.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def raw_hash
  @raw_hash
end

#scope_filter_definitionsObject (readonly)

Returns the value of attribute scope_filter_definitions.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def scope_filter_definitions
  @scope_filter_definitions
end

#slugObject (readonly)

Returns the value of attribute slug.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def slug
  @slug
end

#source_pathObject

Returns the value of attribute source_path.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def source_path
  @source_path
end

#source_typeObject

Returns the value of attribute source_type.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def source_type
  @source_type
end

#title_keyObject (readonly)

Returns the value of attribute title_key.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def title_key
  @title_key
end

#visible_whenObject (readonly)

Returns the value of attribute visible_when.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def visible_when
  @visible_when
end

#zonesObject (readonly)

Returns the value of attribute zones.



6
7
8
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 6

def zones
  @zones
end

Class Method Details

.from_hash(hash) ⇒ Object

Note: signature stays single-positional. The loader sets ‘source_path` via `set_source!` after `from_hash` returns; adding a `source_path:` kwarg would collide with Ruby 3’s bare-hash → kwargs conversion when callers pass string-keyed hashes inline (e.g. ‘from_hash(“name” => “x”, …)`). Errors from `FilterFormValidator.validate_shape!` therefore do not embed a file path at boot — page_name is enough to locate the problem; rake `lcp_ruby:validate` and the DB-backed save path do surface source_path through their own re-validation.

Parameters:

  • hash (Hash)

    raw page hash (from YAML or DB JSON column)



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 51

def self.from_hash(hash)
  hash = HashUtils.stringify_deep(hash)

  # Reject `parameters:` block + enforce filter_form/scope_filters
  # shape rules before constructing anything downstream. Runs at
  # loader time when `loader.model_definitions` may be mid-build;
  # model-aware rules are enforced later from ConfigurationValidator
  # and Pages::DefinitionValidator via `validate_against_models!`.
  Pages::FilterFormValidator.validate_shape!(hash, page_name: hash["name"])

  zones = (hash["zones"] || []).map { |z| ZoneDefinition.from_hash(z) }

  new(
    name: hash["name"],
    model: hash["model"],
    slug: hash["slug"],
    dialog_config: hash["dialog"] || {},
    zones: zones,
    auto_generated: hash["auto_generated"] || false,
    layout: hash["layout"] || :semantic,
    title: hash["title"],
    title_key: hash["title_key"],
    index_presenter: hash["index_presenter"],
    raw_hash: hash,
    filter_form_definition: hash["filter_form"] || [],
    scope_filter_definitions: hash["scope_filters"] || [],
    auto_submit: hash.key?("auto_submit") ? hash["auto_submit"] : "derive",
    visible_when: hash["visible_when"]
  )
end

Instance Method Details

#any_zone_gated?Boolean

True when at least one zone has a parseable ‘visible_when:` —i.e. the page delegates access control to per-zone gates. Memoized because zones are immutable post-construction; called per-request by `Authorization::PageGate` and at boot by `Authorization::RuntimeInvariantValidator` (AUTH-002 check).

Returns:

  • (Boolean)


99
100
101
102
103
104
105
106
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 99

def any_zone_gated?
  return @any_zone_gated if defined?(@any_zone_gated)

  @any_zone_gated = @zones.any? do |z|
    z.respond_to?(:visible_when) && z.visible_when &&
      Conditions::Validator.parseable?(z.visible_when)
  end
end

#auto_generated?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 86

def auto_generated?
  @auto_generated
end

#composite?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 116

def composite?
  !auto_generated? && zones.size > 1 && !standalone?
end

#dialog_closable?Boolean

Returns:

  • (Boolean)


225
226
227
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 225

def dialog_closable?
  dialog_config.fetch("closable", true)
end

#dialog_only?Boolean

Returns:

  • (Boolean)


160
161
162
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 160

def dialog_only?
  !routable? && dialog_config.any?
end

#dialog_sizeObject



221
222
223
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 221

def dialog_size
  dialog_config["size"] || "medium"
end

#dialog_title_keyObject



229
230
231
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 229

def dialog_title_key
  dialog_config["title_key"]
end

#form_zoneObject



186
187
188
189
190
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 186

def form_zone
  return @form_zone if defined?(@form_zone)

  @form_zone = @zones.find(&:form_zone?)
end

#grid?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 108

def grid?
  @layout == :grid
end

#grid_template_columnsObject



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 206

def grid_template_columns
  return @grid_template_columns if defined?(@grid_template_columns)

  @grid_template_columns = if has_sidebar?
    main_w = zones_for_area("main").filter_map(&:width).first
    sidebar_w = zones_for_area("sidebar").filter_map(&:width).first
    if main_w || sidebar_w
      m = main_w || 8
      s = sidebar_w || 4
      # Master-detail: sidebar is column 1 (left), main is column 2 (right)
      master_detail? ? "#{s}fr #{m}fr" : "#{m}fr #{s}fr"
    end
  end
end

#has_below?Boolean

Returns:

  • (Boolean)


156
157
158
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 156

def has_below?
  zones_for_area("below").any?
end

#has_selection?Boolean

Returns:

  • (Boolean)


170
171
172
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 170

def has_selection?
  selection_zone.present?
end

#has_sidebar?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 152

def has_sidebar?
  zones_for_area("sidebar").any?
end

#has_tabs?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 148

def has_tabs?
  tab_zones.any?
end

#index_composite?Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 120

def index_composite?
  return @index_composite if defined?(@index_composite)

  # Master-detail pages are always index composites (selection zone is the index)
  if master_detail?
    @index_composite = true
    return @index_composite
  end

  presenter = main_zone&.presenter_zone? &&
    LcpRuby.loader.presenter_definitions.dig(main_zone.presenter)

  @index_composite = composite? &&
    !has_tabs? &&
    presenter &&
    presenter.index_config&.dig("table_columns").present? &&
    !presenter.has_show_sections?
end

#main_presenter_nameObject



192
193
194
195
196
197
198
199
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 192

def main_presenter_name
  # For master-detail, the index presenter is the selection zone (sidebar list)
  if master_detail?
    selection_zone&.presenter
  else
    main_zone&.presenter
  end
end

#main_zoneObject



180
181
182
183
184
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 180

def main_zone
  zones.find { |z| z.presenter_zone? && z.area == "main" } ||
    zones.find(&:presenter_zone?) ||
    zones.first
end

#master_detail?Boolean

Returns:

  • (Boolean)


174
175
176
177
178
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 174

def master_detail?
  return @master_detail if defined?(@master_detail)

  @master_detail = has_selection? && !standalone?
end

#routable?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 82

def routable?
  slug.present?
end

#selection_zoneObject



164
165
166
167
168
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 164

def selection_zone
  return @selection_zone if defined?(@selection_zone)

  @selection_zone = @zones.find(&:selection?)
end

#semantic?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 112

def semantic?
  @layout == :semantic
end

#standalone?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 90

def standalone?
  @model.nil?
end

#tab_zonesObject



144
145
146
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 144

def tab_zones
  zones_for_area("tabs")
end

#titleObject



201
202
203
204
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 201

def title
  I18nLabel.resolve(literal: @title_literal, key: @title_key,
                    fallback: @name.humanize)
end

#zones_for_area(area) ⇒ Object



139
140
141
142
# File 'lib/lcp_ruby/metadata/page_definition.rb', line 139

def zones_for_area(area)
  @zones_by_area ||= zones.group_by(&:area)
  @zones_by_area.fetch(area.to_s, [])
end