在将数字输入Ruby on Rails表单时,小数点和逗号的使用

18

如何在Ruby/Rails中允许用户在表单中输入小数点或逗号?换句话说,我希望用户能够输入2,000.99而不是在我的数据库中得到2.00。

这方面是否有最佳实践?

gsub是否可以用于浮点数或大整数?或者当将浮点数或整数输入表单时,Rails会自动在逗号处截断数字吗?我尝试使用self.price.gsub(",", "")但得到"undefined method `gsub' for 8:Fixnum",其中8是我在表单中输入的任何数字。

8个回答

27

我曾遇到一个类似的问题,试图在表单中使用本地化内容。使用内置方法ActionView::Helpers::NumberHelper来本地化输出是相对简单的,但是解析本地化输入不受ActiveRecord支持。

这是我的解决方案,请告诉我如果有什么错误。对我来说,这个解决方案过于简单,是否正确不确定。谢谢!:)

首先,让我们给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

那么我们需要在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

最后,让我们声明哪些字段应该具有本地化输入。
class Article < ActiveRecord::Base
  attr_localized :price
end

现在,在您的表单中,您可以输入"1.936,27",ActiveRecord不会在无效数字上引发错误,因为它会变成1936.27。


我见过的最佳解决方案 - riley
2
我喜欢意大利里拉兑欧元的汇率引用... :-) - Giuseppe
太好了,我会将你的代码添加到一个宝石中,非常有用! - svelandiag
ActiveRecord::Base和String类应该保存在哪里? 更新:明白了,https://dev59.com/zG035IYBdhLWcg3wC7if#5654580 - Ken Ratanachai S.
1
您可以将它们保存在 config/initializers 目录下。 - panteo

10

这是我几年前从 Greg Brown(Ruby Best Practices 的作者)那里复制的一些代码。在您的模型中,您可以确定哪些项是“人性化”的。

class LineItem < ActiveRecord::Base
  humanized_integer_accessor :quantity
  humanized_money_accessor :price
end
在你的视图模板中,你需要引用已经人性化处理过的字段:
= form_for @line_item do |f|
  Price:
  = f.text_field :price_humanized

这是由以下内容驱动的:

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

这里所提到的 with_commas 方法是什么? - Tinynumbers
这是我从应用程序中提取的自定义扩展 - 我忘记它没有包含在Rails中:class String def with_commas self.reverse.gsub(/\d{3}/,"\\&,").reverse.sub(/^,/,"") end class Numeric def with_commas to_s.with_commas end end - Jeremy Weathers

2
你可以尝试在 before_validation 或 before_save 中去除逗号。哎呀,你想在文本字段被转换之前这样做。你可以使用虚拟属性:
def price=(price)
   price = price.gsub(",", "")
   self[:price] = price  # or perhaps price.to_f
end

这是一个无限循环。self.price = price 会调用 price=。我建议改为使用 self[:price] = price - epochwolf

0

看看i18n_alchemy gem,用于日期和数字解析以及本地化。

I18nAlchemy旨在处理基于当前I18n区域设置格式的日期、时间和数字解析。主要思想是让ORM(例如ActiveRecord)自动接受以当前语言环境格式给出的日期/数字,并将这些值本地化。


0

我在我的项目中编写了以下代码。这解决了我所有的问题。

config/initializers/decimal_with_comma.rb

# frozen_string_literal: true

module ActiveRecord
  module Type
    class Decimal
      private

      alias_method :cast_value_without_comma_separator, :cast_value

      def cast_value(value)
        value = value.gsub(',', '') if value.is_a?(::String)
        cast_value_without_comma_separator(value)
      end
    end

    class Float
      private

      alias_method :cast_value_without_comma_separator, :cast_value

      def cast_value(value)
        value = value.gsub(',', '') if value.is_a?(::String)
        cast_value_without_comma_separator(value)
      end
    end

    class Integer
      private

      alias_method :cast_value_without_comma_separator, :cast_value

      def cast_value(value)
        value = value.gsub(',', '') if value.is_a?(::String)
        cast_value_without_comma_separator(value)
      end
    end
  end
end

module ActiveModel
  module Validations
    class NumericalityValidator
      protected

        def parse_raw_value_as_a_number(raw_value)
          raw_value = raw_value.gsub(',', '') if raw_value.is_a?(::String)
          Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
        end
    end
  end
end

-1

这里有一个简单的方法,可以确保数字输入被正确读取。输出仍然会带有点而不是逗号。虽然这不太美观,但在某些情况下至少不是关键问题。

在您想要启用逗号分隔符的控制器中,只需要调用一个方法即可。也许在MVC方面不是完美的,但相当简单,例如:

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

这个想法是修改参数字符串,例如:

class ApplicationController < ActionController::Base

  def allow_comma(number_string)
    number_string.sub!(".", "").sub!(",", ".")
  end

end

-1

我无法实现之前提出的def price=(price)虚拟属性建议,因为该方法似乎会递归调用自身。

最终,我从属性哈希中移除了逗号,因为正如您所怀疑的那样,ActiveRecord似乎会将插入DECIMAL字段的逗号截断。

在我的模型中:

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

-1
你可以尝试这个:
def price=(val)
  val = val.gsub(',', '')
  super
end

请添加一些文字来解释为什么这是答案和/或为什么它有效。 - AaA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接