Class: MoneyFormatValidator

Inherits:
ActiveModel::EachValidator
  • Object
show all
Defined in:
app/validators/money_format_validator.rb

Overview

Validate fields to look like currency values

NOTE: Currently limited to USD/CAD values.

Options:

  • ‘exclude_cents = true|false` determines if amount can have cents (default false)

  • ‘allow_negative = true|false` determines if amount can be negative (default false)

A blank value is considered valid (use presence validator to check for that)

Examples

validates :amount, money_format: true                 # optional
validates :amount, money_format: { exclude_cents: true, allow_negative: true }
validates :amount, money_format: true, presence: true # required

Constant Summary collapse

MONEY_REGEX =
/\A[-+]?\d+(\.\d{1,2})?\z/

Instance Method Summary collapse

Instance Method Details

#validate_each(record, attr_name, value) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/validators/money_format_validator.rb', line 21

def validate_each(record, attr_name, value)
  if defined?(ActiveRecord::Base) && record.kind_of?(ActiveRecord::Base)
    # need to get the actual value being set because BigDecimal will convert
    # "invalid" to 0.0 making it impossible to validate
    value = record.send("#{attr_name}_before_type_cast".to_sym)
  end
  return if value.blank?

  # Need to first convert BigDecimal to floating point representation of string
  if value.kind_of?(BigDecimal)
    value = value.to_s("F")
  end

  # Finally convert any other types to string and clean off whitespace
  value = value.to_s.strip

  # Regex seems a bit odd to use here (vs converting to BigDeciaml) but we need
  # to check for values that BigDecimal can't represent (e.g., "badvalue") so
  # for now this works.
  record.errors.add(attr_name, :money_format, options) unless value =~ MONEY_REGEX

  # Check if value has cents but shouldn't
  if options[:exclude_cents] && value_has_cents?(value)
    record.errors.add(attr_name, :money_format_has_cents)
  end

  # Check if value is negative but shouldn't
  if value_is_negative?(value)
    record.errors.add(attr_name, :money_format_is_negative) unless options[:allow_negative]
  end
end

#value_has_cents?(value) ⇒ Boolean

Returns:

  • (Boolean)


53
54
55
# File 'app/validators/money_format_validator.rb', line 53

def value_has_cents?(value)
  BigDecimal.new(value.to_i) != BigDecimal.new(value)
end

#value_is_negative?(value) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
# File 'app/validators/money_format_validator.rb', line 57

def value_is_negative?(value)
  value.to_i < 0
end