Class: Readymade::Form

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Model
Defined in:
lib/readymade/form.rb

Direct Known Subclasses

InstantForm

Defined Under Namespace

Classes: FormOptions

Constant Summary collapse

PERMITTED_ATTRIBUTES =
[].freeze
REQUIRED_ATTRIBUTES =
[].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params, **args) ⇒ Form

Returns a new instance of Form.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/readymade/form.rb', line 14

def initialize(params, **args)
  @params = params
  @record = args[:record]
  @args = args
  @nested_forms = []
  @required_attributes = Array(args[:required]).presence
  @permitted_attributes = (Array(@required_attributes) + Array(args[:permitted])).presence

  parse_datetime_params

  # Slice all attributes which is not required by form
  # to omit save of unpredictable params
  @params&.slice!(*permitted_attributes) # if permitted_attributes.present?

  # dynamically creates attr accessors
  @permitted_attributes&.each do |key|
    singleton_class.class_eval do
      attr_accessor key
    end
  end

  # automatically validates all REQUIRED_ATTRIBUTES
  singleton_class.validates(*required_attributes, presence: true) if required_attributes.present?

  build_nested_forms

  super(@params)
end

Instance Attribute Details

#argsObject

Returns the value of attribute args.



9
10
11
# File 'lib/readymade/form.rb', line 9

def args
  @args
end

#nested_formsObject (readonly)

list nested_forms in child form in order to validate them



150
151
152
# File 'lib/readymade/form.rb', line 150

def nested_forms
  @nested_forms
end

#paramsObject

Returns the value of attribute params.



9
10
11
# File 'lib/readymade/form.rb', line 9

def params
  @params
end

#recordObject

Returns the value of attribute record.



9
10
11
# File 'lib/readymade/form.rb', line 9

def record
  @record
end

Instance Method Details

#build_nested_formsObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/readymade/form.rb', line 53

def build_nested_forms
  nested_forms_mapping.each do |attr, form_class|
    next if params[attr].blank?

    if form_class.is_a?(Array)
      n_forms = if params[attr].is_a?(Hash)
                  # { 0 => {id: 1, name: 'my name'}, 1 => { id: 2, name: 'other name' }}
                  params[attr].map { |_i, attrs| form_class[0].new(attrs) }
                else
                  # [{id: 1, name: 'my name'}, { id: 2, name: 'other name' }]
                  params[attr].map { |attrs| form_class[0].new(attrs) }
                end
      @nested_forms.push(*n_forms)
      define_singleton_method("#{attr}_forms") { n_forms }
    else
      @nested_forms.push(f = form_class.new(params[attr]))
      define_singleton_method("#{attr}_form") { f }
    end
  end
end

#datetime_paramsObject

list datetime_params in child form in order to parse datetime properly



145
146
147
# File 'lib/readymade/form.rb', line 145

def datetime_params
  []
end

#humanized_nameObject



121
122
123
# File 'lib/readymade/form.rb', line 121

def humanized_name
  self.class.name.underscore.split('/')[0]
end

#nested_forms_mappingObject

define nested forms in format { attr_name: MyFormClass } use the following syntax if attribute is a collection: { attr_collection_name: [MyFormClass] }



154
155
156
# File 'lib/readymade/form.rb', line 154

def nested_forms_mapping
  {}
end

#parse_datetime_paramsObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/readymade/form.rb', line 127

def parse_datetime_params
  datetime_params.each do |param|
    next if @params[param].present?

    # set datetime to nil if year is blank
    if @params["#{param}(1i)"].blank?
      @params[param] = nil

      next
    end

    @params[param] = DateTime.new(*(1..5).map { |i| @params["#{param}(#{i}i)"].to_i })
  end
rescue ArgumentError
  nil
end

#permitted_attributesObject



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

def permitted_attributes
  @permitted_attributes ||= self.class::PERMITTED_ATTRIBUTES
end

#required_attributesObject



47
48
49
50
51
# File 'lib/readymade/form.rb', line 47

def required_attributes
  return [] if params.try(:[], :_destroy).present?

  @required_attributes ||= self.class::REQUIRED_ATTRIBUTES
end

#sync_errors(from: self, to: record) ⇒ Object

sync errors from form to record or vice-versa



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/readymade/form.rb', line 102

def sync_errors(from: self, to: record)
  return if [from, to].any?(&:blank?)

  if rails_errors_v2?
    from.errors.messages.each do |key, values|
      Array.wrap(values).uniq.each do |uv|
        to.errors.add(key, uv)
      end
    end
  else
    errors = from.errors.instance_variable_get('@messages').to_h
    errors.merge!(to.errors.instance_variable_get('@messages').to_h)

    to.errors.instance_variable_set('@messages', errors)
    to.errors.messages.transform_values!(&:uniq) # Does not work with rails 6.1
  end
rescue FrozenError => _e
end

#sync_nested_errors(nested_forms) ⇒ Object

copy errors from nested forms into parent form



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/readymade/form.rb', line 83

def sync_nested_errors(nested_forms)
  if rails_errors_v2?
    nested_forms.each do |n_form|
      n_form.errors.each do |code|
        errors.add("#{n_form.humanized_name}.#{code.attribute}", code.message)
      end
    end
  else
    nested_forms.each do |n_form|
      n_form.errors.each do |code, text|
        errors.add("#{n_form.humanized_name}.#{code}", text)
      end
    end
  end

  false
end

#validateObject



74
75
76
# File 'lib/readymade/form.rb', line 74

def validate
  super && validate_nested(*nested_forms)
end

#validate_nested(*nested_forms) ⇒ Object



78
79
80
# File 'lib/readymade/form.rb', line 78

def validate_nested(*nested_forms)
  nested_forms.compact.map(&:validate).all? || sync_nested_errors(nested_forms)
end