6

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

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

这可以正常工作,但是,如果您添加、删除或更改关联的Lines,那么这不会被阻止。

在我的Line模型中,我有以下验证:

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

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


所以...简而言之,我如何验证与订单关联的没有更改,也没有添加或删除?

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

4

3 回答 3

5

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

  • before_add
  • after_add
  • before_remove
  • after_remove

也来自相同的文档:

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

也许您可以添加一个回调方法,以before_add确保before_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

另一件值得尝试的事情是添加验证错误并falsevalidate_editable_lines!验证测试失败时返回。如果可行,我当然建议将方法名称更改为validate_editable_lines(sans !bang)。:)

于 2013-09-11T17:09:03.863 回答
1

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

于 2013-09-11T16:51:18.013 回答
0

This is an interesting problem, and to the best of my knowledge slightly tricky to solve.

Here is one approach: http://anti-pattern.com/dirty-associations-with-activerecord

Another approach which I think is slightly cleaner would be to simply check at the controller level before you add/remove a Line, and not to use validations.

Yet another approach is you can add before_create and before_destroy callbacks to Line, and check if the Order instance has been completed.

于 2013-09-11T16:42:43.443 回答