我有一个相当典型的订单模型,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
这可以很好地防止行在直接修改行时被取消行。但是,当您编辑订单并删除行时,验证永远不会运行,因为它已从订单中删除。
所以...简而言之,我如何验证与订单相关联的行不会更改,并且不会添加或删除?
我在想我错过了一些明显的东西。
答案 0 :(得分:4)
在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
另一件值得尝试的事情是在验证测试失败时添加验证错误并在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_destroy
和Line
个回调,并检查Order
实例是否已完成。