class Formant::FormObject

Base class for Form objects.

See www.reinteractive.net/posts/158-form-objects-in-rails (among others) for more info on form objects for more details.

This is a simplified implementation of form objects that focuses on collecting input in a PORO, specifying any special parsing and transformation on fields upon input (i.e., before validation), and special formatting/transformation on fields for display/output and redisplay of forms.

Attributes

format_fields[RW]
parse_fields[RW]

Public Class Methods

parse(field_name, options={}) click to toggle source

Directive to add a parse rule for the specified attribute. Parse rules let you apply any special parsing/transformation logic on the form’s attributes upon input. The parsing rules are applied automatically prior to validation.

Usage example:

class MyForm < FormObject

...
parse :appointment_time, as: :datetime
parse :phone, as: :phone_number
parse :price, as: :currency
parse :email, to: :strip_whitespace
...

end

The above example specifies that the appointment_time attribute should be parsed with the parse_datetime method (which converts the datetime string into an ActiveSupport::TimeWithZone object), and that the # phone attribute should be parsed with the parse_phone_number method (which normalizes the phone number into a standard format).

# File lib/formant.rb, line 59
def self.parse(field_name, options={})
  self.parse_fields ||= []
  parse_type = options[:as] || options[:to]
  raise "no parse type provided" if parse_type.blank?
  self.parse_fields << [ field_name, "parse_#{parse_type}", options ]
end
reformat(field_name, options={}) click to toggle source

Directive to add a reformat rule for the specified attribute. These let you apply any special formatting/normalization logic on the form’s attributes upon output. Typically you want to do this when you have to redisplay a form.

The format rules are triggered when the FormObject#reformatted! method is invoked, which modifies the attribute in place.

Usage example:

class MyForm < FormObject

...
reformat :appointment_time, as: :datetime, format: :day_date_time
reformat :phone, as: :phone_number, country_code: 'US'
...

end

The above example specifies that the appointment_time attribute should be reformatted with the format_datetime method (using the format specified in the locale file with the :day_date_time key), and that the phone attribute should be parsed with the parse_phone_number method, and a fixed country_code of ‘US’

# File lib/formant.rb, line 90
def self.reformat(field_name, options={})
  self.format_fields ||= []
  self.format_fields << [ field_name, "format_#{options[:as]}", options ]
end

Public Instance Methods

reformatted!() click to toggle source

Triger any formatting rules specified with the reformat directive. The attributes are reformatted and mutated in place.

Returns an instance of the form object.

# File lib/formant.rb, line 101
def reformatted!
  self.class.format_fields.each do |field_name, format_method, options|
    formatted_value = send(format_method, get_field(field_name), options)
    set_field(field_name, formatted_value)
  end
  self
end
to_params() click to toggle source

Return all the attributes as a params hash.

# File lib/formant.rb, line 112
def to_params
  attrs = Hash.new
  instance_variables.each do |ivar|
    name = ivar[1..-1]
    attrs[name.to_sym] = instance_variable_get(ivar) if respond_to? "#{name}="
  end
  attrs
end

Private Instance Methods

as_sym(field_name) click to toggle source
# File lib/formant.rb, line 180
def as_sym(field_name)
  field = "@#{field_name}".to_sym
end
format_currency(field_value, options={}) click to toggle source
# File lib/formant.rb, line 156
def format_currency(field_value, options={})
  ActionView::Base.new.number_to_currency(field_value, options) if field_value
end
format_datetime(field_value, options={}) click to toggle source
# File lib/formant.rb, line 148
def format_datetime(field_value, options={})
  I18n.l(field_value, options).squish if field_value
end
format_number_with_delimiter(field_value, options={}) click to toggle source
# File lib/formant.rb, line 160
def format_number_with_delimiter(field_value, options={})
  ActionView::Base.new.number_with_delimiter(field_value, options)
end
format_phone_number(field_value, options={}) click to toggle source
# File lib/formant.rb, line 152
def format_phone_number(field_value, options={})
  field_value.phony_formatted(options) if field_value
end
get_field(field_name) click to toggle source
# File lib/formant.rb, line 172
def get_field(field_name)
  instance_variable_get(as_sym(field_name))
end
parse_currency(field_value, options={}) click to toggle source
# File lib/formant.rb, line 133
def parse_currency(field_value, options={})
  if field_value.is_a?(String)
    field_value.gsub(/[^0-9\.]+/, '').to_d
  elsif field_value.is_a?(Numeric)
    field_value.to_d
  else
    field_value
  end
end
parse_datetime(field_value, options={}) click to toggle source
# File lib/formant.rb, line 124
def parse_datetime(field_value, options={})
  Time.zone.parse(field_value || '')
end
parse_fields!() click to toggle source
# File lib/formant.rb, line 164
def parse_fields!
  self.class.parse_fields.each do |field_name, parse_method, options|
    parsed_value = send(parse_method, get_field(field_name), options)
    set_field(field_name, parsed_value)
  end
  true
end
parse_phone_number(field_value, options={}) click to toggle source
# File lib/formant.rb, line 128
def parse_phone_number(field_value, options={})
  cc = options[:country_code] || 'US'
  PhonyRails.normalize_number(field_value, country_code: cc)
end
parse_strip_whitespace(field_value, options={}) click to toggle source
# File lib/formant.rb, line 143
def parse_strip_whitespace(field_value, options={})
  return field_value unless field_value.is_a?(String)
  options[:squish] ? field_value.squish : field_value.strip if field_value
end
set_field(field_name, value) click to toggle source
# File lib/formant.rb, line 176
def set_field(field_name, value)
  instance_variable_set(as_sym(field_name), value)
end