Class: Lifeform::Form

Inherits:
Phlex::HTML
  • Object
show all
Includes:
CapturingRenderable
Defined in:
lib/lifeform/form.rb

Overview

A form object which stores field definitions and can be rendered as a component

Constant Summary collapse

MODEL_PATH_HELPER =
:polymorphic_path

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CapturingRenderable

#render_in

Constructor Details

#initialize(model = nil, url: nil, library: self.class.library, emit_form_tag: true, parent_name: nil, **parameters) ⇒ Form

rubocop:disable Metrics/ParameterLists



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/lifeform/form.rb', line 119

def initialize( # rubocop:disable Metrics/ParameterLists
  model = nil, url: nil, library: self.class.library, emit_form_tag: true, parent_name: nil, **parameters
)
  @model, @url, @library_name, @parameters, @emit_form_tag, @parent_name =
    model, url, library, parameters, emit_form_tag, parent_name
  @library = Libraries.const_get(@library_name.to_s.classify)
  @subform_instances = {}

  @method = parameters[:method] ||= model.respond_to?(:persisted?) && model.persisted? ? :patch : :post
  parameters[:accept_charset] ||= "UTF-8"
  verify_method
end

Instance Attribute Details

#emit_form_tagBoolean (readonly)

Returns:

  • (Boolean)


114
115
116
# File 'lib/lifeform/form.rb', line 114

def emit_form_tag
  @emit_form_tag
end

#libraryClass<Libraries::Default> (readonly)

Returns:



108
109
110
# File 'lib/lifeform/form.rb', line 108

def library
  @library
end

#modelObject (readonly)

Returns:

  • (Object)


102
103
104
# File 'lib/lifeform/form.rb', line 102

def model
  @model
end

#parametersHash (readonly)

Returns:

  • (Hash)


111
112
113
# File 'lib/lifeform/form.rb', line 111

def parameters
  @parameters
end

#parent_nameBoolean (readonly)

Returns:

  • (Boolean)


117
118
119
# File 'lib/lifeform/form.rb', line 117

def parent_name
  @parent_name
end

#urlString (readonly)

Returns:

  • (String)


105
106
107
# File 'lib/lifeform/form.rb', line 105

def url
  @url
end

Class Method Details

.field(name, type: :text, library: self.library, **parameters) ⇒ Object



33
34
35
36
# File 'lib/lifeform/form.rb', line 33

def field(name, type: :text, library: self.library, **parameters)
  parameters[:name] = name.to_sym
  fields[name] = FieldDefinition.new(type, Libraries.const_get(library.to_s.classify), parameters)
end

.fieldsHash<Symbol, FieldDefinition>

Returns:



25
26
27
# File 'lib/lifeform/form.rb', line 25

def fields
  @fields ||= {}
end

.inherited(subclass) ⇒ Object



14
15
16
17
# File 'lib/lifeform/form.rb', line 14

def inherited(subclass)
  super
  subclass.library library
end

.library(library_name = nil) ⇒ Object



42
43
44
45
# File 'lib/lifeform/form.rb', line 42

def library(library_name = nil)
  @library = library_name.to_sym if library_name
  @library ||= :default
end

.name_of_model(model) ⇒ Object



79
80
81
82
83
84
85
86
87
88
# File 'lib/lifeform/form.rb', line 79

def name_of_model(model)
  return "" if model.nil?

  if model.respond_to?(:to_model)
    model.to_model.model_name.param_key
  else
    # Or just use basic underscore
    model.class.name.underscore.tr("/", "_")
  end
end

.param_keysArray<Symbol>

Returns:

  • (Array<Symbol>)


91
92
93
# File 'lib/lifeform/form.rb', line 91

def param_keys
  fields.keys
end

.param_string_keysArray<String>

Returns:

  • (Array<String>)


96
97
98
# File 'lib/lifeform/form.rb', line 96

def param_string_keys
  fields.keys.map(&:to_s)
end

.parameters_to_attributes(kwargs) ⇒ Object

Parameters:

  • kwargs (Hash)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/lifeform/form.rb', line 63

