Module: Plutonium::Definition::FormLayout

Extended by:
ActiveSupport::Concern
Included in:
Base, Interaction::Base, Wizard::FieldCapture
Defined in:
lib/plutonium/definition/form_layout.rb

Overview

Declarative form sectioning. Mixed into both resource definitions and interactions (mirrors StructuredInputs). The layout references field KEYS only and carries section-level options; per-field config stays on input.

Examples:

form_layout do
  section :identity, :name, :email, label: "Your identification"
  section :address, :street, :city, collapsible: true, columns: 2,
    condition: -> { object.requires_address? }
  ungrouped label: "Other"
end

Defined Under Namespace

Classes: Builder, ResolvedSection, Section

Constant Summary collapse

UNGROUPED_KEY =
:ungrouped

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.assign_ownership(layout, fields) ⇒ Array(Hash, Array<Symbol>)

First-section-wins ownership, shared by every layout resolver (the resource form via #resolve_form_sections, and the wizard's StepAdapter / FieldImporter). Each field is claimed by the FIRST explicit section that lists it, restricted to the currently-permitted fields; a listed field outside that set is simply skipped (never renders, never an error).

Parameters:

  • layout (Array<Section>)
  • fields (Array<Symbol,String>)

    the permitted field set, in order

Returns:

  • (Array(Hash, Array<Symbol>))

    [owner_by_field, leftover_fields]



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/plutonium/definition/form_layout.rb', line 45

def self.assign_ownership(layout, fields)
  fields = fields.map(&:to_sym)
  known = fields.to_set
  owner = {}
  layout.each do |section|
    next if section.ungrouped?
    section.fields.map(&:to_sym).each { |f| owner[f] ||= section.key if known.include?(f) }
  end
  leftovers = fields.reject { |f| owner.key?(f) }
  [owner, leftovers]
end

.resolve_sections(layout, fields) ⇒ Array<ResolvedSection>

The canonical resolution: first-section-wins ownership, with unclaimed permitted fields falling into the (explicit, or a synthesized trailing) ungrouped bucket so nothing silently disappears. Every declared section is kept (empty ones are dropped at render). Shared by #resolve_form_sections and the wizard StepAdapter's raw-layout path so the two never drift.

Parameters:

  • layout (Array<Section>)
  • fields (Array<Symbol,String>)

    the permitted field set, in order

Returns:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/plutonium/definition/form_layout.rb', line 66

def self.resolve_sections(layout, fields)
  owner, leftovers = assign_ownership(layout, fields)

  resolved = layout.map do |section|
    section_fields =
      if section.ungrouped?
        leftovers
      else
        section.fields.map(&:to_sym).select { |f| owner[f] == section.key }
      end
    ResolvedSection.new(section:, fields: section_fields)
  end

  unless layout.any?(&:ungrouped?)
    resolved.push(ResolvedSection.new(
      section: Section.new(key: UNGROUPED_KEY, fields: [].freeze, options: {}.freeze),
      fields: leftovers
    ))
  end

  resolved
end

Instance Method Details

#defined_form_layoutObject

Instance access — the form render path holds a definition/interaction instance (mirrors the defineable_prop convention).



148
149
150
# File 'lib/plutonium/definition/form_layout.rb', line 148

def defined_form_layout
  self.class.defined_form_layout
end

#resolve_form_sections(resource_fields) ⇒ Object

Resolve the policy-filtered field list into ordered ResolvedSections. Returns nil when no layout is declared (caller falls back to one grid).



154
155
156
157
158
159
# File 'lib/plutonium/definition/form_layout.rb', line 154

def resolve_form_sections(resource_fields)
  layout = defined_form_layout
  return nil unless layout

  FormLayout.resolve_sections(layout, resource_fields)
end