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



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

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
61
62
63
64
65
66
67
68
69
# 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)
      when ActiveRecord::Associations::CollectionProxy, ActiveRecord::Relation, Array
        if attribute_types[key].type == :array
          value
        else
          # This is a sub-form that needs to read the properties directly
          # from the original model. We cannot pass an array here as it
          # would be passed to the form constructor causing an error.
          model
        end
      else
        value
      end
  end

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

  form
end

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



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

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



80
81
82
83
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 80

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.



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

def map_model(_model); end

#persisted?Boolean

Returns:



93
94
95
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 93

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

#to_keyObject



97
98
99
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 97

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



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

def to_model
  self
end

#to_paramObject



108
109
110
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 108

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:



156
157
158
159
160
161
162
163
164
165
166
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 156

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



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'decidim-core/lib/decidim/attribute_object/form.rb', line 116

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