19

什麼是在state_machine創業板的過渡之前進行確認正確的語法持久性過嗎?驗證對state_machine寶石

我試過以下,

before_transition :apple => :orange do 
    validate :validate_core 
end 

def validate_core 
    if core.things.blank? 
    errors.add(:core, 'must have one thing') 
    end 
end 

,但我得到了下面的錯誤,

undefined method `validate' for #<StateMachine::Machine:0x007ffed73e0bd8> 

我也試着寫它,

state :orange do 
    validate :validate_core 
end 

但這記錄保存後會導致回滾,這並不理想。我想停止狀態機首先轉換爲:orange

的核心問題是,在我的控制器,我有依靠的object.save的結果邏輯。我的狀態機的驗證直到初始保存後才啓動,因此保存返回值爲true,並且控制器繼續執行邏輯,如果對象無效,則不應該命中。

我已經通過除了檢查保存之外手動測試有效性來解決此問題,但感覺應該有一種方法來在對象保存之前進行驗證激發。

回答

26

該特定狀態機的想法是將驗證聲明嵌入到狀態中。

state :orange do 
    validate :validate_core 
end 

無論何時對象正在轉換爲橙色,上述配置將執行驗證:validate_core

event :orangify do 
    transition all => :orange 
end 

我明白您對回滾的關注,但請記住,回滾是在交易中執行的,因此它非常便宜。

record.orangify! 

此外,請記住,您還可以使用不使用異常的非爆炸版本。

> c.orangify 
    (0.3ms) BEGIN 
    (0.3ms) ROLLBACK 
=> false 

這就是說,如果你想使用基於之前的過渡採用不同的方法,那麼你只需要知道,如果回調返回false,過渡被暫停。

before_transition do 
    false 
end 

> c.orangify! 
    (0.2ms) BEGIN 
    (0.2ms) ROLLBACK 
StateMachine::InvalidTransition: Cannot transition state via :cancel from :purchased (Reason(s): Transition halted) 

注意交易開始工作,但它有可能,如果回調是在開始的時候不會執行查詢。

before_transaction接受一些參數。您可以產生對象和事務實例。

before_transition do |object, transaction| 
    object.validate_core 
end 

,確實可以通過事件

before_transition all => :orange do |object, transaction| 
    object.validate_core # => false 
end 

在這種情況下,限制它,但是validate_core應該是返回真/假的簡單方法。如果你想使用定義的驗證鏈,那麼我想到的是在模型本身上調用valid?

before_transition all => :orange do |object, transaction| 
    object.valid? 
end 

但是,請注意,您不能在事務範圍之外運行事務。事實上,如果您檢查perform的代碼,您將看到回調在交易中。

# Runs each of the collection's transitions in parallel. 
# 
# All transitions will run through the following steps: 
# 1. Before callbacks 
# 2. Persist state 
# 3. Invoke action 
# 4. After callbacks (if configured) 
# 5. Rollback (if action is unsuccessful) 
# 
# If a block is passed to this method, that block will be called instead 
# of invoking each transition's action. 
def perform(&block) 
    reset 

    if valid? 
    if use_event_attributes? && !block_given? 
     each do |transition| 
     transition.transient = true 
     transition.machine.write(object, :event_transition, transition) 
     end 

     run_actions 
    else 
     within_transaction do 
     catch(:halt) { run_callbacks(&block) } 
     rollback unless success? 
     end 
    end 
    end 

    # ... 
end 

要跳過的事務,您應該猴補丁state_machine使過渡方法(如)檢查記錄是否轉換之前有效。

下面是你應該實現

# Override orangify! state machine action 
# If the record is valid, then perform the actual transition, 
# otherwise return early. 
def orangify!(*args) 
    return false unless self.valid? 
    super 
end 

當然什麼一個例子,你不能這樣做手工爲每個方法,這就是爲什麼你應該猴子補丁庫實現這一結果。

+0

問題不在於回滾的性能,更多的是保存通過(至少在最初)。因此,在我的控制器中,嚮導爲控制器供電,控制器檢查保存並繼續運行,並使用戶進入下一步。與此同時,背景中的狀態回滾和用戶現在正處於他們不應訪問的步驟。我在我的控制器中有一個明確的檢查,但感覺像不需要的混亂。 –

+0

您是否試圖按照我所示的方式返回false?它應該停止轉換。 –

+0

我做過了,它確實如此,但它仍然作爲回滾。我將更新我的問題以更好地反映我正在尋找的內容。我做了一個假設,即在before_transition上停止狀態轉換將會停止回滾,但事實並非如此。 –

0

我還是新的,但不是就是

validates 

,而不是

validate 

http://edgeguides.rubyonrails.org/active_record_validations.html

還只是讀你所要做的驗證的狀態的文件,我從來沒有用過state_machine,但我認爲是這樣的:

state :orange do 
     validates_presence_of :apple 
end 
+0

的'validate'電話是良好的,當你自定義的驗證是一種方法,請參閱http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate。在不幸的狀態下做它意味着它不會被檢查直到在.save方法之後,這會導致一些非常奇怪的行爲。 –

0

Rails正在尋找一種「驗證」狀態的方法。但驗證是一種有效的記錄方法。所有模型都從活動記錄繼承,但狀態不是,所以它沒有驗證方法。解決這個問題的方法是定義一個類方法並在狀態中調用它。因此,可以說你的模特叫做水果,你可以有這樣的東西

class Fruit < ActiveRecord::Base 
    def self.do_the_validation 
     validate :validate_core 
    end 

    before_transition :apple => :orange, :do => :do_the_validation 
end 

我不知道你是否需要自我。此外,第二行可能需要是:

self.validate :validate_core 

我認爲這應該工作。話雖如此,是否有任何理由讓你在過渡之前驗證它?爲什麼不直接把自己的驗證?它應該始終驗證。

+0

RE:'總是驗證',有一個關聯在你達到某個特定狀態之前是不需要的。 –

+0

沒有結束工作。我在驗證前使用和不使用'self'。 –

+0

你可以發佈validate_core方法嗎? – Philip7899

0

驗證methos是你的模型類的方法,所以你不能叫他從您傳遞給state_machine類方法塊,因爲你有新的上下文。

試試這個:

YourModel < AR::B 
    validate :validate_core 

    state_machine :state, :initial => :some_state do 
    before_transition :apple => :orange do |model, transition| 
     model.valid? 
    end 
    end 

    def validate_core 
    if core.things.blank? 
     errors.add(:core, 'must have one thing') 
    end 
    end 
end 
1

你可以嘗試做這樣的事情,取消過渡到下一個狀態:

before_transition :apple => :orange do 
    if core.things.blank? 
    errors.add(:core, 'must have one thing') 
    throw :halt 
    end 
end 

這樣,如果core.things是空白的,那麼核心會出現錯誤,並且轉換將被取消。我認爲它也不會對數據庫進行任何更改。雖然沒有嘗試過這個代碼,但只是閱讀它的源代碼。鑑於上面的代碼,可能會導致更多的代碼來捕捉異常,那麼下面的方法如何?

def orange_with_validation 
    if core.things.blank? && apple? 
    errors.add(:core, 'must have one thing') 
    else 
    #transition to orange state 
    orange 
    end 
end 

在轉換到橙色狀態之前,您可以在需要驗證的地方使用上面的代碼。這種方法可以讓你解決state_machine回調的限制。在你的控制器中使用嚮導的形式將會阻止你的表單移動到下一步到期,並在驗證失敗時避免任何數據庫命中。

+0

這是一個有趣的想法,但後來我必須編寫代碼來捕獲此異常,所以最終並不會真的減少持久性代碼。 –

+0

我上面提供的代碼可能只是解決您的問題。它允許您在對象保存之前觸發驗證。你只需要在你想要的地方使用'#orange_with_validation'而不是'#orange'。 – Gjaldon

0

「停止[ping]狀態機轉變爲:首先是橙色」的目標聽起來像是過渡時期的守護者。 state_machine支持:if和:除非轉換定義上的選項。和ActiveModel驗證器一樣,這些選項的值可以是一個lambda或一個代表調用該對象的方法名稱的符號。

event :orangify 
    transition :apple => :orange, :if => lambda{|thing| thing.validate_core } 
    # OR transition :apple => :orange, :if => :validate_core 
end