1

这就是我想要做的;我认为这是一个常见问题,但不知何故我找不到任何相关主题......

我有一个具有范围唯一性约束的模型。我决定通过在迁移中的表上定义一个唯一索引来做到这一点,如下所示:

class CreateLossRatios < ActiveRecord::Migration
  def up
    ...
    add_index :loss_ratios, [ :tool_id, :ends_at ], :unique => true
  end

  def down
    ...
  end
end

这使得 ActiveRecord 在尝试保存违反索引唯一性的记录时抛出异常。现在我想让它显示为验证错误。我认为最好的方法是在 LossRatio 模型中捕获 ActiveRecord::RecordNotUnique 并使用有意义的消息填充错误哈希。我已经这样做了:

class LossRatio < ActiveRecord::Base
  belongs_to :tool

  validates :rate, :ends_at, :tool, :presence => true
  validates_numericality_of :rate
  validates_inclusion_of :rate, :in => (0..1)

  %w{ create save }.each do |name|
    %W{ #{name} #{name}! }.each do |method|
      define_method(method) do |*args|
        begin
          super(*args)
        rescue ActiveRecord::RecordNotUnique => ex
          self.errors.add(:ends_at, I18n.t('activerecord.errors.models.loss_ratio.attributes.ends_at.not_unique'))
        end
      end
    end
  end

end

这可行,但似乎有点麻烦。我知道我在这里做出假设(即,如果我添加另一个数据库级别的唯一性约束等会发生什么),但我看不到解决这个问题的方法。处理此类场景时是否有更优雅的解决方案/最佳实践?我能想到的另一种选择是使用rescue_from,但我不想这样做,因为

  • 我不认为这个逻辑属于控制器,我想让它对应用程序逻辑透明
  • 很可能不会有关联的控制器(这些对象将通过另一个模型作为关联单独创建),从我的角度来看,这使得它更加错误。

有没有办法让这个模型从任何实例方法抛出的异常中拯救出来?我试过使用类级别的救援子句,但它什么也没抓住。


另一个问题是我是否仍然应该对ends_at 使用AR 范围验证。即使处理了 RecordNotUnique,对象仍会认为自己有效,并在尝试保存失败后设置其时间戳。它会导致任何不必要的副作用吗?

4

2 回答 2

2

您还应该验证模型中的唯一性(如建议的 gmalette)。这样,您可以在大多数错误到达数据库之前获取它们。虽然它会花费您额外的费用,SELECT但它可以确保您进行实际验证。

有了这些,数据库索引应该只在两个独立进程几乎同时尝试插入冲突数据时解决竞争条件。我通常通过给出错误消息要求用户重试来处理这些错误。

选择性地处理数据库错误并不是一个好主意,因为这本身通常很容易出错。而是尝试在 ruby​​ 层中处理尽可能多的验证,并将数据库层仅用作安全网。

于 2012-04-10T14:25:46.243 回答
1

要解决验证问题,您可以尝试

validates_uniqueness_of :ends_at, :scope => :tool_id

于 2012-04-10T14:17:13.040 回答