Rails antes de la validación strip whitespace mejores prácticas


Me gustaría que mi modelo de usuario desinfectara alguna entrada antes de guardar. Por ahora un simple stripping de espacios en blanco servirá. Así que para evitar que la gente se registre con " Harry "y pretender ser" Harry", por ejemplo.

Asumo que es una buena idea hacer este stripping antes de la validación, para que validates_uniqueness_of pueda evitar duplicados accidentales.

class User < ActiveRecord::Base
  has_many :open_ids

  validates_presence_of :name
  validates_presence_of :email
  validates_uniqueness_of :name
  validates_uniqueness_of :email
  validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i

  before_validation :strip_whitespace, :only => [:name, :email, :nick]

  private
  def strip_whitespace(value)
    value.responds_to?('strip') ? value.strip : value
  end
end

Sin Embargo, este código viene con un error ArgumentError: número incorrecto de argumentos (0 a 1). Asumí la devolución de llamada se pasarían los valores.

También: ¿es este stripping realmente una buena idea? O debería validar en espacio y decirle al usuario que "Harry" contiene spacess inválido (quiero permitir "Harry Potter" pero no "Harry\s\sPotter").

Editar: Como se señaló en un comentario, mi código es incorrecto (que es por lo que estaba haciendo la pregunta a.o.). Por favor, asegúrese de leer la respuesta aceptada además de mi pregunta para el código correcto y para evitar los mismos errores que cometí.

Author: berkes, 2010-07-16

13 answers

No creo que before_validation funcione así. Probablemente quieras escribir tu método así:

def strip_whitespace
  self.name = self.name.strip unless self.name.nil?
  self.email = self.email.strip unless self.email.nil?
  self.nick = self.nick.strip unless self.nick.nil?
end

Podría hacerlo más dinámico si desea usar algo como self.columns, pero esa es la esencia de la misma.

 50
Author: Karl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-18 01:27:21

Hay varias gemas para hacer esto automáticamente. Esas gemas funcionan de la misma manera de crear callback en before_validation. Una buena gema está en https://github.com/holli/auto_strip_attributes

gem "auto_strip_attributes", "~> 2.2"

class User < ActiveRecord::Base
  auto_strip_attributes :name, :nick, nullify: false, squish: true
  auto_strip_attributes :email
end

El stripping es a menudo una buena idea. Especialmente para espacios en blanco iniciales y finales. El usuario a menudo crea espacios finales al copiar / pegar valor a un formulario. Con los nombres y otras cadenas de identificación, también es posible que desee aplastar la cadena. Para que "Harry Potter" se convierta "Harry Potter" (opción de aplastar en la gema).

 36
Author: holli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-06 13:50:55

La respuesta de Charlie es buena, pero hay un poco de detalle. Aquí hay una versión más ajustada:

def clean_data
  # trim whitespace from beginning and end of string attributes
  attribute_names.each do |name|
    if send(name).respond_to?(:strip)
      send("#{name}=", send(name).strip)
    end
  end
end

La razón por la que usamos

self.foo = "bar"

En lugar de

foo = "bar"

En el contexto de los objetos ActiveRecord es que Ruby interpreta estos últimos como una asignación de variable local. Simplemente establecerá la variable foo en el ámbito de su método, en lugar de llamar al método "foo=" de su objeto.

Pero si está llamando a un método, no hay ambigüedad. El intérprete sabe que no eres se refiere a una variable local llamada foo porque no hay ninguna. Así por ejemplo con:

self.foo = foo + 1

Necesita usar "self" para la tarea, pero no para leer el valor actual.

 24
Author: Erik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-27 10:24:08

Me gustaría agregar una trampa que podría experimentar con las soluciones "before_validations" anteriores. Tomemos este ejemplo:

u = User.new(name: " lala")
u.name # => " lala"
u.save
u.name # => "lala"

Esto significa que tiene un comportamiento inconsistente basado en si su objeto fue guardado o no. Si desea abordar esto, le sugiero otra solución a su problema: sobrescribir los métodos de configuración correspondientes.

class User < ActiveRecord::Base
  def name=(name)
    write_attribute(:name, name.try(:strip))
  end
end

También me gusta este enfoque porque no te obliga a habilitar el stripping para todos los atributos que lo soportan, a diferencia del attribute_names.each mencionado anteriormente. Además, no se requieren devoluciones de llamada.

 17
Author: emrass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-17 13:20:15

Me gusta la respuesta de Karl, pero ¿hay una manera de hacerlo sin hacer referencia a cada uno de los atributos por nombre? Es decir, ¿hay una manera de simplemente ejecutar a través de los atributos del modelo y la tira de llamadas en cada uno (si responde a ese método)?

Esto sería deseable para no tener que actualizar el método remove_whitespace cada vez que cambie el modelo.

UPDATE

Veo que Karl insinuó que podrías querer hacer este tipo de cosas. No supe inmediatamente cómo podría hacerse, pero aquí hay algo que funciona para mí como se describió anteriormente. Probablemente hay una mejor manera de hacerlo, pero esto funciona:

def clean_data
  # trim whitespace from beginning and end of string attributes
  attribute_names().each do |name|
  if self.send(name.to_sym).respond_to?(:strip)
    self.send("#{name}=".to_sym, self.send(name).strip)
  end
