如何验证has_many关系未更改

时间:2013-09-11 16:08:52

标签: ruby-on-rails validation activerecord

我有一个相当典型的订单模型,has_many

class Order < ActiveRecord::Base
  has_many :lines
  validates_associated :lines

订单完成后,不应该更改任何属性或相关行(尽管您可以将状态更改为未完成)。

  validate do
    if completed_at.nil? == false && completed_at_was.nil? == false
      errors.add(:base, "You can't change once complete")
    end
  end

这样可行,,如果您添加,删除或更改关联的,则不会阻止此操作。

在我的模型中,我进行了以下验证:

validate do
  if order && order.completed_at.nil? == false
    errors.add(:base, "Cannot change once order completed.")
  end
end

这会成功停止正在修改的已完成订单中的行,并阻止将行添加到已完成的订单。

所以我还需要防止线路从已完成的订单中取出。我在 Line 模型中尝试了这个:

validate do
  if order_id_was.nil? == false
    if Order.find(order_id_was).completed_at.nil? == false
      errors.add(:base, "Cannot change once order completed.")
    end
  end
end

这可以很好地防止在直接修改时被取消。但是,当您编辑订单并删除时,验证永远不会运行,因为它已从订单中删除。


所以...简而言之,我如何验证与订单相关联的不会更改,并且不会添加或删除?

我在想我错过了一些明显的东西。

3 个答案:

答案 0 :(得分:4)

ActiveRecord::Associations的“关联回调”部分中,您会看到有几个回调可以添加到has_many定义中:

  • before_add
  • after_add
  • before_remove
  • after_remove

同样来自相同的文档:

  

如果任何before_add回调抛出异常,该对象就不会被添加到集合中。与before_remove回调相同;如果抛出异常,则不会删除该对象。

也许您可以向before_addbefore_remove添加一个回调方法,以确保订单不会被冻结,并在不允许的情况下抛出异常。

has_many :lines,
         before_add:    :validate_editable!,
         before_remove: :validate_editable!

private

def validate_editable_lines!(line)
  # Define the logic of how `editable?` works based on your requirements
  raise ActiveRecord::RecordNotSaved unless editable?(line)
end

另一件值得尝试的事情是在验证测试失败时添加验证错误并在false内返回validate_editable_lines!。如果可行,我建议您将方法名称更改为validate_editable_lines(无!爆炸)。 :)

答案 1 :(得分:1)

可能会向模型添加locked属性,并在订单完成后将locked的值设置为true。 然后,在控制器中添加将在更新操作之前触发的before_filter,以便检查locked标志的值。如果设置为true,则向用户提出错误/通知/无论该行项目无法更改。

答案 2 :(得分:0)

这是一个有趣的问题,据我所知,解决起来有些棘手。

以下是一种方法:http://anti-pattern.com/dirty-associations-with-activerecord

我认为另一种方法是稍微清洁一下,只需在添加/删除Line之前检查控制器级别,而不是使用验证。

另一种方法是,您可以向before_create添加before_destroyLine个回调,并检查Order实例是否已完成。