Decimals, rails, and "," as a decimal separator

This will be completely technical post. I want just to leave somewhere a solution for a problem which I had to solve some time ago. I hope that there is some gem to do that. But the only one I found, which could fit my needs, is Delocalize, but it do some stuff on my models - I wanted to keep my models intact.

The problem is, that rails interpret numbers in format “1.23”, but in many countries we use “1,23”, and we have only ‘,’ on numpad, so if users have to type a lot of numbers in forms (usecase in my application), it’s obviously a pain.

params[:foo] = DecimalSupport.parse_model_decimals(params[:foo], Foo)
module DecimalSupport
def self.parse_model_decimals(hash, model, options = {})
defaults = I18n.translate(:'number.format', locale: options[:locale], default: {})
precision_defaults = I18n.translate(:'number.precision.format', locale: options[:locale], default: {})
defaults = defaults.merge(precision_defaults)
options = options.reverse_merge(defaults)

hash.each do |field, value|
column = model.columns.find {|c| c.name == field.to_s}

hash[field] = if column.type == :decimal
self.parse_decimal(value, options)
else
value
end
end
hash
end

def self.parse_decimal(value, options = {})
if value.blank?
"0.0"
else
separator_position = value.rindex(options[:separator])

if separator_position.present?
int_part = value[0...separator_position]
frac_part = value[(separator_position+1)..-1]

int_part.gsub!(options[:delimiter], '')
[int_part, frac_part].join(".")
else
value
end
end
end
end
ActionView::Helpers::FormBuilder.class_eval do
include ActionView::Helpers::NumberHelper

def decimal_number_field(field, options = {})
value = object.send(field)
column = object.class.columns.find {|c| c.name == field.to_s}

new_options = { raise: true }
new_options[:precision] = column.try(:scale)
new_options[:separator] = options.delete(:separator)
new_options[:delimiter] = options.delete(:delimiter)
new_options[:significant] = options.delete(:significant)
new_options[:strip_insignificant_zeros] = options.delete(:strip_insignificant_zeros)
new_options[:raise] = true

new_options.delete_if {|k,v| v.nil? }

options[:value] = number_with_precision(value, new_options)
options[:class] = "#{options[:class]} decimal_number_field"

text_field(field, options)
end
end

In first gist, we have usage example in controller. In the second, an implementation of these method. It can probably be done in cleaner & easier way. Third file shows an implementation of decimal_number_field, which I use in my views.

The other thing I want to mention is that I have to always truncate my decimals when saving, instead of rounding them if they are longer, than defined “scale”. I found out that “The precise behavior is operating system-specific, but generally the effect is truncation to the permissible number of digits.” (from Stack Overflow), but unfortunately, on my system default behaviour was rounding a number, so I decided to create an observer.

def before_save(model)
model.class.columns.each do |column|
if column.type == :decimal
int_part, frac_part = model.send(column.name).to_s.split(".")
new_value = [int_part, frac_part[0...column.scale]].join(".")
model.send("#{column.name}=", new_value)
end
end
end

And that’s all. It’s my first english post and I hope it isn’t as horrible as I think it is.