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

发布于 2024-11-03 19:40:56 字数 426 浏览 1 评论 0原文

我正在尝试实现一个“挂起”事件,将对象转换为:挂起状态。但我需要能够“取消暂停”,并返回到之前的状态。我向模型添加了 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 文档并不是很有帮助,而且我在网上找不到示例。有时很难知道如何向谷歌描述某些东西:)

I'm trying to implement a "suspend" event that transitions the object to the :suspended state. But I need to be able to "unsuspend", and return to the previous state. I added a previous_state field to the model, but I can't see how to access it inside an event block.

This is the basic logic I'm trying to implement:

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

The state_machine docs haven't been very helpful, and I can't find examples online. Sometimes it's tough to know how to describe something to google :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

温柔戏命师 2024-11-10 19:40:56

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

The author of state_machine has also provided an alternate solution here: https://groups.google.com/d/msg/pluginaweek-talk/LL9VJdL_x9c/vP1qv6br734J

To wit:

Another possible solution is to be a little creative with how the state machine works. There are plenty of hooks in ORMs like ActiveRecord that give us the ability to set the state at any stage in the process. Consider the following:

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

In this example, a new state, restored, is introduced even though it never actually gets persisted in the database. We instead provide a before_validation hook that rewrites the state based on the previous state. You can see the results below:

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

This should require one less database hit as it occurs before validation. In my case the complete picture looks like this:

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
夢归不見 2024-11-10 19:40:56

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

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 回调,以保存挂起之前的状态。

我向取消挂起事件添加了 after_transition 回调,因此状态会立即更新到之前的状态,然后擦除之前的状态以防止在对象生命周期的后期出现问题。

这并不完美。它可以工作,但它比仅仅将挂起和取消挂起事件创建为独立方法要复杂得多。我没有走这条路,因为我希望 state_machine 控制所有状态更改,并且突破该路线会消除防止移至/移出无效状态、回调等的保护。

This isn't a perfect solution in my opinion, but I did figure out how to accomplish my task:

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

I started by adding an "unsuspended" state to my machine. While I never want something to stay in this state, I can't dynamically tell state_machine what state I want to unsuspend to.

I added a before_transition callback to the suspend event, to save the state before it is suspended.

I added an after_transition callback to the unsuspend event, so the state is immediately updated to the previous state, and that previous state is then wiped out to prevent issues later in the object's life.

This is not perfect. It works, but it's a lot more complicated than just creating the suspend and unsuspend events as standalone methods. I didn't go that route because I want state_machine controlling all state changes, and breaking out of that removes the protections against moving to/from invalid states, callbacks, etc.

杀手六號 2024-11-10 19:40:56

简单的解决方案

我提供了使用 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

Simple solution

I am providing an alternative version to the solution that uses owner block argument. Might be useful is some cases.

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

Use of around_transition

Also note that you can replace the two unsuspend blocks with 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文