2012-11-20 17 views
3

在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,然後改變其中任何一個前無過的每一行 - 但我想知道,如果一個「正確」的解決方案存在)

回答

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行之間的交換價值,這是真正的問題,因爲模型級別的驗證可以輕鬆繞過。

+0

不幸的是,我在'[「store_id」,「kind」]上有一個唯一索引。我可能被說服去除它,但是有沒有另一種解決方案?這是在測試框架,所以我打開略有骯髒的伎倆。例如,我可以通過原始SQL執行此操作嗎? – Chowlett