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

时间:2011-04-27 16:26:10

标签: ruby-on-rails rubygems state-machine

我正在尝试实现一个将对象转换为: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文档不是很有用,我在网上找不到例子。有时很难知道如何描述谷歌的东西:)

3 个答案:

答案 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

使用around_transition

另请注意,您可以使用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