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



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/lifeform/form.rb', line 124

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 = {}

  self.class.initialize_field_definitions!

  @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)


119
120
121
# File 'lib/lifeform/form.rb', line 119

def emit_form_tag
  @emit_form_tag
end

#libraryClass<Libraries::Default> (readonly)

Returns:



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

def library
  @library
end

#modelObject (readonly)

Returns:

  • (Object)


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

def model
  @model
end

#parametersHash (readonly)

Returns:

  • (Hash)


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

def parameters
  @parameters
end

#parent_nameBoolean (readonly)

Returns:

  • (Boolean)


122
123
124
# File 'lib/lifeform/form.rb', line 122

def parent_name
  @parent_name
end

#urlString (readonly)

Returns:

  • (String)


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

def url
  @url
end

Class Method Details

.configurationObject



23
# File 'lib/lifeform/form.rb', line 23

def configuration = @configuration ||= ActiveSupport::OrderedOptions.new

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



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

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

.fields(&block) ⇒ Hash<Symbol, FieldDefinition>

Parameters:

  • block (Proc, nil)

Returns:



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

def fields(&block)
  @fields ||= {}
  @fields_setup_block = block if block_given?

  @fields
end

.inherited(subclass) ⇒ Object



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

def inherited(subclass)
  super
  subclass.library library
end

.initialize_field_definitions!Object



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

def initialize_field_definitions!
  return unless @fields_setup_block

  @fields_setup_block.(configuration)
end

.library(library_name = nil) ⇒ Object



51
52
53
54
# File 'lib/lifeform/form.rb', line 51

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

.name_of_model(model) ⇒ Object



88
89
90
91
92
93
94
95
96
97
# File 'lib/lifeform/form.rb', line 88

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>)


100
# File 'lib/lifeform/form.rb', line 100

def param_keys = fields.keys

.param_string_keysArray<String>

Returns:

  • (Array<String>)


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

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

.parameters_to_attributes(kwargs) ⇒ Object

Parameters:

  • kwargs (Hash)


72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/lifeform/form.rb', line 72

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



56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/lifeform/form.rb', line 56

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



47
48
49
# File 'lib/lifeform/form.rb', line 47

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

.subformsObject



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

def subforms = @subforms ||= {}

.tObject

Helper to point to ‘I18n.t` method



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

def t(...) = I18n.t(...)

Instance Method Details

#add_authenticity_tokenObject

rubocop:disable Metrics/AbcSize



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/lifeform/form.rb', line 155

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



168
169
170
# File 'lib/lifeform/form.rb', line 168

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

#auto_render_fieldsObject



201
# File 'lib/lifeform/form.rb', line 201

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

#field(name, **field_parameters) ⇒ Object



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

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



182
183
184
185
186
187
188
# File 'lib/lifeform/form.rb', line 182

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



190
191
192
193
194
195
196
197
198
199
# File 'lib/lifeform/form.rb', line 190

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



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/lifeform/form.rb', line 139

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