Class: Forms::Validations::Introspector

Inherits:
Object
  • Object
show all
Defined in:
lib/forms/validations/introspector.rb

Overview

Introspects an ActiveModel class (or instance) and translates each attribute's validators into a data: hash ready to merge into a Phlex element. Phlex handles the data- prefixing and underscore-to-hyphen conversion at render time.

Sticks to validators whose semantics are reproducible client side. Skips anything with :if / :unless / :on because those need server context the browser doesn't have. Server validation remains authoritative — the client side just shortens the loop for the common cases.

Defined Under Namespace

Classes: Null

Constant Summary collapse

CONTROLLER_PREFIX =

The Stimulus identifier prefix. Kept in sync with the file path the controllers ship at (phlex_forms/controllers/validations/*_controller), so lazyLoadControllersFrom("phlex_forms/controllers") resolves validations--length → .../validations/length_controller (issue #12).

"validations"
SUPPORTED =

Validators we know how to mirror. Keys are the short class name (without namespace), values are the controller suffix appended to CONTROLLER_PREFIX. Matching on the short name picks up both ActiveModel::Validations::* (form objects) and ActiveRecord::Validations::* (AR models inherit a parallel set of subclasses for uniqueness/etc.) without listing both namespaces.

{
  "PresenceValidator" => "presence",
  "LengthValidator" => "length",
  "FormatValidator" => "format",
  "NumericalityValidator" => "numericality",
  "InclusionValidator" => "inclusion",
  "ExclusionValidator" => "exclusion",
  "ConfirmationValidator" => "confirmation",
  "AcceptanceValidator" => "acceptance"
}.freeze
LENGTH_KEYS =
%i[maximum minimum is].freeze
NUMERICALITY_KEYS =
%i[
  greater_than greater_than_or_equal_to
  less_than less_than_or_equal_to
  equal_to other_than
  only_integer odd even
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_class) ⇒ Introspector

Returns a new instance of Introspector.



60
61
62
# File 'lib/forms/validations/introspector.rb', line 60

def initialize(model_class)
  @model_class = model_class
end

Class Method Details

.for(model_or_class) ⇒ Object

Convenience: returns a null introspector for nil models so callers don't need to guard.



51
52
53
54
55
56
57
58
# File 'lib/forms/validations/introspector.rb', line 51

def self.for(model_or_class)
  return Null.new if model_or_class.nil?

  klass = model_or_class.is_a?(Class) ? model_or_class : model_or_class.class
  return Null.new unless klass.respond_to?(:validators_on)

  new(klass)
end

Instance Method Details

#data_attributes_for(attribute) ⇒ Object

Returns a hash of the shape:

{
controller: "validations--presence validations--length",
validations__presence_required_value: "true",
validations__length_maximum_value: "60",
}

Returns {} when no supported validators are present. The double-underscore key encoding survives Phlex/Rails' underscore-to-hyphen rewrite: __- (deliberate).



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/forms/validations/introspector.rb', line 74

def data_attributes_for(attribute)
  validators = applicable_validators(attribute)
  return {} if validators.empty?

  controllers = []
  attrs = {}

  validators.each do |validator|
    suffix = SUPPORTED[validator.class.name.split("::").last]
    next unless suffix

    controllers << "#{CONTROLLER_PREFIX}--#{suffix}"
    value_attributes(suffix, validator, attribute).each do |key, value|
      attrs[data_key(suffix, key)] = value
    end
  end

  return {} if controllers.empty?

  { controller: controllers.uniq.join(" ") }.merge(attrs)
end