state_machines rails machine ruby-on-rails rubygems state-machines

ruby-on-rails - rails - aasm state machine



Usando el state_machine de pluginaweek, ¿puedo hacer referencia al objeto activerecord durante un evento? (3)

Estoy tratando de implementar un evento de "suspensión" que transiciones del objeto al estado: suspendido. Pero necesito poder "volver a suspender" y volver al estado anterior. Agregué un campo previous_state al modelo, pero no puedo ver cómo acceder a él dentro de un bloque de eventos.

Esta es la lógica básica que estoy tratando de implementar:

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

Los documentos state_machine no han sido muy útiles y no puedo encontrar ejemplos en línea. A veces es difícil saber cómo describir algo a google :)


Esta no es una solución perfecta en mi opinión, pero sí descubrí cómo lograr mi tarea:

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

Empecé añadiendo un estado "sin suspensión" a mi máquina. Aunque nunca quiero que algo permanezca en este estado, no puedo decirle dinámicamente a state_machine a qué estado quiero volver a ponerme.

Agregué una devolución de llamada before_transition al evento suspend, para guardar el estado antes de que se suspenda.

Agregué una devolución de llamada after_transition al evento unsuspende, por lo que el estado se actualiza inmediatamente al estado anterior, y ese estado anterior se borra para evitar problemas más adelante en la vida del objeto.

Esto no es perfecto Funciona, pero es mucho más complicado que simplemente crear los eventos de suspender y suspender el servicio como métodos independientes. No fui por esa ruta porque quiero que state_machine controle todos los cambios de estado, y salir de eso elimina las protecciones contra el movimiento hacia / desde estados inválidos, devoluciones de llamadas, etc.


El autor de state_machine también ha proporcionado una solución alternativa aquí: https://groups.google.com/d/msg/pluginaweek-talk/LL9VJdL_x9c/vP1qv6br734J

Esto es:

Otra posible solución es ser un poco creativo con la forma en que funciona la máquina de estado. Hay muchos ganchos en ORM como ActiveRecord que nos dan la capacidad de establecer el estado en cualquier etapa del proceso. Considera lo siguiente:

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

En este ejemplo, se introduce un nuevo estado, restaurado, aunque en realidad nunca se conserve en la base de datos. En su lugar, proporcionamos un enlace before_validation que reescribe el estado en función del estado anterior. Puedes ver los resultados a continuación:

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

Esto debería requerir un hit de base de datos menos como ocurre antes de la validación. En mi caso, la imagen completa se ve así:

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


Solución simple

Proporciono una versión alternativa a la solución que usa el argumento de bloque owner . Puede ser útil en algunos casos.

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

Uso de around_transition

También tenga en cuenta que puede reemplazar los dos bloques sin around_transition con 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