使用虚拟属性干掉此模型

在我的表单中,我有一个虚拟属性,允许我接受混合数字(例如38 1/2)并将它们转换为小数。 我也有一些validation(我不确定我正在处理这个问题),如果出现爆炸,会抛出错误。

class Client < ActiveRecord::Base attr_accessible :mixed_chest attr_writer :mixed_chest before_save :save_mixed_chest validate :check_mixed_chest def mixed_chest @mixed_chest || chest end def save_mixed_chest if @mixed_chest.present? self.chest = mixed_to_decimal(@mixed_chest) else self.chest = "" end end def check_mixed_chest if @mixed_chest.present? && mixed_to_decimal(@mixed_chest).nil? errors.add :mixed_chest, "Invalid format. Try 38.5 or 38 1/2" end rescue ArgumentError errors.add :mixed_chest, "Invalid format. Try 38.5 or 38 1/2" end private def mixed_to_decimal(value) value.split.map{|r| Rational(r)}.inject(:+).to_d end end 

但是,我想添加另一个列,翼展,它将具有虚拟属性:mixed_wingspan ,但我不知道如何抽象它以重用它 – 我将对几十个输入使用相同的转换/validation。

理想情况下,我想使用像accept_mixed :chest, :wingspan ...这样可以处理定制的getter,setter,validations等。

编辑:

我试图用元编程重新创建function,但我在几个地方都在努力:

 def self.mixed_number(*attributes) attributes.each do |attribute| define_method("mixed_#{attribute}") do "@mixed_#{attribute}" || attribute end end end mixed_number :chest 

这将胸部设为“@mixed_chest”! 我正试图像上面那样得到实例变量@mixed_chest

您将需要一个自定义validation器

就像是

 class MixedNumberValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.present? && MixedNumber.new(value).to_d.nil? record.errors[attribute] << (options[:message] || "Invalid format. Try 38.5 or 38 1/2") end end end 

那你可以做

 validates :chest, mixed_number: true 

请注意,我将mixed_to_decimal内容提取到一个单独的类中

 class MixedNumber def initialize(value) @value = value end def to_s @value end def to_d return nil if @value.blank? @value.split.map{|r| Rational(r)}.inject(:+).to_d rescue ArgumentError nil end end 

并且该定义允许您在save_chest方法中删除if语句。

现在你只需要做一些元编程就能让事情顺利进行,正如我在回答你的另一个问题时所建议的那样 。 你基本上想要的东西

 def self.mixed_number(*attributes) attributes.each do |attribute| define_method("mixed_#{attribute}") do instance_variable_get("@mixed_#{attribute}") || send(attribute) end attr_writer "mixed_#{attribute}" define_method("save_mixed_#{attribute}") do # exercise for the reader ;) end before_save "save_#{attribute}" validates "mixed_#{attribute}", mixed_number: true end end mixed_number :chest, :waist, :etc