Class: Decidim::AttributeObject::Form

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Validations, Model
Defined in:
decidim-core/lib/decidim/attribute_object/form.rb

Overview

This is the main Form class that provides the functionality for all core forms that take in user input from the user interface forms and converts the inputs to expected formats.

This replaces the Rectify::Form classes in Decidim which used to provide similar functionality. The API provided by this class is largely compatible with `Rectify::Form`.

Direct Known Subclasses

Form

Constant Summary

Constants included from TypeMap

TypeMap::Boolean, TypeMap::Decimal

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Model

#[], #[]=, #attributes, #attributes_with_values, #initialize, #to_h

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.


16
17
18
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 16

def context
  @context
end

Class Method Details

.ensure_hash(object) ⇒ Object


76
77
78
79
80
81
82
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 76

def self.ensure_hash(object)
  if object.is_a?(Hash)
    object
  else
    {}
  end
end

.from_model(model) ⇒ Object


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 40

def self.from_model(model)
  form_attributes = attribute_types.keys.each_with_object({}) do |key, attrs|
    next unless model.respond_to?(key)

    value = model.send(key)
    attrs[key] =
      case value
      when ActiveStorage::Attached::One
        value.attachment.try(:blob)
      when ActiveStorage::Attached::Many
        value.attachments.map(&:blob)
      else
        value
      end
  end

  form = new(form_attributes)
  form.map_model(model)

  form
end

.from_params(params, additional_params = {}) ⇒ Object


62
63
64
65
66
67
68
69
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 62

def self.from_params(params, additional_params = {})
  params_hash = hash_from(params)
  mimicked_params = ensure_hash(params_hash[mimicked_model_name])

  attributes_hash = params_hash.merge(mimicked_params).merge(additional_params)

  new(attributes_hash)
end

.hash_from(params) ⇒ Object


71
72
73
74
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 71

def self.hash_from(params)
  params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
  params.with_indifferent_access
end

.infer_model_nameObject


26
27
28
29
30
31
32
33
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 26

def self.infer_model_name
  return :form unless name

  class_name = name.split("::").last
  return :form if class_name == "Form"

  class_name.chomp("Form").underscore.to_sym
end

.mimic(model_name) ⇒ Object


18
19
20
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 18

def self.mimic(model_name)
  @model_name = model_name.to_s.underscore.to_sym
end

.mimicked_model_nameObject


22
23
24
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 22

def self.mimicked_model_name
  @model_name || infer_model_name
end

.model_nameObject

Converts the mimicked name to ActiveModel naming.


36
37
38
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 36

def self.model_name
  ActiveModel::Name.new(self, nil, mimicked_model_name.to_s)
end

Instance Method Details

#map_model(_model) ⇒ Object

Use the map_model method within the form implementations to map any custom form-specific attributes from the model to the form.


105
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 105

def map_model(_model); end

#persisted?Boolean

Returns:


84
85
86
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 84

def persisted?
  id.present? && id.to_i.positive?
end

#to_keyObject


88
89
90
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 88

def to_key
  [id]
end

#to_modelObject

Required for the active model naming to work correctly to form the HTML class attributes for the form elements (e.g. edit_account instead of edit_account_form).


95
96
97
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 95

def to_model
  self
end

#to_paramObject


99
100
101
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 99

def to_param
  id.to_s
end

#valid?(_context = nil) ⇒ Boolean

Although we are running the nested attributes validations through the NestedValidator, we still need to check for the errors in the nested attributes after the main validations are run in case the main validations are adding errors to some of the nested attributes.

An example of such form is the Decidim::Budgets::Admin::ComponentForm which adds errors to the sub-attribute “settings” during its own validations. Because these errors are not added to the main form object, the main form object would be interpreted as valid without checking the sub-attribute validations.

This preserves the backwards compatibility with Rectify::Form which did the validations in this order and fails the main record validation in case one of the nested attributes is not valid. This is needed e.g. for the customized component validations (e.g. Budgets component form).

Returns:


147
148
149
150
151
152
153
154
155
156
157
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 147

def valid?(_context = nil)
  super && self.class.attribute_types.none? do |name, type|
    value = public_send(name)

    if value.is_a?(Decidim::AttributeObject::Model) || (type.respond_to?(:validate_nested?) && type.validate_nested?)
      _value_has_errors?(value)
    else
      false
    end
  end
end

#with_context(new_context) ⇒ Object


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 107

def with_context(new_context)
  @context = if new_context.is_a?(Hash)
               OpenStruct.new(new_context)
             else
               new_context
             end

  attributes.each do |_name, value|
    case value
    when Array
      value.each do |v|
        next unless v.respond_to?(:with_context)

        v.with_context(context)
      end
    else
      next unless value.respond_to?(:with_context)

      value.with_context(context)
    end
  end

  self
end