Валидация перед настойчивостью на камне state_machine
Каков правильный синтаксис для выполнения проверки перед переходом в state_machine gem?
Я пробовал следующее,
before_transition :apple => :orange do
validate :validate_core
end
def validate_core
if core.things.blank?
errors.add(:core, 'must have one thing')
end
end
Но я получаю следующую ошибку:
undefined method `validate' for #<StateMachine::Machine:0x007ffed73e0bd8>
Я также пробовал написать его, как
state :orange do
validate :validate_core
end
Но это приводит к откату после сохранения записи, что является менее идеальным. Я бы хотел, чтобы конечный автомат сначала переключился на :orange
.
Основная проблема заключается в том, что в моем контроллере у меня есть логика, которая опирается на результат object.save
. Проверка, которую у меня есть для моего конечного автомата, не срабатывает до момента первоначального сохранения, поэтому сохранение возвращается как истинное, а контроллер переходит к логике, он не должен ударяться, если объект недействителен.
Я работал над этим, проверяя корректность вручную в дополнение к проверке сохранения, но похоже, что должен быть способ проверки достоверности перед сохранением объекта.
Ответы
Ответ 1
Идея этого конечного автомата заключается в том, чтобы встроить декларацию валидации внутри состояния.
state :orange do
validate :validate_core
end
В приведенной выше конфигурации будет выполняться проверка :validate_core
всякий раз, когда объект переходит на оранжевый.
event :orangify do
transition all => :orange
end
Я понимаю вашу озабоченность откатом, но имейте в виду, что откат выполняется в транзакции, поэтому он довольно дешев.
record.orangify!
Кроме того, помните, что вы также можете использовать версию non bang, которая не использует исключения.
> c.orangify
(0.3ms) BEGIN
(0.3ms) ROLLBACK
=> false
Тем не менее, если вы хотите использовать другой подход на основе перехода до перехода, вам нужно только знать, что если обратный вызов возвращает false, переход останавливается.
before_transition do
false
end
> c.orangify!
(0.2ms) BEGIN
(0.2ms) ROLLBACK
StateMachine::InvalidTransition: Cannot transition state via :cancel from :purchased (Reason(s): Transition halted)
Обратите внимание, что транзакция всегда запускается, но, скорее всего, запрос не будет выполнен, если обратный вызов находится в самом начале.
before_transaction
принимает некоторые параметры. Вы можете предоставить объект и экземпляр транзакции.
before_transition do |object, transaction|
object.validate_core
end
и вы можете ограничить его событием
before_transition all => :orange do |object, transaction|
object.validate_core # => false
end
В этом случае validate_core
, однако, должен быть простым методом, который возвращает true/false. Если вы хотите использовать определенную цепочку валидации, то мне приходит в голову призывать valid?
к самой модели.
before_transition all => :orange do |object, transaction|
object.valid?
end
Однако, обратите внимание, что вы не можете выполнить транзакцию за пределами транзакции. Фактически, если вы проверите код для perform
, вы увидите, что обратные вызовы находятся внутри транзакции.
# Runs each of the collection transitions in parallel.
#
# All transitions will run through the following steps:
# 1. Before callbacks
# 2. Persist state
# 3. Invoke action
# 4. After callbacks (if configured)
# 5. Rollback (if action is unsuccessful)
#
# If a block is passed to this method, that block will be called instead
# of invoking each transition action.
def perform(&block)
reset
if valid?
if use_event_attributes? && !block_given?
each do |transition|
transition.transient = true
transition.machine.write(object, :event_transition, transition)
end
run_actions
else
within_transaction do
catch(:halt) { run_callbacks(&block) }
rollback unless success?
end
end
end
# ...
end
Чтобы пропустить транзакцию, вы должны установить патч state_machine, чтобы методы перехода (например, orangify!
) проверяли, действительна ли запись до перехода.
Вот пример того, что вы должны достичь
# Override orangify! state machine action
# If the record is valid, then perform the actual transition,
# otherwise return early.
def orangify!(*args)
return false unless self.valid?
super
end
Конечно, вы не можете сделать это вручную для каждого метода, поэтому для обезболивания вам необходимо обезопасить библиотеку, чтобы достичь этого результата.
Ответ 2
Вы можете попытаться отменить переход к следующему состоянию, выполнив что-то вроде этого:
before_transition :apple => :orange do
if core.things.blank?
errors.add(:core, 'must have one thing')
throw :halt
end
end
Таким образом, если core.things пуст, тогда для ядра появится ошибка, и переход будет отменен. Я предполагаю, что он также не внесет никаких изменений в БД. Не пробовал этот код, а просто прочитал его источник. Учитывая, что код выше, скорее всего, приведет к еще большему количеству кода, чтобы поймать исключение, как насчет подхода ниже?
def orange_with_validation
if core.things.blank? && apple?
errors.add(:core, 'must have one thing')
else
#transition to orange state
orange
end
end
Вы можете использовать код выше в тех местах, где вы хотели бы проверить, прежде чем переходить в оранжевое состояние. Этот подход позволяет обходить ограничения обратных вызовов state_machine. Использование его в вашем контроллере, который поддерживает форму мастера, остановит вашу форму от перехода к следующему шагу и будет избегать любых ударов БД, если он не прошел проверку.
Ответ 3
Я все еще новичок, но не
validates
вместо
validate
http://edgeguides.rubyonrails.org/active_record_validations.html
Также просто прочитав документацию, которую вы должны выполнить в состоянии, я никогда не использовал state_machine, но я думаю, что-то вроде этого:
state :orange do
validates_presence_of :apple
end
Ответ 4
Rails ищет метод 'validate' для состояния. Но validate - активный метод записи. Все ваши модели наследуются от активной записи, но состояния нет, поэтому у нее нет метода проверки. Способ обойти это - определить метод класса и вызвать его в состоянии. Итак, скажем, ваша модель называется Fruit, у вас может быть что-то вроде этого
class Fruit < ActiveRecord::Base
def self.do_the_validation
validate :validate_core
end
before_transition :apple => :orange, :do => :do_the_validation
end
Я не уверен, нужен ли вам сам. Кроме того, может потребоваться вторая строка:
self.validate :validate_core
Я думаю, это должно сработать. Это, как говорится, есть ли какая-то причина, по которой вы его проверяете до перехода? Почему бы просто не поставить валидацию самостоятельно? Он всегда должен проверять.
Ответ 5
validate methos - это метод класса вашей модели, поэтому вы не можете вызвать его из блока, который вы передаете методу класса state_machine, потому что у вас есть новый контекст.
Попробуйте следующее:
YourModel < AR::B
validate :validate_core
state_machine :state, :initial => :some_state do
before_transition :apple => :orange do |model, transition|
model.valid?
end
end
def validate_core
if core.things.blank?
errors.add(:core, 'must have one thing')
end
end
end
Ответ 6
Цель "остановить [ping] состояние машины от перехода на: оранжевый в первую очередь" звучит как охранник на переходе. state_machine поддерживает это с помощью: if и: if options в определении перехода. Как и в случае с валидаторами ActiveModel, значение для этих параметров может быть либо лямбдой, либо символом, представляющим имя метода для вызова объекта.
event :orangify
transition :apple => :orange, :if => lambda{|thing| thing.validate_core }
# OR transition :apple => :orange, :if => :validate_core
end