使用虚拟属性干掉此模型

时间:2012-12-12 20:43:30

标签: ruby-on-rails

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

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,但我不知道如何抽象它以重用它 - 我将使用相同的转换/验证了几十个输入。

理想情况下,我想使用像accept_mixed :chest, :wingspan ...这样的东西,它会照顾自定义的getter,setter,验证等。

编辑:

我正在尝试使用元编程重新创建功能,但我在一些地方苦苦挣扎:

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

1 个答案:

答案 0 :(得分:1)

您需要custom validator

这样的东西
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

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

现在你只需要进行一些元编程来完成所有工作,就像我在你的另一个问题上提出in my answer一样。你基本上想要像

这样的东西
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