Class: Snails::Form

Inherits:
Object show all
Defined in:
runtime/snails/form.rbs,
runtime/snails/form/errors.rbs,
runtime/snails/form/rule_engine.rbs,
runtime/snails/form/rule_options.rbs,
runtime/snails/form.rb,
runtime/snails/form/errors.rb,
runtime/snails/form/rule_engine.rb,
runtime/snails/form/rule_options.rb

Defined Under Namespace

Classes: Errors

Constant Summary collapse

VALIDATIONS =

---- declaration registries (module-level, owner-partitioned) ----- Array constants, not class ivars: spinel does not carry a class ivar into a subclass, and a class-body registration lands here with the owner's class name.

[]
MESSAGE_SCOPES =

Returns:

[]
DEFAULT_RULE_OPTIONS =

Options that may sit at the validates call level and apply to every rule in that call — validates :email, presence: true, if: :ready?. A rule-level option of the same name wins.

[ :if, :unless, :allow_nil, :allow_blank ]
COMMON_RULE_OPTIONS =

The options every rule accepts, and the options each rule brings on top. The rule-name allowlist is the hash's keys: an unknown rule or option key in validates is rejected at declaration time, never ignored at validation time.

[ :message, :allow_nil, :allow_blank, :if, :unless ]
RULE_DECLARATION_OPTIONS =

Returns:

{
  presence: [],
  length: [ :minimum, :maximum, :is, :within, :in ],
  format: [ :with, :without ],
  inclusion: [ :in ],
  exclusion: [ :in ],
  uniqueness: [ :model, :column, :scope ],
  confirmation: [],
}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(values = {}) ⇒ Form

project the input hash onto the declared fields through the write seam, then run the normalize hook. Keys are matched against FIELDS (String and Symbol input keys both — HTTP params carry Strings; a later key wins when both spellings are present, following hash order), so keys outside the declaration are ignored: HTTP input is a superset by nature, and an input key can never reach framework state (errors) because it is not a field. A field the input never carried reads nil.

Examples:

form = SignupForm.new(params)

Parameters:

  • values (Hash) (defaults to: {}) —

    the input hash

Raises:

  • (ArgumentError) —

    values is not a Hash (anything else is a caller bug, not empty input), or the form declares no FIELDS loop



191
# File 'runtime/snails/form.rb', line 191

def initialize: (?Hash[untyped, untyped] values) -> void

Class Method Details

.field_names ⇒ Array[String]

