使用pluginaweek的state_machine,我可以在事件期间引用activerecord对象吗?

我正在尝试实现一个“暂停”事件,将对象转换为:suspended状态。 但我需要能够“取消悬挂”,并返回到以前的状态。 我在模型中添加了一个previous_state字段,但我看不到如何在事件块中访问它。

这是我试图实现的基本逻辑:

event :suspend do owner.previous_state = self.state transition [:new, :old] => :suspended end event :unsuspend do transition :suspended => owner.previous_state.to_sym owner.previous_state = nil end 

state_machine文档不是很有帮助,我在网上找不到例子。 有时很难知道如何描述谷歌的东西:)

state_machine的作者还提供了另一种解决方案: https ://groups.google.com/d/msg/pluginaweek-talk/LL9VJdL_x9c/vP1qv6br734J

以机智:

另一种可能的解决方案是对状态机的工作方式有所启发。 像ActiveRecord这样的ORM中有很多钩子,它们使我们能够在过程的任何阶段设置状态。 考虑以下:

 class Vehicle < ActiveRecord::Base before_validation do |vehicle| # Set the actual value based on the previous state if we've just restored vehicle.state = vehicle.previous_state if vehicle.restored? end state_machine :initial => :parked do event :ignite do transition :parked => :idling end event :restore do transition any => :restored end state :parked do validates_presence_of :name end end # Look up previous state here... def previous_state 'parked' end end 

在此示例中,引入了一个已恢复的新状态,即使它实际上从未在数据库中持久存在。 我们提供了一个before_validation钩子,它根据先前的状态重写状态。 您可以看到以下结果:

 v = Vehicle.new(:name => 'test') # => # v.save # => true v.name = nil # => nil v.ignite # => true v # => # v.restore # => false v.errors # => #["can't be blank"]}> v.state # => "idling" v.name = 'test' # => "test" v.restore # => true v # => # v.parked? # => true 

这应该在validation之前需要少一个数据库命中。 在我的情况下,完整的图片看起来像这样:

 module Interpreting::Status extend ActiveSupport::Concern included do before_validation :restore_previous_state, if: :interpreter_cancelled? state_machine :state, :initial => :ordered do before_transition :to => :interpreter_booked, :do => :set_previous_state state :ordered state :confirmed state :interpreter_booked state :interpreter_cancelled # Transient status end end protected def set_previous_state self.previous_state = self.state end def restore_previous_state self.state = self.previous_state end end 

在我看来,这不是一个完美的解决方案,但我确实弄清楚如何完成我的任务:

 state_machine :initial => :new do state :new state :old state :suspended before_transition :to => :suspended, :do => :set_previous_state state :unsuspended after_transition :to => :unsuspended, :do => :restore_previous_state event :suspend do transition any - :suspended => :suspended end event :unsuspend do transition :suspended => :unsuspended, :if => :previous_state_present? end end private def previous_state_present? previous_state.present? end def set_previous_state self.previous_state = state end def restore_previous_state if previous_state self.state = previous_state self.previous_state = nil end end 

我开始在我的机器上添加一个“未挂起”的状态。 虽然我从不希望某些东西停留在这种状态,但我无法动态告诉state_machine我想要取消挂起的状态。

我在暂停事件中添加了一个before_transition回调,以便在暂停之前保存状态。

我向unsuspend事件添加了一个after_transition回调函数,因此状态会立即更新到之前的状态,然后消除之前的状态以防止在对象的生命中出现问题。

这并不完美。 它的工作原理,但它比仅使用独立方法创建挂起和取消挂起事件要复杂得多。 我没有走那条路,因为我希望state_machine控制所有的状态变化,并且中断会消除阻止移动到无效状态,回调等的保护。

简单解决方案

我正在为使用owner块参数的解决方案提供替代版本。 有些情况可能有用。

 state_machine :initial => :new do state :new state :old before_transition :on => :suspend do |owner| owner.previous_state = owner.state end before_transition :on => :unsuspend do |owner| owner.previous_state.present? end after_transition :on => :unsuspend do |owner| owner.state = owner.previous_state end event :suspend do transition any - :suspended => :suspended end event :unsuspend do transition :suspended => :unsuspended end end 

使用around_transition

另请注意,您可以使用around_transition替换两个未unsuspend块:

 around_transition :on => :unsuspend do |owner, transition_block| if owner.previous_state.present? transition_block.call owner.state = owner.previous_state end end