end

Fin

 8
Author: CharlieMezak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-06 01:03:07

Si tiene acceso a ActiveSupport, utilice squish en lugar de strip.

Http://api.rubyonrails.org/classes/String.html#method-i-squish

 8
Author: emptywalls,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-20 04:00:30

En su lugar podemos escribir un método mejor más genérico independientemente de lo que pueda ser el tipo de atributos con el objeto (podría tener 3 campos de tipo de cadena, pocos booleanos, pocos numéricos)

before_validation :strip_input_fields


def strip_input_fields
  self.attributes.each do |key, value|
    self[key] = value.strip if value.respond_to?("strip")
  end
end

¡Espero que will ayude a alguien!

 8
Author: Ajay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-25 19:54:12

StripAttributes Gem

Usé strip_attributes. Es realmente impresionante y fácil de implementar.

Comportamiento predeterminado

class DrunkPokerPlayer < ActiveRecord::Base
  strip_attributes
end

De forma predeterminada, esto solo eliminará los espacios en blanco iniciales y finales y actuará sobre todos los atributos del modelo. Esto es ideal porque no es destructivo y no requiere que especifique qué atributos deben ser rayados.

Usando except

# all attributes will be stripped except :boxers
class SoberPokerPlayer < ActiveRecord::Base
  strip_attributes :except => :boxers
end

Usando only

# only :shoe, :sock, and :glove attributes will be stripped
class ConservativePokerPlayer < ActiveRecord::Base
  strip_attributes :only => [:shoe, :sock, :glove]
end

Utilizando allow_empty

# Empty attributes will not be converted to nil
class BrokePokerPlayer < ActiveRecord::Base
  strip_attributes :allow_empty => true
end

Usando collapse_spaces

# Sequential spaces in attributes will be collapsed to one space
class EloquentPokerPlayer < ActiveRecord::Base
  strip_attributes :collapse_spaces => true
end

Usando expresiones regulares

class User < ActiveRecord::Base
  # Strip off characters defined by RegEx
  strip_attributes :only => [:first_name, :last_name], :regex => /[^[:alpha:]\s]/
  # Strip off non-integers
  strip_attributes :only => [:phone], :regex => /[^0-9]/
end
 5
Author: Rameshwar Vyevhare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-20 17:49:36

Aquí hay un enfoque alternativo, si lo que más le preocupa es que los usuarios introduzcan datos incorrectamente en sus formularios de front-end...

# app/assets/javascripts/trim.inputs.js.coffee
$(document).on "change", "input", ->
  $(this).val $(this).val().trim()

Luego incluya el archivo en su solicitud.js si aún no estás incluyendo todo el árbol.

Esto asegurará que cada entrada obtenga espacios en blanco iniciales y finales eliminados antes de ser enviados para ser guardados por Rails. Está vinculado a document, y delegado a las entradas, por lo que cualquier entrada añadida a la página más tarde se procesará como bien.

Ventajas:

  • No requiere enumerar atributos individuales por nombre
  • No requiere ninguna metaprogramación
  • No requiere dependencias de bibliotecas externas

Contras:

  • Los datos enviados de cualquier otra manera que los formularios (por ejemplo, a través de API) no se recortarán
  • No tiene características avanzadas como squish (pero podría agregarlo usted mismo)
  • Como se mencionó en los comentarios, no funciona si JS es deshabilitado (¿pero quién codifica para eso?)
 4
Author: brookr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-30 08:14:21

Reemplazar el atributo write methods es otra buena manera. Por ejemplo:

class MyModel
  def email=(value)
    super(value.try(:strip))
  end
end

Entonces cualquier parte de la aplicación que establezca el valor lo tendrá despojado, incluyendo assign_attributes y así sucesivamente.

 3
Author: Matt Connolly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-05 04:13:31

Aunque podría adoptar un enfoque similar a la respuesta de Karl, prefiero una sintaxis más concisa con menos asignaciones:

def strip_whitespace
  self.name.try(:strip!)
  self.email.try(:strip!)
  self.nick.try(:strip!)
end
 3
Author: chad_,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-22 19:37:21

Como aún no puedo comentar, tendré que preguntar aquí: ¿qué método está dando el Argumententerror? strip, o responds_to?

Además, .strip elimina solo los espacios en blanco iniciales y finales. Si quieres que" Harry Potter " con dos espacios no sea aceptado, tendrías que usar una expresión regular o, más simplemente, podrías llamar .dividir, que elimina espacios, y volver a concatenar la cadena con un solo espacio.

En cuanto a si el stripping es una buena idea, no veo un problema cuando es solo el espacio en blanco inicial/final. Sin embargo, si hay varios espacios entre palabras, notificaría al usuario en lugar de eliminar automáticamente los espacios adicionales y darle al usuario un inicio de sesión que no es lo que envió.

 2
Author: davidcelis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-14 07:48:22

Otra opción de gema es attribute_normalizer :

# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher

: strip Eliminará los espacios en blanco iniciales y finales.

normalize_attribute  :author, :with => :strip
 1
Author: cweston,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-08 16:10:14