def parameters_to_attributes(kwargs)
  attributes = {}
  kwargs.each do |key, value|
    case value
    when Hash
      value.each do |inner_key, inner_value|
        attributes[:"#{key}_#{inner_key}"] = process_value(inner_key, inner_value)
      end
    else
      attributes[key] = process_value(key, value) unless value.nil?
    end
  end

  attributes
end

.process_value(key, value) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/lifeform/form.rb', line 47

def process_value(key, value)
  return value if key == :if

  case value
  when TrueClass
    key.to_s
  when FalseClass
    nil
  when Symbol, Integer
    value.to_s
  else
    value
  end
end

.subform(name, klass, parent_name: nil) ⇒ Object



38
39
40
# File 'lib/lifeform/form.rb', line 38

def subform(name, klass, parent_name: nil)
  subforms[name.to_sym] = { class: klass, parent_name: parent_name }
end

.subformsObject



29
30
31
# File 'lib/lifeform/form.rb', line 29

def subforms
  @subforms ||= {}
end

.tObject

Helper to point to ‘I18n.t` method



20
21
22
# File 'lib/lifeform/form.rb', line 20

def t(...)
  I18n.t(...)
end

Instance Method Details

#add_authenticity_tokenObject

rubocop:disable Metrics/AbcSize



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/lifeform/form.rb', line 148

def add_authenticity_token # rubocop:disable Metrics/AbcSize
  if helpers.respond_to?(:token_tag, true) # Rails
    helpers.send(:token_tag, nil, form_options: {
                   action: parameters[:action].to_s,
                   method: parameters[:method].to_s.downcase
                 })
  elsif helpers.respond_to?(:csrf_tag, true) # Roda
    helpers.send(:csrf_tag, parameters[:action].to_s, @method.to_s)
  else
    raise Lifeform::Error, "Missing token tag helper. Override `add_authenticity_token' in your Form object"
  end
end

#attributesObject



161
162
163
# File 'lib/lifeform/form.rb', line 161

def attributes
  @attributes ||= self.class.parameters_to_attributes(parameters)
end

#auto_render_fieldsObject



194
195
196
# File 'lib/lifeform/form.rb', line 194

def auto_render_fields
  self.class.fields.map { |k, _v| render(field(k)) }
end

#field(name, **field_parameters) ⇒ Object



165
166
167
168
169
170
171
172
173
# File 'lib/lifeform/form.rb', line 165

def field(name, **field_parameters)
  # @type [FieldDefinition]
  field_definition = self.class.fields[name.to_sym]
  # @type [Class<Libraries::Default>]
  field_library = field_definition.library
  field_library.object_for_field_definition(
    self, field_definition, self.class.parameters_to_attributes(field_parameters)
  )
end

#subform(name, model = nil) ⇒ Object



175
176
177
178
179
180
181
# File 'lib/lifeform/form.rb', line 175

def subform(name, model = nil)
  @subform_instances[name.to_sym] ||= self.class.subforms[name.to_sym][:class].new(
    model,
    emit_form_tag: false,
    parent_name: self.class.subforms[name.to_sym][:parent_name] || self.class.name_of_model(self.model)
  )
end

#template(&block) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity



183
184
185
186
187
188
189
190
191
192
# File 'lib/lifeform/form.rb', line 183

def template(&block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
  form_tag = library::FORM_TAG
  parameters[:action] ||= url || (model ? helpers.send(self.class.const_get(:MODEL_PATH_HELPER), model) : nil)

  send(form_tag, **attributes) do
    unsafe_raw(add_authenticity_token) unless parameters[:method].to_s.downcase == "get"
    unsafe_raw @method_tag&.() || ""
    block ? yield_content(&block) : auto_render_fields
  end
end

#verify_methodObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/lifeform/form.rb', line 132

def verify_method
  return if %w[get post].include?(parameters[:method].to_s.downcase)

  @method_tag = Class.new(Phlex::HTML) do # TODO: break this out into a real component
    def initialize(method:)
      @method = method
    end

    def template
      input type: "hidden", name: "_method", value: @method, autocomplete: "off"
    end
  end.new(method: @parameters[:method].to_s.downcase)

  parameters[:method] = :post
end