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

前端 未结 8 764
生来不讨喜
生来不讨喜 2020-12-08 16:19

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

相关标签:
8条回答
  • 2020-12-08 16:45

    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.

    0 讨论(0)
  • 2020-12-08 16:45

    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
    
    0 讨论(0)
  • 2020-12-08 16:50

    Here's something simple that makes sure that number input is read correctly. The output will still be with a point instead of a comma. That's not beautiful, but at least not critical in some cases.

    It requires one method call in the controller where you want to enable the comma delimiter. Maybe not perfect in terms of MVC but pretty simple, e.g.:

    class ProductsController < ApplicationController
    
      def create
        # correct the comma separation:
        allow_comma(params[:product][:gross_price])
    
        @product = Product.new(params[:product])
    
        if @product.save
          redirect_to @product, :notice => 'Product was successfully created.'
        else
          render :action => "new"
        end
      end
    
    end
    

    The idea is to modify the parameter string, e.g.:

    class ApplicationController < ActionController::Base
    
      def allow_comma(number_string)
        number_string.sub!(".", "").sub!(",", ".")
      end
    
    end
    
    0 讨论(0)
  • 2020-12-08 16:55

    I was unable to implement the earlier def price=(price) virtual attribute suggestion because the method seems to call itself recursively.

    I ended up removing the comma from the attributes hash, since as you suspect ActiveRecord seems to truncate input with commas that gets slotted into DECIMAL fields.

    In my model:

    before_validation :remove_comma
    
    def remove_comma
      @attributes["current_balance"].gsub!(',', '')  # current_balance here corresponds to the text field input in the form view
    
      logger.debug "WAS COMMA REMOVED? ==> #{self.current_balance}"
    end
    
    0 讨论(0)
  • 2020-12-08 16:57

    You can try this:

    def price=(val)
      val = val.gsub(',', '')
      super
    end
    
    0 讨论(0)
  • 2020-12-08 17:02

    You can try stripping out the commas before_validation or before_save

    Oops, you want to do that on the text field before it gets converted. You can use a virtual attribute:

    def price=(price)
       price = price.gsub(",", "")
       self[:price] = price  # or perhaps price.to_f
    end
    
    0 讨论(0)
提交回复
热议问题