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