2014-04-16 29 views
0

好滑軌的驗證輔助回報,我已經有了一個模型,這個簡化版本(的Rails 3.2.13):製作假

class Transfer < ActiveRecord::Base 
    attr_accessible :from,:to,:total 
    validates_presence_of :from,:to,:total 

    before_validation :positive_numbers, on: :create 
    before_validation :check_enough_balance, on: :create 
    after_validation :update_balances 

    private 
    def positive_numbers 
    unless self.total>0 
     errors.add(:total,"should be greater than 0") 
     return false 
    end 
    end 
    def check_enough_balance 
    @sender=User.find(self.from) 
    @receiver=User.find(self.to) 
    unless @sender.enough_balance(self.total) 
     errors.add(:base,"Not enough credit") 
     return false 
    end 
    end 
    def update_balances 
    @sender.balance -= self.total 
    @receiver.balance += self.total 
    @sender.save 
    @receiver.save 
    end 
    def another_action 
    puts 'does something' 
    end 

end 

每當total<0,該實例與false返回, errors數組正確填充並且another_action回調未被調用。

我不知道怎麼去使用此相同的行爲的Rails內置的驗證輔助,這是我試了一下:

class Transfer < ActiveRecord::Base 
    attr_accessible :from,:to,:total 
    validates_presence_of :from,:to,:total 
    validates_numericality_of :total, greater_than: 0 

    before_validation :check_enough_balance, on: :create 
    after_validation :update_balances 
    private 

    def check_enough_balance 
    @sender=User.find(self.from) 
    @receiver=User.find(self.to) 
    unless @sender.enough_balance(self.total) 
     errors.add(:base,"Not enough credit") 
     return false 
    end 
    end 
    def update_balances 
    @sender.balance -= self.total 
    @receiver.balance += self.total 
    @sender.save 
    @receiver.save 
    end 
end 
class User<ActiveRecord::Base 
    attr_accessible :username 
    validates_presence_of :username, :balance 

    def enough_balance(amount) 
    self.balance >= amount 
    end 

end 

然而,在這種情況下,由於驗證助手不return false下面的自定義驗證check_enough_balance被調用,我希望它的行爲完全一樣,我相信使用驗證幫助器在某種程度上更加優雅和簡潔。

回答

2

before_validation回調字面意思是「在檢查驗證之前運行此方法」。如果您希望您的驗證在another_action之前運行,請考慮將其移至其他回調。根據你的例子,我想你可能想要回調after_validation,但還有其他支持的回調可能會更好。

after_validation :another_action, on: :create 

您可以在這裏找到支持的回調的完整列表:ActiveRecord::Callbacks

驗證規則應該是獨立的單位,無論您的其他規則如何,都是有意義的。對於所有驗證規則來說,通常最好運行並收集所有組合的錯誤,以便用戶可以同時修復所有錯誤。

對於您的特定情況,檢查用戶是否可以覆蓋總數爲0或更少似乎很好。這只是另一個獨立於其他的驗證規則。儘管如此,考慮移動,從一個回調到一個實際的驗證:

class Transfer < ActiveRecord::Base 
    ... 
    validate :enough_balance 

    private 

    def enough_balance 
    unless User.find(self.from).enough_balance(self.total) 
     errors.add(:base, "Not enough credit") 
    end 
    end 
end 

如果您需要無法執行某些原因,檢查(例如性能),它很容易再次爲無效狀態檢查無需處理通過改變的條件下在同一時間的錯誤:

unless self.total <= 0 || User.find(self.from).enough_balance(self.total) 

您可以找到有關自定義的更多信息,驗證器在這裏:Active Record Validations - Custom Validators

保存記錄的情況不應該發生的有效組成部分(包括回調之前和之後)。我們誰都想要的副作用,如果我們,例如,只是想手動檢查記錄是否有效:

transfer = Transfer.new(...) 
if transfer.valid? 
    # Stuff gets saved?!?! 
    ... 
end 

相反,使用before_saveafter_save回調更新相關記錄。只有ActiveRecord決定保存記錄是可以接受的,這些回調纔會運行。如果驗證失敗,那麼這些回調將不會運行,作爲save調用的一部分。

after_save :update_balances 

而且,在這樣的時候進行額外的節省,這是使用save!方法,而不是save方法和交易都包裹一般的最佳實踐。當您顯式檢查返回值時,應使用save。當你假定一切都處於你期望的狀態時,應該使用save!。通過save!引發的異常可用於回滾作爲該事務的一部分所做的所有其他更改。

一些例子:

transfer = Transfer.new(...) 

if transfer.save # Good! 
    ... 

if transfer.save! # Bad, causes an exception when you might expect false 
    ... 

transfer.save # Bad, can silently fail 

transfer.save! # Good, raises an exception if it unexpectedly fails 

# All changes will be rolled back if any of the `save!` calls raise exceptions 
Transfer.transaction do 
    transfer.save! 
    something_else.save! 
    yet_another_thing.save! 
end 

你通常會放你Transfer.transaction呼叫周圍的transfer.savetransfer.save!呼叫控制器。

有關交易的更多信息可以在這裏找到:ActiveRecord::Transactions::ClassMethods

+0

這是一個很好的想法。但是我忘了提及我需要在**驗證之前調用這個回調函數。我編輯了這個例子,所以前面的'another_action'現在是'check_enough_balance',它檢查了足夠的信用。我很感激回覆壽。 – lllllll

+0

@ vint-i-vuit我想你的例子中,你應該把'check_enough_balance'當作另一個驗證器。查看我的編輯瞭解更多信息。 –

+0

再一次,我很感激答覆。我編輯了擴展示例的問題(我想我不應該簡化它開始),以顯示*爲什麼*我希望驗證一旦失敗就停止。正如你所看到的,我有一個'after_validation'方法,我希望一旦所有的驗證都通過並且沒有錯誤就運行。我使用驗證助手最接近的是'after_validation:update_users_balance,如果:: all_valid'其中'def all_valid; self.errors.empty ?; end'。然而,由於某些原因,**保存**新的@ @轉移,即使它是無效的。 :/ – lllllll