Handling international currency input in Ruby on Rails

前端 未结 5 1835
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-01 10:06

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

相关标签:
5条回答
  • 2021-02-01 10:30

    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
    
    0 讨论(0)
  • 2021-02-01 10:30

    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).

    0 讨论(0)
  • 2021-02-01 10:35

    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.

    0 讨论(0)
  • 2021-02-01 10:37

    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.

    0 讨论(0)
  • 2021-02-01 10:49

    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.

    0 讨论(0)
提交回复
热议问题