Class: LcpRuby::VirtualFields::VirtualForm

Inherits:
Object
  • Object
show all
Defined in:
lib/lcp_ruby/virtual_fields/virtual_form.rb

Overview

Per-request synthetic form-like object. Builds an anonymous AM::Model class on first ‘#to_model` access, declares attributes for each VirtualField, whitelists incoming params against the declared field set, and coerces values through AM::Attributes’ type system.

Designed as shared infrastructure (spec § 1 Dual goal): consumers include ‘Pages::FilterForm` (PR 5) plus future use cases (multi- step wizards, virtual show pages, PaaS config UI, custom-field designer).

Security invariants (spec § 3):

* Attributes are declared only from the boot-validated field
  list — never from URL keys.
* Incoming params are SLICED against the union of every field's
  `attribute_names` (`date_range` expands to `_from`/`_to`)
  before assignment. `__send__`, `unknown`, and any other
  attacker-supplied key is dropped silently.
* `ActiveModel::API` is included — it transitively brings in
  `ActiveModel::Validations` (via AM::AttributeAssignment +
  AM::Validations). The original spec feared this would retain
  the anonymous class via `DescendantsTracker`; Rails 8+ uses
  `ObjectSpace::WeakMap`, so synthetic classes ARE reclaimed by
  GC. Pinned by spec/lib/lcp_ruby/virtual_fields/lifecycle_spec.rb.
* Hash / Parameters values for plain attributes coerce to `nil`
  (so AM::Attributes applies the declared default). Array-typed
  attributes (multi:) defer to `Type::ArrayOf#cast`, which
  filters Hash elements out of arrays element-by-element.

Direct Known Subclasses

Pages::FilterForm

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, fields:, target_model: nil, context: {}) ⇒ VirtualForm

Returns a new instance of VirtualForm.



37
38
39
40
41
42
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 37

def initialize(name:, fields:, target_model: nil, context: {})
  @name         = name
  @fields       = fields
  @target_model = target_model
  @context      = context
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



35
36
37
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 35

def context
  @context
end

#fieldsObject (readonly)

Returns the value of attribute fields.



35
36
37
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 35

def fields
  @fields
end

#nameObject (readonly)

Returns the value of attribute name.



35
36
37
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 35

def name
  @name
end

#target_modelObject (readonly)

Returns the value of attribute target_model.



35
36
37
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 35

def target_model
  @target_model
end

Instance Method Details

#any?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 68

def any?
  @fields.any?
end

#attributesObject



72
73
74
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 72

def attributes
  to_model.attributes
end

#each_field(&block) ⇒ Object



60
61
62
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 60

def each_field(&block)
  @fields.each(&block)
end

#field(name) ⇒ Object



64
65
66
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 64

def field(name)
  @fields.find { |f| f.name.to_s == name.to_s }
end

#model_nameObject

AM::Model contract for ‘form_with(model: filter_form.to_model)`. Returning a stable, present name keeps Rails from triggering the “anonymous class” branch which falls back to the literal class name (something like `#<Class:0x…>`).



52
53
54
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 52

def model_name
  @model_name ||= ActiveModel::Name.new(self.class, nil, @name.to_s.camelize)
end

#persisted?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 56

def persisted?
  false
end

#to_modelObject



44
45
46
# File 'lib/lcp_ruby/virtual_fields/virtual_form.rb', line 44

def to_model
  @to_model ||= build_synthetic_instance
end