我正在尝试实现一个将对象转换为:suspended状态的“suspend”事件。但我需要能够“取消悬挂”,并返回到之前的状态。我在模型中添加了一个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文档不是很有用,我在网上找不到例子。有时很难知道如何描述谷歌的东西:)
答案 0 :(得分:1)
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') # => #<Vehicle id: nil, name: "test", state: "parked">
v.save # => true
v.name = nil # => nil
v.ignite # => true
v # => #<Vehicle id: 1, name: nil, state: "idling">
v.restore # => false
v.errors # => #<OrderedHash {:name=>["can't be blank"]}>
v.state # => "idling"
v.name = 'test' # => "test"
v.restore # => true
v # => #<Vehicle id: 1, name: "test", state: "parked">
v.parked? # => true
这应该在验证之前需要少一个数据库命中。就我而言,完整的图片看起来像这样:
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
答案 1 :(得分:0)
在我看来,这不是一个完美的解决方案,但我确实弄清楚如何完成我的任务:
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控制所有的状态变化,并且中断会消除阻止移入/移出无效状态,回调等的保护。
答案 2 :(得分:0)
我正在为使用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
另请注意,您可以使用unsuspend
替换两个around_transition
块:
around_transition :on => :unsuspend do |owner, transition_block|
if owner.previous_state.present?
transition_block.call
owner.state = owner.previous_state
end
end