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
Public Class Methods
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
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
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
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
# File lib/formant.rb, line 180 def as_sym(field_name) field = "@#{field_name}".to_sym end
# 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
# File lib/formant.rb, line 148 def format_datetime(field_value, options={}) I18n.l(field_value, options).squish if field_value end
# File lib/formant.rb, line 160 def format_number_with_delimiter(field_value, options={}) ActionView::Base.new.number_with_delimiter(field_value, options) end
# File lib/formant.rb, line 152 def format_phone_number(field_value, options={}) field_value.phony_formatted(options) if field_value end
# File lib/formant.rb, line 172 def get_field(field_name) instance_variable_get(as_sym(field_name)) end
# 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
# File lib/formant.rb, line 124 def parse_datetime(field_value, options={}) Time.zone.parse(field_value || '') end
# 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
# 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
# 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
# File lib/formant.rb, line 176 def set_field(field_name, value) instance_variable_set(as_sym(field_name), value) end