引发属性访问器覆盖中的错误?

时间:2012-10-08 23:42:50

标签: ruby-on-rails ruby-on-rails-3 attr

我正在覆盖ActiveRecord中的属性访问器,以将格式为“hh:mm:ss”的字符串转换为秒。这是我的代码:

class Call < ActiveRecord::Base
  attr_accessible :duration

  def duration=(val)
    begin
      result = val.to_s.split(/:/)
             .map { |t| Integer(t) }
             .reverse
             .zip([60**0, 60**1, 60**2])
             .map { |i,j| i*j }
             .inject(:+)
    rescue ArgumentError
      #TODO: How can I correctly report this error?
      errors.add(:duration, "Duration #{val} is not valid.")
    end
    write_attribute(:duration, result)
  end

  validates :duration, :presence => true,
                       :numericality => { :greater_than_or_equal_to => 0 }

  validate :duration_string_valid

  def duration_string_valid
    if !duration.is_valid? and duration_before_type_cast
      errors.add(:duration, "Duration #{duration_before_type_cast} is not valid.")
    end
  end
end

我正在尝试在验证期间有意义地报告此错误。我所拥有的前两个想法包含在代码示例中。

  1. 添加访问者覆盖内部的错误 - 有效,但我不确定它是否是一个很好的解决方案。
  2. 使用验证方法duration_string_valid。检查其他验证是否失败,并报告duration_before_type_cast。在这种情况下,duration.is_valid?不是有效的方法,我不确定如何检查持续时间是否已通过其他验证。
  3. 我可以在duration =(val)中设置一个实例变量,并在duration_string_valid内报告。
  4. 我希望得到一些反馈,知道这是否是进行此操作的好方法,以及我如何改进错误报告。

1 个答案:

答案 0 :(得分:5)

首先,清理您的代码。将字符串移动到持续时间转换器到服务层。在lib/目录中创建StringToDurationConverter

# lib/string_to_duration_converter.rb
class StringToDurationConverter
  class << self
    def convert(value)
      value.to_s.split(/:/)
         .map { |t| Integer(t) }
         .reverse
         .zip([60**0, 60**1, 60**2])
         .map { |i,j| i*j }
         .inject(:+)
    end
  end
end

第二次,添加自定义DurationValidator验证程序

# lib/duration_validator.rb
class DurationValidator < ActiveModel::EachValidator
  # implement the method called during validation
  def validate_each(record, attribute, value)
    begin
      StringToDurationConverter.convert(value)
    resque ArgumentError
      record.errors[attribute] << 'is not valid.'
    end
  end
end

你的模型看起来像这样:

class Call < ActiveRecord::Base
  attr_accessible :duration

  validates :duration, :presence => true,
                       :numericality => { :greater_than_or_equal_to => 0 },
                       :duration => true

  def duration=(value)
    result = StringToDurationConverter.convert(value)
    write_attribute(:duration, result)
  end
end