0

我有一个包含大量字段的表单,这些字段以小数形式存储在我的数据库中。

但是,我希望用户能够输入分数,特别是混合数字(例如 38 1/2)

这个答案为将混合数字转换为分数提供了一个很好的解决方案,我已经对其进行了修改并将其放入我的模型中的一个函数中:

def to_dec
  self.chest = self.chest.split.map { |r| Rational(r) }.inject(:+).to_f
end

我已经尝试用validate :to_decand来调用它before_save :to_dec。但是,这些都不起作用。

我知道为什么,但不知道如何解决它。

通过使用puts self.chest,在我有机会调整如何完成之前,我可以知道该值正在转换为数字。在我的表单中输入“10 1/2”并保存,puts self.chest即使函数只是:

def to_dec
  puts self.chest
end

另外,puts '38 1/2'.split.map { |r| Rational(r) }.inject(:+).to_d给了我 38.5,所以我知道问题不在于转换。

我已将视图输入类型从数字更改为字符串,但我的模型中似乎仍然有一个数字。

我被困住了。

此外,其他一些字段也需要支持混合数字——我想以某种方式将其抽象出来,这样我就不需要为每个值编写不同的函数。

4

1 回答 1

1

原因是 ActiveRecord 在定义模型属性时会查看您的数据库模式。这就是所有属性出现在模型类上的方式,而无需像在普通的旧 Ruby 对象上那样使用attr_accessor :chest(或def chest和)手动定义它们。def chest=

当它这样做时,ActiveRecord 还会查看列类型,并在分配属性时应用类型转换。因此,因为您的chest列是小数,它会将您给它的任何字符串转换为浮点数。在这样做的过程中,它将获取可以转换的字符串的第一位,并丢弃其余的,这就是你得到38.0.

您可以通过多种方式解决此问题 - 您可以将数字作为字符串存储在数据库中,这可能会节省一些转换(但如果您需要在查询中使用数字表示,则不会那么有用),或者您可以使用虚拟属性

基本上,您需要像这样定义一个 getter 和一个 setter:

def rational_chest
  # I assume this would create a rational from a float
  # and respond to `to_s` in a sane manner...
  Rational(chest) if chest
end

def rational_chest=(value)
  self.chest = parse_rational(value)
end

private

def parse_rational(value)
  return nil if value.blank?
  value.split.map{|r| Rational(r)}.inject(:+).to_f
end

然后rational_chest在您的表单中使用,而不仅仅是chest

由于您需要在许多领域这样做,您可以求助于一些漂亮的元编程:

def self.rational_attribute(attribute)
  define_method("rational_#{attribute}") do
    Rational(send(attribute))
  end

  define_method("rational_#{attribute}=") do |value|
    send "#{attribute}=", value.split.map{|r| Rational(r)}.inject(:+).to_f
  end
end

def self.rational_attributes(*attributes)
  attributes.each {|attribute| rational_attribute attribute}
end

rational_attributes :chest, :waist, :etc

如果您在多个模型中使用它,请将其夹在一个模块中(没有self.s 和extend它!

于 2012-12-10T23:30:04.047 回答