Используя государственную машину pluginaweek, я могу ссылаться на объект ActiveRecord, установленным в ходе мероприятия?
Я пытаюсь реализовать событие" suspend", которое переводит объект в состояние: suspended. Но мне нужно уметь "ничего не подозревать", и вернуться в прежнее состояние. Я добавил в модель поле 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 не очень помогли, и я не могу найти примеры в интернете. Иногда трудно понять, как описать что-то другому. google :)
3 ответа:
Автор state_machine также предоставил альтернативное решение здесь: https://groups.google.com/d/msg/pluginaweek-talk/LL9VJdL_x9c/vP1qv6br734J
А именно:
Другое возможное решение состоит в том, чтобы быть немного творческим с тем, как работает государственная машина. Существует множество крючков в ORMs, таких как ActiveRecord, которые дают нам возможность устанавливать состояние на любой стадии процесса. Рассмотрим следующее: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
На мой взгляд, это не идеальное решение, но я понял, как выполнить свою задачу:
Я начал с добавления состояния" unsuspended " к моей машине. Хотя я никогда не хочу, чтобы что-то оставалось в этом состоянии, я не могу динамически сказать state_machine, в какое состояние я хочу не подозревать.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
Я добавил обратный вызов before_transition к событию suspend, чтобы сохранить состояние до его приостановки.
Я добавил after_transition обратного вызова для восстановления событий, поэтому состояние немедленно обновляется до предыдущего состояния, и это предыдущее состояние затем стирается, чтобы предотвратить проблемы позже в жизни объекта.
Это не идеально. Это работает, но это намного сложнее, чем просто создавать события suspend и unsuspend как автономные методы. Я не пошел по этому пути, потому что я хочу, чтобы state_machine контролировал все изменения состояния, и выход из него удаляет защиту от перемещения в/из недопустимых состояний, обратных вызовов и т. д.
Простое решение
Я предоставляю альтернативную версию решения, которая использует аргумент блока
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