J'aimerais que mon modèle utilisateur nettoie certaines entrées avant de les enregistrer. Pour l'instant, un simple effacement des espaces fera l'affaire. Donc, pour éviter que les gens s'inscrivent avec "Harry" et prétendent être "Harry", par exemple.
Je suppose que c'est une bonne idée de faire cette suppression avant la validation, afin que validates_uniqueness_of puisse éviter les doublons accidentels.
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
Cependant, ce code est livré avec une erreur ArgumentError: nombre d'arguments incorrect (0 pour 1). J'ai supposé que le rappel recevrait les valeurs.
Aussi: ce décapage est-il vraiment une bonne idée? Ou devrais-je plutôt valider l'espace et dire à l'utilisateur que "Harry" contient des espaces invalides (je veux autoriser "Harry Potter" mais pas "Harry\s\sPotter").
Edit: Comme indiqué dans un commentaire, mon code est erroné (c'est pourquoi je posais la question a.o.). Veuillez vous assurer de lire la réponse acceptée en plus de ma question pour le bon code et pour éviter les mêmes erreurs que j'ai commises.
Je ne crois pas before_validation
fonctionne comme ça. Vous voudrez probablement écrire votre méthode comme ceci à la place:
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
Vous pouvez le rendre plus dynamique si vous souhaitez utiliser quelque chose comme self.columns
, mais c'est l'essentiel.
Il existe plusieurs gemmes pour le faire automatiquement. Ces gemmes fonctionnent de la même manière pour créer un rappel dans before_validation. Un bon bijou est à 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
Le décapage est souvent une bonne idée. Surtout pour les espaces blancs avant et arrière. L'utilisateur crée souvent des espaces de fin lorsqu'il copie/colle une valeur dans un formulaire. Avec des noms et d'autres chaînes d'identification, vous pouvez également vouloir écraser la chaîne. Pour que "Harry Potter" devienne "Harry Potter" (option squish dans la gemme).
La réponse de Charlie est bonne, mais il y a un peu de verbosité. Voici une version plus serrée:
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 raison pour laquelle nous utilisons
self.foo = "bar"
au lieu de
foo = "bar"
dans le contexte des objets ActiveRecord est que Ruby interprète ce dernier comme une affectation de variable locale. Il va juste définir la variable foo dans la portée de votre méthode, au lieu d'appeler la méthode "foo =" de votre objet.
Mais si vous appelez une méthode, il n'y a pas d'ambiguïté. L'interpréteur sait que vous ne faites pas référence à une variable locale appelée foo car il n'y en a pas. Ainsi par exemple avec:
self.foo = foo + 1
vous devez utiliser "self" pour l'affectation, mais pas pour lire la valeur actuelle.
Je voudrais ajouter un écueil que vous pourriez rencontrer avec les solutions "before_validations" ci-dessus. Prenez cet exemple:
u = User.new(name: " lala")
u.name # => " lala"
u.save
u.name # => "lala"
Cela signifie que vous avez un comportement incohérent selon que votre objet a été enregistré ou non. Si vous souhaitez résoudre ce problème, je suggère une autre solution à votre problème: remplacer les méthodes de définition correspondantes.
class User < ActiveRecord::Base
def name=(name)
write_attribute(:name, name.try(:strip))
end
end
J'aime également cette approche, car elle ne vous oblige pas à activer la suppression pour tous les attributs qui la prennent en charge - contrairement au attribute_names.each
mentionné plus tôt. De plus, aucun rappel n'est requis.
J'aime la réponse de Karl, mais y a-t-il un moyen de le faire sans référencer chacun des attributs par leur nom? Autrement dit, existe-t-il un moyen de simplement parcourir les attributs du modèle et la bande d'appel sur chacun (s'il répond à cette méthode)?
Ce serait souhaitable, donc je n'ai pas à mettre à jour la méthode remove_whitespace chaque fois que je change de modèle.
MISE À JOUR
Je vois que Karl a laissé entendre que vous pourriez vouloir faire ce genre de chose. Je ne savais pas immédiatement comment cela pouvait être fait, mais voici quelque chose qui fonctionne pour moi comme décrit ci-dessus. Il y a probablement une meilleure façon de le faire, mais cela fonctionne:
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
Au lieu de cela, nous pouvons écrire une meilleure méthode plus générique quel que soit le type d'attributs avec l'objet (peut avoir 3 champs de type chaîne, quelques booléens, peu numériques)
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
J'espère que cela aidera quelqu'un!
Si vous avez accès à ActiveSupport, utilisez squish au lieu de strip.
http://api.rubyonrails.org/classes/String.html#method-i-squish
J'ai utilisé strip_attributes . C'est vraiment génial et facile à mettre en œuvre.
class DrunkPokerPlayer < ActiveRecord::Base
strip_attributes
end
Par défaut, cela ne supprimera que les espaces blancs de début et de fin et agira sur tous les attributs du modèle. Ceci est idéal car il n'est pas destructif et ne vous oblige pas à spécifier quels attributs doivent être entrelacés.
except
# all attributes will be stripped except :boxers
class SoberPokerPlayer < ActiveRecord::Base
strip_attributes :except => :boxers
end
only
# only :shoe, :sock, and :glove attributes will be stripped
class ConservativePokerPlayer < ActiveRecord::Base
strip_attributes :only => [:shoe, :sock, :glove]
end
allow_empty
# Empty attributes will not be converted to nil
class BrokePokerPlayer < ActiveRecord::Base
strip_attributes :allow_empty => true
end
collapse_spaces
# Sequential spaces in attributes will be collapsed to one space
class EloquentPokerPlayer < ActiveRecord::Base
strip_attributes :collapse_spaces => true
end
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
Remplacer les méthodes d'écriture d'attributs est un autre bon moyen. Par exemple:
class MyModel
def email=(value)
super(value.try(:strip))
end
end
Ensuite, toute partie de l'application qui définit la valeur sera supprimée, y compris assign_attributes, etc.
Voici une approche alternative, si vous êtes principalement préoccupé par la mauvaise saisie des données par les utilisateurs dans vos formulaires frontaux ...
# app/assets/javascripts/trim.inputs.js.coffee
$(document).on "change", "input", ->
$(this).val $(this).val().trim()
Incluez ensuite le fichier dans votre application.js si vous n'incluez pas déjà l'arborescence entière.
Cela garantira que chaque entrée sera supprimée avant et avant d'être soumise pour être enregistrée par Rails. Il est lié à document
et délégué aux entrées, donc toutes les entrées ajoutées à la page plus tard seront également traitées.
Avantages:
Inconvénients:
Bien que je puisse adopter une approche similaire à la réponse de Karl, je préfère une syntaxe plus concise avec moins d'affectations:
def strip_whitespace
self.name.try(:strip!)
self.email.try(:strip!)
self.nick.try(:strip!)
end
Comme je ne peux pas encore commenter, je vais devoir demander ici: quelle méthode donne ArgumentError? strip
ou responds_to?
Aussi, .strip
supprime uniquement les espaces de début et de fin. Si vous voulez que "Harry Potter" avec deux espaces ne soit pas accepté, vous devrez soit utiliser une expression régulière, soit, plus simplement, vous pourriez appeler .split, qui supprime les espaces, et reconcatier la chaîne avec un seul espace.
En ce qui concerne le décapage est une bonne idée, je ne vois pas de problème quand c'est juste le blanc de tête/de fin. S'il y a plusieurs espaces entre les mots, j'informerais l'utilisateur au lieu de supprimer automatiquement les espaces supplémentaires et de donner à l'utilisateur une connexion qui n'est pas celle qu'il a soumise.
Une autre option de gemme est attribute_normalizer :
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
: strip Supprime les espaces de début et de fin.
normalize_attribute :author, :with => :strip
À partir de Ruby 2.3.0, vous pouvez utiliser l'opérateur de navigation sécurisée (&.)
before_validation :strip_whitespace
def strip_whitespace
self.name&.strip!
self.email&.strip!
self.nick&.strip!
end
GEMMES:
https://github.com/rmm5t/strip_attributes/
https://github.com/holli/auto_strip_attributes/