state_machines rails machine ruby-on-rails ruby validation ruby-on-rails-4 state-machines

ruby-on-rails - rails - aasm state machine



ValidaciĆ³n antes de la persistencia en la gema state_machine (6)

El objetivo de "detener [hacer ping] a la máquina de estado de la transición a: naranja en primer lugar" suena como un guardia de la transición. state_machine admite esto con: if y: a menos que haya opciones en la definición de transición. Al igual que con los validadores de ActiveModel, el valor de estas opciones puede ser una lambda o un símbolo que represente el nombre del método para llamar al objeto.

event :orangify transition :apple => :orange, :if => lambda{|thing| thing.validate_core } # OR transition :apple => :orange, :if => :validate_core end

¿Cuál es la sintaxis correcta para realizar una validación antes de una transición en la gema state_machine ?

He intentado lo siguiente,

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

Pero me sale el siguiente error,

undefined method `validate'' for #<StateMachine::Machine:0x007ffed73e0bd8>

También he intentado escribirlo como,

state :orange do validate :validate_core end

Pero esto provoca una reversión después de que se guarda el registro, que es menos que ideal. Me gustaría detener la transición de la máquina de estados a :orange en primer lugar.

El problema principal es que en mi controlador tengo una lógica que se basa en el resultado de object.save . La validación que tengo para mi máquina de estado no se activa hasta después del guardado inicial, por lo que guardar se devuelve como verdadero y el controlador pasa a la lógica que no debería golpear si el objeto no es válido.

He resuelto este problema probando la validez manualmente además de verificar el guardado, pero parece que debería haber una manera de hacer que se active la validación antes de que el objeto salve.


La idea de esa máquina de estado particular es incrustar la declaración de validación dentro del estado.

state :orange do validate :validate_core end

La configuración anterior realizará la validación :validate_core siempre que el objeto esté en transición a naranja.

event :orangify do transition all => :orange end

Entiendo su preocupación por la reversión, pero tenga en cuenta que la reversión se realiza en una transacción, por lo que es bastante barata.

record.orangify!

Además, recuerde que también puede usar la versión no explosiva que no usa excepciones.

> c.orangify (0.3ms) BEGIN (0.3ms) ROLLBACK => false

Dicho esto, si desea utilizar un enfoque diferente basado en la transición anterior, solo debe saber que si la devolución de llamada devuelve falso, la transición se detiene.

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)

Tenga en cuenta que una transacción siempre se inicia, pero es probable que no se realice ninguna consulta si la devolución de llamada es al principio.

La before_transaction acepta algunos params. Puede ceder el objeto y la instancia de transacción.

before_transition do |object, transaction| object.validate_core end

Y de hecho puedes restringirlo por evento.

before_transition all => :orange do |object, transaction| object.validate_core # => false end

En este caso, validate_core , sin embargo, se supone que es un método simple que devuelve verdadero / falso. Si desea utilizar la cadena de validación definida, entonces, ¿qué me viene a la mente es invocar valid? En el modelo en sí.

before_transition all => :orange do |object, transaction| object.valid? end

Sin embargo, tenga en cuenta que no puede ejecutar una transacción fuera del alcance de una transacción. De hecho, si inspecciona el código para perform , verá que las devoluciones de llamada están dentro de la transacción.

# Runs each of the collection''s 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''s 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

Para omitir la transacción, debe aplicar el parche state_machine para que los métodos de transición (como por orangify! ) Verifiquen si el registro es válido antes de la transición.

Aquí hay un ejemplo de lo que debes lograr

# 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

Por supuesto, no puedes hacer eso manualmente para cada método, por eso debes poner un parche en la biblioteca para lograr este resultado.


Puedes intentar cancelar la transición al siguiente estado haciendo algo como esto:

before_transition :apple => :orange do if core.things.blank? errors.add(:core, ''must have one thing'') throw :halt end end

De esta manera, si core.things está en blanco, aparecerá un error para el núcleo y la transición se cancelará. Supongo que tampoco haría ningún cambio en la base de datos. Aunque no he probado este código, solo he leído su fuente. Dado que el código anterior, probablemente llevaría a más código para detectar la excepción, ¿qué le parece el siguiente enfoque?

def orange_with_validation if core.things.blank? && apple? errors.add(:core, ''must have one thing'') else #transition to orange state orange end end

Puede usar el código de arriba en los lugares donde le gustaría validación antes de que pase al estado naranja. Este enfoque le permite solucionar las limitaciones de las devoluciones de llamada de state_machine. Si lo utiliza en su controlador, que activa el formulario del asistente, evitará que su formulario se mueva al siguiente paso y evitará cualquier golpe de base de datos cuando falle la validación.


Rails está buscando un método ''validar'' para el estado. Pero validar es un método de registro activo. Todos sus modelos heredan del registro activo, pero el estado no, por lo que no tiene un método de validación. La forma de evitar esto es definir un método de clase y llamarlo en estado. Así que digamos que tu modelo se llama Fruit, podrías tener algo como esto

class Fruit < ActiveRecord::Base def self.do_the_validation validate :validate_core end before_transition :apple => :orange, :do => :do_the_validation end

No estoy seguro si te necesitas a ti mismo. Además, la segunda línea debe ser:

self.validate :validate_core

Creo que esto debería funcionar. Dicho esto, ¿hay alguna razón por la que tenga validación antes de la transición? ¿Por qué no solo poner la validación por sí misma? Siempre debe validar.


Sigo siendo nuevo pero no es

validates

en lugar de

validate

http://edgeguides.rubyonrails.org/active_record_validations.html

También solo leyendo la documentación que tiene que hacer la validación en el estado, nunca he usado state_machine pero pienso algo como esto:

state :orange do validates_presence_of :apple end


validar methos es el método de clase de su modelo, por lo que no puede llamarlo desde el bloque que pasa al método de clase state_machine, porque tiene un nuevo contexto.

Prueba esto:

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