class Readymade::Form

Constants

PERMITTED_ATTRIBUTES
REQUIRED_ATTRIBUTES

Attributes

args[RW]
nested_forms[R]

list nested_forms in child form in order to validate them

params[RW]
record[RW]

Public Class Methods

form_options(**args) click to toggle source

@form = Items::Forms::Create.form_options(company: current_company) f.association :item_category, collection: @form, required: @form.required?(:item_category)

# File lib/readymade/form.rb, line 164
def self.form_options(**args)
  Readymade::Form::FormOptions.new(**args.merge!(form_class: self))
end
new(params, **args) click to toggle source
Calls superclass method
# 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

Public Instance Methods

build_nested_forms() click to toggle source
# File lib/readymade/form.rb, line 51
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_params() click to toggle source

list datetime_params in child form in order to parse datetime properly

# File lib/readymade/form.rb, line 135
def datetime_params
  []
end
humanized_name() click to toggle source
# File lib/readymade/form.rb, line 111
def humanized_name
  self.class.name.underscore.split('/')[0]
end
nested_forms_mapping() click to toggle source

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

# File lib/readymade/form.rb, line 144
def nested_forms_mapping
  {}
end
parse_datetime_params() click to toggle source

uses datetime_params to fix the following issue: stackoverflow.com/questions/5073756/where-is-the-rails-method-that-converts-data-from-datetime-select-into-a-datet

# File lib/readymade/form.rb, line 117
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_attributes() click to toggle source
# File lib/readymade/form.rb, line 43
def permitted_attributes
  @permitted_attributes ||= self.class::PERMITTED_ATTRIBUTES
end
required_attributes() click to toggle source
# File lib/readymade/form.rb, line 47
def required_attributes
  @required_attributes ||= self.class::REQUIRED_ATTRIBUTES
end
sync_errors(from: self, to: record) click to toggle source

sync errors from form to record or vice-versa

# File lib/readymade/form.rb, line 92
def sync_errors(from: self, to: record)
  return if [from, to].any?(&:blank?)

  if Rails.version.to_f > 6.0
    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) click to toggle source

copy errors from nested forms into parent form

# File lib/readymade/form.rb, line 81
def sync_nested_errors(nested_forms)
  nested_forms.each do |n_form|
    n_form.errors.each do |code, text|
      errors.add("#{n_form.humanized_name}.#{code}", text)
    end
  end

  false
end
validate() click to toggle source
Calls superclass method
# File lib/readymade/form.rb, line 72
def validate
  super && validate_nested(*nested_forms)
end
validate_nested(*nested_forms) click to toggle source
# File lib/readymade/form.rb, line 76
def validate_nested(*nested_forms)
  nested_forms.compact.map(&:validate).all? || sync_nested_errors(nested_forms)
end