Reputation: 1080
I am trying to create a custom Decimal Type using the Rails 5's Attributes API to accepting localized user input. It looks like below:
class Decimal < ActiveRecord::Type::Decimal
def cast(value)
return unless value
cast_value(value.is_a?(String) ? parse_from_string(value) : value)
end
def changed_in_place?(raw_old_value, new_value)
raw_old_value != serialize(new_value)
end
def parse_from_string(value)
delimiter = I18n.t('number.format.delimiter')
separator = I18n.t('number.format.separator')
value.gsub(delimiter, '_').gsub(separator, '.')
end
end
I also have a custom form builder to show a formatted value to the user. When submitting the form to create resources (models entities), it works fine. However, when submitting the form to update resources, the validates_numericality_of
validator marks my custom attribute as invalid (not_a_number). After some research in active model's source code, I reached this piece of code in NumericalityValidator
.
But I don't understand what I could change to make this works. Any ideas?!
Upvotes: 2
Views: 224
Reputation: 1080
I made it work by changing my custom Decimal type.
class Decimal < ActiveRecord::Type::Decimal
def cast(value)
return unless value
if value.is_a?(String)
if numeric_string?(value)
value = value.to_s.to_numeric
else
return value
end
end
cast_value(value)
end
def value_constructed_by_mass_assignment?(value)
if value.is_a?(String)
numeric_string?(value)
else
super
end
end
def numeric_string?(value)
number = value.to_s.gsub(/[.,]/, '.' => '', ',' => '.')
/\A[-+]?\d+/.match?(number)
end
end
Upvotes: 1
Reputation: 15838
The validator uses a variable called raw_value
. It tries to get that raw value from your object, check the lines 35 to 38.
I guess you can define a method on your model using your attribute's name with "_before_type_cast" to return a numeric value that the validator can use.
If your attribute is called, lets say, amount
, you can do:
def amount_before_type_cast
amount.to_number
end
Then you'll have to define a method on your custom type to turn it into a number, maybe something like:
def to_number
value.gsub(/\D/,'').to_i #remove all non-digit and turn it into an integer
end
Upvotes: 1