Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decimals and commas when entering a number into a Ruby on Rails form

What's the best Ruby/Rails way to allow users to use decimals or commas when entering a number into a form? In other words, I would like the user be able to enter 2,000.99 and not get 2.00 in my database.

Is there a best practice for this?

-- Update ---

Does gsub work with floats or bigintegers? Or does rails automatically cut the number off at the , when entering floats or ints into a form? I tried using self.price.gsub(",", "") but get "undefined method `gsub' for 8:Fixnum" where 8 is whatever number I entered in the form.

like image 794
James Avatar asked Jun 30 '11 21:06

James


2 Answers

I had a similar problem trying to use localized content inside forms. Localizing output is relatively simple using ActionView::Helpers::NumberHelper built-in methods, but parsing localized input it is not supported by ActiveRecord.

This is my solution, please, tell me if I'm doing anything wrong. It seems to me too simple to be the right solution. Thanks! :)

First of all, let's add a method to String.

class String
  def to_delocalized_decimal
    delimiter = I18n::t('number.format.delimiter')
    separator = I18n::t('number.format.separator')
    self.gsub(/[#{delimiter}#{separator}]/, delimiter => '', separator => '.')
  end
end

Then let's add a class method to ActiveRecord::Base

class ActiveRecord::Base
  def self.attr_localized(*fields)
    fields.each do |field|
      define_method("#{field}=") do |value|
        self[field] = value.is_a?(String) ? value.to_delocalized_decimal : value
      end
    end
  end
end

Finally, let's declare what fields should have an input localized.

class Article < ActiveRecord::Base
  attr_localized :price
end

Now, in your form you can enter "1.936,27" and ActiveRecord will not raise errors on invalid number, because it becomes 1936.27.

like image 138
panteo Avatar answered Sep 19 '22 22:09

panteo


Here's some code I copied from Greg Brown (author of Ruby Best Practices) a few years back. In your model, you identify which items are "humanized".

class LineItem < ActiveRecord::Base
  humanized_integer_accessor :quantity
  humanized_money_accessor :price
end

In your view templates, you need to reference the humanized fields:

= form_for @line_item do |f|
  Price:
  = f.text_field :price_humanized

This is driven by the following:

class ActiveRecord::Base
  def self.humanized_integer_accessor(*fields)
    fields.each do |f|
      define_method("#{f}_humanized") do
        val = read_attribute(f)
        val ? val.to_i.with_commas : nil
      end
      define_method("#{f}_humanized=") do |e|
        write_attribute(f,e.to_s.delete(","))
      end
    end
  end

  def self.humanized_float_accessor(*fields)
    fields.each do |f|
      define_method("#{f}_humanized") do
        val = read_attribute(f)
        val ? val.to_f.with_commas : nil
      end
      define_method("#{f}_humanized=") do |e|
        write_attribute(f,e.to_s.delete(","))
      end
    end
  end

  def self.humanized_money_accessor(*fields)
    fields.each do |f|
      define_method("#{f}_humanized") do
        val = read_attribute(f)
        val ? ("$" + val.to_f.with_commas) : nil
      end
      define_method("#{f}_humanized=") do |e|
        write_attribute(f,e.to_s.delete(",$"))
      end
    end
  end
end
like image 25
Jeremy Weathers Avatar answered Sep 20 '22 22:09

Jeremy Weathers