Handling international currency input in Ruby on Rails

你说的曾经没有我的故事 提交于 2019-12-03 06:01:29

问题


I have an application that handles currency inputs. However, if you're in the US, you might enter a number as 12,345.67; in France, it might be 12.345,67.

Is there an easy way, in Rails, to adapt the currency entry to a locale?

Note that I'm not looking for display of the currency (ala number_to_currency), I'm looking to deal with someone typing in a currency string, and converting it into a decimal.


回答1:


You could give this a shot:

   def string_to_float(string)

      string.gsub!(/[^\d.,]/,'')          # Replace all Currency Symbols, Letters and -- from the string

      if string =~ /^.*[\.,]\d{1}$/       # If string ends in a single digit (e.g. ,2)
        string = string + "0"             # make it ,20 in order for the result to be in "cents"
      end

      unless string =~ /^.*[\.,]\d{2}$/   # If does not end in ,00 / .00 then
        string = string + "00"            # add trailing 00 to turn it into cents
      end

      string.gsub!(/[\.,]/,'')            # Replace all (.) and (,) so the string result becomes in "cents"  
      string.to_f / 100                   # Let to_float do the rest
   end

And the test Cases:

describe Currency do
  it "should mix and match" do
    Currency.string_to_float("$ 1,000.50").should eql(1000.50)
    Currency.string_to_float("€ 1.000,50").should eql(1000.50)
    Currency.string_to_float("€ 1.000,--").should eql(1000.to_f)
    Currency.string_to_float("$ 1,000.--").should eql(1000.to_f)    
  end     

  it "should strip the € sign" do
    Currency.string_to_float("€1").should eql(1.to_f)
  end

  it "should strip the $ sign" do
    Currency.string_to_float("$1").should eql(1.to_f)
  end

  it "should strip letter characters" do
    Currency.string_to_float("a123bc2").should eql(1232.to_f)
  end

  it "should strip - and --" do
    Currency.string_to_float("100,-").should eql(100.to_f)
    Currency.string_to_float("100,--").should eql(100.to_f)
  end

  it "should convert the , as delimitor to a ." do
    Currency.string_to_float("100,10").should eql(100.10)
  end

  it "should convert ignore , and . as separators" do
    Currency.string_to_float("1.000,10").should eql(1000.10)
    Currency.string_to_float("1,000.10").should eql(1000.10)
  end

  it "should be generous if you make a type in the last '0' digit" do
    Currency.string_to_float("123,2").should eql(123.2)
  end



回答2:


We wrote this:

class String
  def safe_parse
    self.gsub(I18n.t("number.currency.format.unit"), '').gsub(I18n.t("number.currency.format.delimiter"), '').gsub(I18n.t("number.currency.format.separator"), '.').to_f
  end
end

Of course, you will have to set the I18n.locale before using this. And it currently only converts the string to a float for the locale that was set. (In our case, if the user is on the french site, we expect the currency amount text to only have symbols and formatting pertaining to the french locale).




回答3:


You need to clean the input so that users can type pretty much whatever they want to, and you'll get something consistent to store in your database. Assuming your model is called "DoughEntry" and your attribute is "amount," and it is stored as an integer.

Here's a method that converts a string input to cents (if the string ends in two digits following a delimeter, it's assumed to be cents). You may wish to make this smarter, but here's the concept:

def convert_to_cents(input)
  if input =~ /^.*[\.,]\d{2}$/ 
    input.gsub(/[^\d-]/,'').to_i
  else
    "#{input.gsub(/[^\d-]/,'')}00".to_i
  end
end

>> convert_to_cents "12,345"
=> 1234500
>> convert_to_cents "12.345,67"
=> 1234567
>> convert_to_cents "$12.345,67"
=> 1234567

Then overwrite the default "amount" accessor, passing it through that method:

class DoughEntry << ActiveRecord::Base

  def amount=(input)
    write_attribute(:amount, convert_to_cents(input))
  end

protected
  def convert_to_cents(input)
    if input =~ /^.*[\.,]\d{2}$/ 
      input.gsub(/[^\d-]/,'').to_i
    else
      "#{input.gsub(/[^\d-]/,'')}00".to_i
    end
  end
end

Now you're storing cents in the database. Radar has the right idea for pulling it back out.




回答4:


Tim,

You can try to use the 'aggregation' feature, combined with a delegation class. I would do something like:

class Product
  composed_of :balance, 
         :class_name => "Money", 
         :mapping => %w(amount)
end

class Money < SimpleDelegator.new
   include Comparable
   attr_reader :amount

   def initialize(amount)
     @amount = Money.special_transform(amount)
     super(@amount)
   end

   def self.special_transform(amount)
     # your special convesion function here
   end

   def to_s
     nummber_to_currency @amount
   end
 end

In this way, you will be able to directly assign:

Product.update_attributes(:price => '12.244,6')

or

Product.update_attributes(:price => '12,244.6')

The advantage is that you do not have to modify anything on controllers/views.




回答5:


Using the translations for numbers in the built-in I18n should allow you to enter your prices in one format (1234.56) and then using I18n bringing them back out with number_to_currency to have them automatically printed out in the correct locale.

Of course you'll have to set I18n.locale using a before_filter, check out the I18n guide, section 2.3.



来源:https://stackoverflow.com/questions/1669214/handling-international-currency-input-in-ruby-on-rails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!