Подтвердить что ты не робот

Валидация перед настойчивостью на камне 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. Проверка, которую у меня есть для моего конечного автомата, не срабатывает до момента первоначального сохранения, поэтому сохранение возвращается как истинное, а контроллер переходит к логике, он не должен ударяться, если объект недействителен.

Я работал над этим, проверяя корректность вручную в дополнение к проверке сохранения, но похоже, что должен быть способ проверки достоверности перед сохранением объекта.

4b9b3361

Ответ 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