Class: Forms::Field

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, model:, scope:, errors:, form:) ⇒ Field

Returns a new instance of Field.



12
13
14
15
16
17
18
# File 'lib/forms/field.rb', line 12

def initialize(name:, model:, scope:, errors:, form:)
  @name = name
  @model = model
  @scope = scope
  @errors = errors
  @form = form
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



10
11
12
# File 'lib/forms/field.rb', line 10

def errors
  @errors
end

#modelObject (readonly)

Returns the value of attribute model.



10
11
12
# File 'lib/forms/field.rb', line 10

def model
  @model
end

#nameObject (readonly)

Returns the value of attribute name.



10
11
12
# File 'lib/forms/field.rb', line 10

def name
  @name
end

#scopeObject (readonly)

Returns the value of attribute scope.



10
11
12
# File 'lib/forms/field.rb', line 10

def scope
  @scope
end

Instance Method Details

#apply_validations(options) ⇒ Object

Merge a per-call validate: override into the field's option hash. Strips :validate and, when rules apply, merges the Stimulus data into data:. Falls back to the form-level introspector when :validate is absent.

validate: false           → opt this field out
validate: true            → the form-level introspector
validate: { length: {…} } → explicit inline rules


152
153
154
155
156
157
158
159
# File 'lib/forms/field.rb', line 152

def apply_validations(options)
  return consume_validate(options) if options.key?(:validate)

  data = form_introspector.data_attributes_for(@name)
  return options if data.empty?

  options.merge(data: merge_data(options[:data], data))
end

#checkbox(*modifiers) ⇒ Object Also known as: check_box



50
51
52
# File 'lib/forms/field.rb', line 50

def checkbox(*modifiers, **)
  Forms::Checkbox.new(*modifiers, checked: field_value, **field_attributes.except(:value), **)
end

#choices_select(choices = nil, *modifiers, **options) ⇒ Object

Enhanced select: choices.js-backed when searchable, native otherwise.



74
75
76
77
78
79
80
81
82
# File 'lib/forms/field.rb', line 74

def choices_select(choices = nil, *modifiers, **options)
  searchable = options.delete(:searchable) { false }
  opts = select_options(options)
  if searchable
    Forms::ChoicesSelect.new(*modifiers, choices:, selected: field_value, searchable: true, **opts)
  else
    Forms::Select.new(*modifiers, choices:, selected: field_value, **opts)
  end
end

#control(label: nil, hint: nil, required: false) ⇒ Object



88
89
90
# File 'lib/forms/field.rb', line 88

def control(label: nil, hint: nil, required: false, **, &)
  Forms::FormControl.new(label:, hint:, error: field_error_message, for: field_id, required:, **, &)
end

#field_idObject



123
124
125
126
127
128
129
# File 'lib/forms/field.rb', line 123

def field_id
  if @scope
    "#{@scope.tr('[', '_').delete(']')}_#{@name}"
  else
    @name.to_s
  end
end

#field_labelObject

Humanized label text: the model's human_attribute_name when available.



95
96
97
98
99
100
101
# File 'lib/forms/field.rb', line 95

def field_label
  if @model.respond_to?(:class) && @model.class.respond_to?(:human_attribute_name)
    @model.class.human_attribute_name(@name)
  else
    @name.to_s.tr("_", " ").capitalize
  end
end

#field_nameObject



119
120
121
# File 'lib/forms/field.rb', line 119

def field_name
  @scope ? "#{@scope}[#{@name}]" : @name.to_s
end

#field_valueObject

Ransack-safe getter: dispatches through method_missing consumers (e.g. Ransack::Search predicate getters) but only swallows the direct dispatch miss.



133
134
135
136
137
138
139
140
141
# File 'lib/forms/field.rb', line 133

def field_value
  return nil unless @model

  @model.public_send(@name)
rescue NoMethodError => e
  raise unless e.receiver.equal?(@model) && e.name == @name

  nil
end

#file(*modifiers) ⇒ Object



46
47
48
# File 'lib/forms/field.rb', line 46

def file(*modifiers, **)
  Forms::FileInput.new(*modifiers, **field_attributes.except(:value), **)
end

#hiddenObject



36
37
38
# File 'lib/forms/field.rb', line 36

def hidden(**)
  Forms::Input.new(type: :hidden, **field_attributes, **)
end

#input(*modifiers, type: :text) ⇒ Object

--- leaf component builders (return component instances to render) ---



22
23
24
# File 'lib/forms/field.rb', line 22

def input(*modifiers, type: :text, **)
  Forms::Input.new(*modifiers, type:, **field_attributes, **)
end

#invalid?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/forms/field.rb', line 115

def invalid?
  @errors&.include?(@name)
end

#label(text = nil, *modifiers, &block) ⇒ Object



84
85
86
# File 'lib/forms/field.rb', line 84

def label(text = nil, *modifiers, **, &block)
  Forms::Label.new(*modifiers, text: text || (block ? nil : field_label), for: field_id, **, &block)
end

#radio(value, *modifiers, **options) ⇒ Object Also known as: radio_button



59
60
61
62
63
64
65
66
# File 'lib/forms/field.rb', line 59

def radio(value, *modifiers, **options)
  Forms::Radio.new(
    *modifiers,
    value:,
    checked: field_value == value,
    **field_attributes.merge(options).merge(id: "#{field_id}_#{value}")
  )
end

#required?Boolean

Inferred from the model's presence validators (ActiveModel). False when the model doesn't expose validators.

Returns:

  • (Boolean)


105
106
107
108
109
110
111
112
113
# File 'lib/forms/field.rb', line 105

def required?
  return false unless @model.respond_to?(:class) && @model.class.respond_to?(:validators_on)

  @model.class.validators_on(@name).any? do |v|
    v.is_a?(ActiveModel::Validations::PresenceValidator) && !conditional?(v)
  end
rescue StandardError
  false
end

#rich_textarea(*modifiers) ⇒ Object Also known as: rich_text_area



31
32
33
# File 'lib/forms/field.rb', line 31

def rich_textarea(*modifiers, **)
  Forms::RichTextarea.new(*modifiers, name: field_name, id: field_id, value: field_value, **)
end

#select(choices = nil, **options) ⇒ Object



69
70
71
# File 'lib/forms/field.rb', line 69

def select(choices = nil, **options)
  Forms::Select.new(choices:, selected: field_value, **select_options(options))
end

#textarea(*modifiers) ⇒ Object Also known as: text_area



26
27
28
# File 'lib/forms/field.rb', line 26

def textarea(*modifiers, **)
  Forms::Textarea.new(*modifiers, **field_attributes, **)
end

#toggle(*modifiers) ⇒ Object



55
56
57
# File 'lib/forms/field.rb', line 55

def toggle(*modifiers, **)
  Forms::Toggle.new(*modifiers, checked: field_value, **field_attributes.except(:value), **)
end

#wrapped_input(*modifiers, type: :text) ⇒ Object

daisyui v5 "icon/text inside the field" wrapper. The block renders the leading content (icon, prefix); the bare input is wired to this field.



42
43
44
# File 'lib/forms/field.rb', line 42

def wrapped_input(*modifiers, type: :text, **, &)
  Forms::WrappedInput.new(*modifiers, type:, **field_attributes.except(:error), error: invalid?, **, &)
end