ruby on rails - validations - Rails before_validation strip mejores prácticas de espacio en blanco
rails validates if (13)
Me gustaría que mi modelo de usuario desinfecte alguna entrada antes de guardar. Por ahora, algunos simples espacios en blanco pelarán. Entonces, para evitar que la gente se registre con "Harry" y pretenda ser "Harry", por ejemplo.
Supongo que es una buena idea hacer esto 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 para 1). Supuse que la devolución de llamada pasaría los valores.
Además: ¿esta extracción es realmente una buena idea? O debería validar el espacio y decirle al usuario que "Harry" contiene un spacess no válido (quiero permitir "Harry Potter" pero no "Harry / s / sPotter").
Editar: Como se señaló en un comentario, mi código es incorrecto (razón por la cual hice la pregunta ao). Asegúrese de leer la respuesta aceptada además de mi pregunta sobre el código correcto y evitar los mismos errores que cometí.
StripAttributes Gem
Usé strip_attributes . Es realmente increíble y fácil de implementar.
Comportamiento por defecto
class DrunkPokerPlayer < ActiveRecord::Base
strip_attributes
end
Por defecto, esto solo quitará los espacios en blanco iniciales y finales y actuará en todos los atributos del modelo. Esto es ideal porque no es destructivo y no requiere que especifique qué atributos necesitan 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
Usando 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 regex
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
Anular los métodos de escritura de atributos 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á desmantelado, incluidos assign_attributes y así sucesivamente.
Aquí hay un enfoque alternativo, si lo que más le preocupa es que los usuarios ingresen datos erróneamente 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 application.js si aún no está incluyendo el árbol completo.
Esto asegurará que cada entrada elimine el espacio en blanco inicial y final antes de enviarlo para que Rails lo guarde. Está vinculado al document
y se delega a las entradas, por lo que también se procesarán las entradas añadidas a la página más tarde.
Pros:
- No requiere el listado de atributos individuales por nombre
- No requiere ninguna metaprogramación
- No requiere dependencias externas de biblioteca
Contras:
- Los datos enviados de otra forma que no sean los formularios (p. Ej., A través de API) no se recortarán
- No tiene funciones avanzadas como squish (pero puedes agregarlo tú mismo)
- Como se menciona en los comentarios, no funciona si JS está desactivado (¿pero quién codifica eso?)
Como aún no puedo comentar, tendré que preguntar aquí: ¿qué método está dando ArgumentError? strip
, o responds_to?
Además, .strip
elimina solo el espacio en blanco .strip
y final. Si desea que "Harry Potter" con dos espacios no se acepte, tendrá que usar una expresión regular o, más simple, puede llamar .split, que elimina espacios, y volver a concatenar la cadena con un solo espacio.
En cuanto a si pelar es una buena idea, no veo un problema cuando solo es el espacio en blanco inicial / final. Sin embargo, si hay varios espacios entre palabras, se lo 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 el que presentaron.
En cambio, podemos escribir un método mejor, más genérico, independientemente de cuál sea el tipo de atributos con el objeto (podría tener 3 campos de tipo cadena, pocos elementos 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 eso ayude a alguien!
Hay varias gemas para hacer esto automáticamente. Esas gemas funcionan de forma similar a la creación de devolución de llamada en before_validation. Una buena joya 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
La extracción a menudo es 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 cuerda. Para que "Harry Potter" se convierta en "Harry Potter" (opción aplastar en la gema).
La respuesta de Charlie es buena, pero hay un poco de verbosidad. 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, Ruby interpreta esto último como una asignación de variable local. Simplemente establecerá la variable foo en su ámbito de método, en lugar de llamar al método "foo =" de su objeto.
Pero si llamas a un método, no hay ambigüedad. El intérprete sabe que no se está refiriendo a una variable local llamada foo porque no la hay. Entonces, por ejemplo, con:
self.foo = foo + 1
necesita usar "self" para la tarea, pero no para leer el valor actual.
Me gusta la respuesta de Karl, pero ¿hay alguna forma de hacerlo sin hacer referencia a cada uno de los atributos por su nombre? Es decir, ¿hay alguna forma de ejecutar los atributos del modelo y la franja de llamadas en cada uno (si responde a ese método)?
Esto sería deseable, así que no tengo que actualizar el método remove_whitespace cada vez que cambio el modelo.
ACTUALIZAR
Veo que Karl insinuó que es posible que desee hacer este tipo de cosas. No supe de inmediato cómo se podría hacer, pero aquí hay algo que funciona para mí como se describió anteriormente. Probablemente haya 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
Me gustaría agregar una trampa que pueda experimentar con las soluciones "before_validations" anteriores. Toma este ejemplo:
u = User.new(name: " lala")
u.name # => " lala"
u.save
u.name # => "lala"
Esto significa que tiene un comportamiento incoherente en función de si su objeto se guardó o no. Si desea abordar esto, le sugiero otra solución a su problema: sobrescribir los métodos del colocador correspondiente.
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 la eliminación de todos los atributos que lo soportan, a diferencia de los attribute_names.each
mencionados anteriormente. Además, no se requieren devoluciones de llamada.
No creo que la 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ías hacerlo más dinámico si quieres usar algo como self.columns
, pero eso es lo esencial.
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 Will strip whitespace principal y posterior.
normalize_attribute :author, :with => :strip
Si bien podría adoptar un enfoque similar a la respuesta de Karl, prefiero una sintaxis más escueta con menos tareas:
def strip_whitespace
self.name.try(:strip!)
self.email.try(:strip!)
self.nick.try(:strip!)
end
Si tiene acceso a ActiveSupport, use squish en lugar de strip.
http://api.rubyonrails.org/classes/String.html#method-i-squish