4

在 Rails 应用程序中,是否可以一次进行一批更改,仅验证更改的最终结果而不是每个单独的步骤?

这是一个有点模糊的问题,所以让我们把它具体化。假设我有一个杂货店应用程序:

class Store < ActiveRecord::Base
  has_many :fruits
end

class Fruit < ActiveRecord::Base
  belongs_to :store
  attr_accessible :kind, :number, :price
  validates_uniqueness_of :kind, :scope => :store_id
end

我已经使用以下数据为数据库播种:

fruits:
 store_id |    kind    | number | price
----------------------------------------
        1 | apple      |     10 | 0.15
        1 | banana     |     80 | 0.02
        1 | cherry     |     25 | 0.50

但我意识到我搞砸了,交换了香蕉和樱桃的数量和价格。我不能这样做:

store = Store.find(1)
old_bananas = store.fruits.where("kind = banana").first
old_cherries = store.fruits.where("kind = cherry").first
old_bananas.kind = "cherry"
old_cherries.kind = "banana"
old_bananas.save! # => Validation Error
old_cherries.save!

因为保存第一行时违反了唯一性约束,即使保存第二行后也可以。是否可以将两个保存一起批处理,并让验证只看到一次进行所有更改的结果?

(我可以想到变通方法——例如,如果我进行了验证:allow_nil,那么在更改其中任何一行之前将每一行都归零——但我想知道是否存在“正确”的解决方案)

4

1 回答 1

1

如果您的约束只是验证,但您在数据库中没有强制执行该唯一索引,请使用该update_attribute方法 - 它会跳过验证。

store.fruits.where("kind = banana").first.update_attribute(:kind, "cherry")
store.fruits.where("kind = cherry").first.update_attribute(:kind, "banana")

或者干脆使用save(validate: false)代替save!.

编辑:因为你有一个唯一的索引,死简单的解决方案是使用一个临时值:

store.fruits.where("kind = banana").first.update_attribute(:kind, "foo")
store.fruits.where("kind = cherry").first.update_attribute(:kind, "banana")
store.fruits.where("kind = foo"   ).first.update_attribute(:kind, "cherry")

我不知道有一种方法可以在 SQL 中就地交换行之间的值,这确实是这里的问题,因为模型级验证很容易被绕过。

于 2012-11-20T14:59:12.890 回答