¿Una forma más elegante de omitir validaciones en Rails?

En mi modelo de usuario, tengo los sospechosos habituales de correo electrónico, nombre, apellido, contraseña, etc.

Tengo varios casos en los que necesito omitir todas o algunas de las validaciones.

Actualmente, tengo una condición de menos que se parece a:

  validates :first_name, presence: :true, etc..., unless: :skip_first_name_validation?
  validates :last_name, presence: :true, etc..., unless: :skip_last_name_validation?
  validates :email, presence: :true, etc..., unless: :skip_email_validation?

Por supuesto también tengo:

  attr_accessor :skip_first_name_validation, :skip_last_name_validation, etc.

Y luego uso métodos privados para verificar el estado de cada uno:

  def skip_first_name_validation?
    skip_first_name_validation
  end

  def skip_last_name_validation?
    skip_last_name_validation
  end

  def skip_email_validation?
    skip_email_validation
  end

  etc..

A partir de ahí, cada vez que necesito saltarme las validaciones, solo asigno a cada uno de estos chicos un true valor en mi controlador.


Entonces, aunque todo esto funciona bien, me pregunto si hay una forma más elegante.

Idealmente, sería bueno si pudiera usar un condicional simple como este para cada atributo en mis modelos:

:skip_validation?

Y en mis controladores, solo haz algo como:

skip_validation(:first_name, :last_name, :password) = true

¿Puede alguien ofrecer una sugerencia sobre cómo podría programar esto? Preferiría no usar una gema/biblioteca existente, pero estoy tratando de entender cómo programar este comportamiento en Rails. Gracias.

preguntado el 03 de mayo de 12 a las 17:05

de eso se trata DCI. De lo contrario, ¿no cabrá una máquina de estado? -

Creo que depende de cómo estés actualizando tus modelos. Por ejemplo, puede utilizar el update_column método para actualizar 1 columna a la vez, lo que omite las validaciones. -

¿Es eso como update_attribute? Creo que esto tampoco es muy limpio, porque ahora mis controladores estarían abarrotados con una serie de estas llamadas a métodos. Idealmente, tendría un método al que puedo pasar parámetros en el controlador como: skip_validation (nombre de usuario, correo electrónico, contraseña, etc.). Francamente, estoy un poco sorprendido de que algo como esto no exista OOTB con rieles, pero ese no es realmente el problema. Me pregunto cómo se hace para programar esto en rieles. -

¿Dependiendo de qué te estás saltando las validaciones? -

Mmm... Supongo que no agregué suficiente claridad cuando hice mi pregunta, o es un problema demasiado complejo. -

2 Respuestas

Esto podría ayudarlo a definir todos los setters y checkers dinámicamente

private 
def self.skip_validations_for(*args)
  
  # this block dynamically defines a setter method 'set_validations' 
  # e.g.
  #   set_validations(true, :email, :last_name, :first_name)
  #   set_validations(false, :email, :last_name, :first_name)
  define_method('set_validations') do |value, *params|
    params.each do |var|
      self.send("skip_#{var.to_s}_validations=", value)
    end
  end

  # this block walks through the passed in fields and defines a checker
  # method `skip_[field_name]_validation?
  args.each do |arg|
    if self.method_defined? arg
      send :define_method, "skip_#{attr.to_s}_validation?" do 
        send "skip_#{attr.to_s}_validation"  
      end
    end
  end
end

Luego, en su modelo, independientemente de si esto es nativo en la clase o se incluye a través de un módulo, puede agregar:

Class Foo < ActiveRecord::Base
  validate :bar, :presence => true
  validate :baz, :length_of => 10

  skip_validations_for :bar, :baz
end

momento en el que tendrá acceso a

set_validations(true, :bar, :baz)

y

skip_bar_validation? 

actualizar:

¿Dónde iría este código?

Esto depende de qué tan ampliamente le gustaría usarlo. Si solo desea anular las validaciones en un modelo, puede ponerlo todo directamente en ese modelo. Sin embargo, si desea crear un Module que te permite hacer esto en cada clase entonces tendrás que crear un Module ponlo en el lib/ directorio y asegúrese de que esté en la ruta de carga. Quizás este podría ayudarte con eso.

¿Por qué las cosas cambiaron tan drásticamente?

Originalmente estaba usando el método _validations en una instancia y ese es un método ActiveRecord que devuelve un hash de validaciones (nombre de campo -> ActiveRecord::Validation). Ese método de hacer las cosas activó automáticamente la habilidad para cada campo. Es más propio de Rails permitirle optar en los campos específicos que desee.

Respondido el 20 de junio de 20 a las 10:06

Este es un buen enfoque :) ¿Qué hay de interpolar el attr.to_s? - theodorton

@theodorton Realmente me gustó tu alias Acercarse. :) - tcople

Hola @TCopple, tengo algunas preguntas de novato. 1. ¿Dónde iría este código? ¿Se agregaría esto a un directorio lib y tendría que incluirlo en cada modelo? 2. es self.class._validators.keys un método de rieles? Estoy un poco confundido acerca de este último método que has descrito. ¿Puedes ayudarme a entenderlo? Gracias. - Nathan

@Nathan actualicé mi solución y agregué algunas respuestas a sus preguntas. eliminé el self.class._validators.keys llame porque eso iba a crear métodos skip_validation para cada campo, en lugar de solo el que seleccionó. Rails prefiere que los contratos sean opcionales, en general. - tcople

@TCopple Gracias :) Recuperé la publicación, así que está abierta. - theodorton

No soy un experto en metaprogramación, pero supongo que podrías implementar algo como esto.

class User < ActiveRecord::Base
  ...
  %w(email first_name last_name).each do |attr|
    self.send :attr_accessor, "skip_#{attr}_validation".to_sym
    self.send :alias, "skip_#{attr}_validation?".to_sym, "skip_#{attr}_validation".to_sym
    self.send :validates_presence_of, attr, { unless: "skip_#{attr}_validation?".to_sym }
  end

  def skip_validation_for(*attrs)
    attrs.map{|attr| send "skip_#{attr}_validation=", true }
  end
end

No es realmente elegante mezclado con el modelo, pero podría convertirlo en un módulo, donde el uso podría ser algo como:

class User < ActiveRecord::Base
  include SkipValidations
  skip_validations :email, :first_name, :last_name
end

contestado el 04 de mayo de 12 a las 15:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.