the field names this form declares, as written in its FIELDS accessor loop (the projection filter and the validates/errors.add declaration check's registry).

Examples:

FIELDS = %w[email password].each { |n| define_method(n) { read_attribute(n) } }

Returns:

  • (Array[String]) —

    the names in declaration order — a copy, so a caller cannot mutate the FIELDS registry

Raises:

  • (ArgumentError) —

    the form declares no FIELDS loop — a form without one cannot project input or check rule targets, so the guidance line replaces a bare NameError



51
# File 'runtime/snails/form.rb', line 51

def self.field_names: () -> Array[String]

.message_scope(scope) ⇒ void

point this form's error keys at a locale subtree. Without it, keys resolve under activemodel.errors.models.<form_name>.attributes.

Examples:

message_scope "activerecord.errors.models.user.attributes"

Parameters:

  • scope (String) —

    the locale subtree prefix

Raises:

  • (ArgumentError) —

    a non-String scope



171
# File 'runtime/snails/form.rb', line 171

def self.message_scope: (untyped scope) -> void

.validate(*method_names, **options) ⇒ void

register an instance method as a custom validation. It runs in registration order like a rule and reports through errors.add, for conditions the declarative rules do not express (short-circuits, cross-field rules). if:/ unless: guard the call with a Symbol method name, as on a rule. The method is dispatched with send, so a typo'd name raises NoMethodError at the first valid? instead of skipping silently.

Examples:

validate :email_is_blocked, if: :check_email?

def email_is_blocked
  errors.add(:email, "is blocked") if email == "blocked@x.test"
end

Parameters:

  • method_names (Array[Symbol]) —

    the method names

  • options (Hash) —

    the if:/unless: Symbol guards

Raises:

  • (ArgumentError) —

    a non-Symbol name or an unknown option



146
# File 'runtime/snails/form.rb', line 146

def self.validate: (*Symbol method_names, **untyped options) -> void

.validates(*attributes, **rules) ⇒ void

register rules for declared fields. Rules run in registration order during valid?, and the errors keep that order — an HTTP 422 body is an ordered message list, so declaration order is the contract. One call may name several fields; each rule's attributes are registered together, before the next rule's (rule-major).

Common options may sit at the call level and apply to every rule in the call — validates :email, presence: true, if: :ready? — or inside one rule's hash; a rule-level option wins.

Examples:

FIELDS = %w[email password password_confirmation kind].each { |n| define_method(n) { read_attribute(n) } }

validates :email, presence: true, format: { with: /\A...\z/ }, allow_blank: true
validates :kind, inclusion: { in: %w[counter delta gauge] }, allow_blank: true
validates :password, presence: true
validates :password, length: { minimum: 8, maximum: 128 }, allow_blank: true
validates :password, confirmation: true, if: :password_length_ok?

Parameters:

  • attributes (Array[Symbol]) —

    field names from the form's FIELDS loop

  • rules (Hash[Symbol, (true, Hash)]) —

    rule name => true or an option hash — the common options (message, allow_nil, allow_blank, if, unless) plus the rule's own (see RULE_DECLARATION_OPTIONS). if/unless/allow_nil/ allow_blank at the call level apply to every rule below them.

Raises:

  • (ArgumentError) —

    a non-Symbol attribute, a name that is not a declared field, an unknown rule, options that do not satisfy the rule, or a confirmation field that is not declared — a rejected call registers nothing



88
# File 'runtime/snails/form.rb', line 88

def self.validates: (*Symbol attributes, **untyped rules) -> void

Instance Method Details

#errors ⇒ Errors

the current error list: empty until a rule or custom method fails, replaced by every valid? call.

Returns:



262
# File 'runtime/snails/form.rb', line 262

def errors: () -> Errors

#human_attribute_name(attribute) ⇒ String

the display name a full message prefixes (email -> Email, password_confirmation -> Password confirmation). Override to carry locale names.

Parameters:

  • attribute (Symbol) —

    the field name

Returns:



272
# File 'runtime/snails/form.rb', line 272

def human_attribute_name: (Symbol attribute) -> String

#normalize ⇒ void

the input-normalization hook, called once from new before validation reads any value. Override it for strips, downcasing, defaults — write through write_attribute (there are no generated setters); input shaping only, never persistence.



282
# File 'runtime/snails/form.rb', line 282

def normalize: () -> void

#read_attribute(name) ⇒ untyped

the read seam of the field-storage path: every generated reader and the rule engine route through this one method, so the value path holds no dynamic dispatch. A missing field answers nil.

Examples:

the accessor loop a form writes against this seam

FIELDS = %w[email password].each { |n| define_method(n) { read_attribute(n) } }

Parameters:

  • name (Symbol, String) —

    the field name (String accepted — the accessor loop iterates member names as written)

Returns:

  • (untyped) —

    the stored value, or nil when absent



211
# File 'runtime/snails/form.rb', line 211

def read_attribute: (untyped name) -> untyped

#valid? ⇒ bool

reset the error list, run this class's registered validations in registration order, and answer whether the input is clean. Rule guards (if/unless/allow_nil/allow_blank) decide what each rule may report, and custom validate methods run in the same order — so errors.full_messages reflects declaration order, the ordered-list contract a 422 body depends on. Calling it again starts from an empty list.

Returns:

  • (bool)


240
# File 'runtime/snails/form.rb', line 240

def valid?: () -> bool

#write_attribute(name, value) ⇒ untyped

the write seam (there are no generated setters: spinel refuses an interpolated define_method name, so writes stay a plain call). The projection and the normalize hook write here. Input only — no dirty tracking; a name outside FIELDS is a caller bug and fails loud instead of storing a value no reader will see.

Parameters:

  • name (Symbol, String) —

    the field name

  • value (untyped) —

    the value to store

Returns:

  • (untyped) —

    value



224
# File 'runtime/snails/form.rb', line 224

def write_attribute: (untyped name, untyped value) -> untyped