处理混合数字 – 在模型中?

我有一个包含大量字段的表单,我将其作为小数存储在我的数据库中。

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

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

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

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

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

通过使用puts self.chest我能够告诉我在将有机会调整完成之前将值转换为数字。 在我的表单输入’10 1/2’并保存, puts self.chest给我10.0,即使该函数只是:

 def to_dec puts self.chest end 

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

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

我被卡住了。

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

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

执行此操作时,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它!