Using pluginaweek state_machine, can I refer to an activerecord object during an event?

I am trying to implement the "suspend" event, which puts the object in the state: suspended. But I need to be able to "refuse" and return to the previous state. I added the previous_state field to the model, but I donโ€™t see how to access it inside the event block.

This is the main 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 didn't help much, and I can't find examples on the Internet. Sometimes it's hard to figure out how to describe something on google :)

+3
source share
3 answers

This is not an ideal solution, in my opinion, but I figured out how to complete 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 the "unsuspended" state to my machine. Although I never want something to remain in this state, I cannot dynamically tell state_machine which state I want to refuse.

I added a before_transition callback for the suspend event to keep the state until it is suspended.

I added the after_transition callback to the unsuspend event, so the state is immediately updated to the previous state, and the previous state is then destroyed to prevent problems later in the object's life.

This is not perfect. It works, but it is much more complicated than just creating suspend and unsuspend events as standalone methods. I didnโ€™t go this route because I want state_machine to control all state changes, and breaking it removes protection from moving to / from invalid states, callbacks, etc.

0
source

State_machine also provided an alternative solution here: https://groups.google.com/d/msg/pluginaweek-talk/LL9VJdL_x9c/vP1qv6br734J

In particular:

Another possible solution is to be a little creative in how the state machine works. There are many hooks in ORMs such as ActiveRecord that give us the ability to set state at any stage of 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 

This example introduces a new state that has been restored, although it is never stored in the database. Instead, we provide a before_validation hook that overwrites 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 more than one hit of the database, as happens before validation. In my case, the full 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 
+1
source

A simple solution

I provide an alternative version of a solution that uses the owner block argument. May be useful in 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 

Using round_transition

Also note that you can replace two blocks unsuspend on 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 
0
source

Source: https://habr.com/ru/post/1389459/


All Articles