70 votes

Rails before_validation strip whitespace meilleures pratiques

J'aimerais que mon modèle utilisateur nettoie certaines entrées avant de les enregistrer. Pour l'instant, une simple suppression de l'espace blanc suffit. Ainsi, pour éviter que des personnes s'enregistrent avec "Harry" et prétendent être "Harry", par exemple.

Je suppose que c'est une bonne idée de faire ce dépouillement avant la validation, afin que le 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 accompagné d'une erreur ArgumentError : wrong number of arguments (0 pour 1). Je supposais que les valeurs seraient transmises à la callback.

Par ailleurs, ce décapage est-il vraiment une bonne idée ? Ou devrais-je plutôt valider sur 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 incorrect (c'est pourquoi je posais la question a.o.). Veuillez vous assurer que vous lisez la réponse acceptée en plus de ma question pour le code correct et pour éviter les mêmes erreurs que j'ai faites.

5 votes

Pour les autres qui tomberaient sur ce sujet, before_validation n'a pas d'objet de type :only option. Et le callback ne prend pas d'argument. Voir les réponses pour plus de détails.

62voto

Karl Points 2712

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
  self.email = self.email.strip
  self.nick = self.nick.strip
end

Vous pouvez le rendre plus dynamique si vous voulez en utilisant quelque chose comme self.columns mais c'est l'essentiel.

6 votes

J'ai ajouté un unless self.name.blank ? derrière eux, pour éviter de supprimer les valeurs NIL.

0 votes

En fonction de votre classe, vous pouvez envisager ruby def strip_whitespace self.email = email.strip end

1 votes

@berkes - Il serait préférable d'ajouter if self.name.respond_to?(:strip) .

49voto

holli Points 793

Il existe plusieurs joyaux pour faire cela automatiquement. Ces gemmes fonctionnent de la même manière que la création de callback dans before_validation. Une bonne gemme se trouve à https://github.com/holli/auto_strip_attributes

gem "auto_strip_attributes", "~> 1.0"

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. En particulier pour les espaces blancs de début et de fin. Les utilisateurs créent souvent des espaces de fin de ligne lorsqu'ils copient/collent des valeurs dans un formulaire. Avec les noms et autres chaînes d'identification, vous pouvez également vouloir écraser la chaîne. Ainsi, "Harry Potter" deviendra "Harry Potter" (option "squish" dans la gemme).

30voto

Erik Points 141

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 cette dernière comme une affectation de variable locale. Il va simplement 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 aucune ambiguïté. L'interprète sait que vous ne vous référez pas à une variable locale appelée foo car il n'y en a pas. Donc par exemple avec :

self.foo = foo + 1

vous devez utiliser "self" pour l'affectation, mais pas pour lire la valeur actuelle.

3 votes

J'utilise ceci mais avec changed.each au lieu de attributes_names pour le limiter aux champs qui ont été modifiés.

22voto

emrass Points 3394

J'aimerais ajouter un piège 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 voulez résoudre ce problème, je vous propose une autre solution : écraser les méthodes setter correspondantes.

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

J'aime aussi cette approche parce qu'elle ne vous oblige pas à activer le stripping pour tous les attributs qui le supportent - contrairement à l'approche du attribute_names.each mentionné plus haut. De plus, aucun rappel n'est nécessaire.

1 votes

Merci pour votre commentaire Ben. J'utilise l'approche mentionnée ci-dessus dans Rails 3 sans aucun problème. De plus, c'est toujours l'approche mentionnée dans la documentation de la version 3.2.8 : api.rubyonrails.org/classes/ActiveRecord/Base.html . Avez-vous rencontré des problèmes avec ce produit ?

9voto

CharlieMezak Points 3648

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 son nom ? En d'autres termes, existe-t-il un moyen de parcourir les attributs du modèle et d'appeler le strip sur chacun d'eux (s'il répond à cette méthode) ?

Cela serait souhaitable pour ne pas avoir à mettre à jour la méthode remove_whitespace chaque fois que je change de modèle.

UPDATE

Je vois que Karl a laissé entendre que vous pourriez vouloir faire ce genre de choses. Je n'ai pas su immédiatement comment le faire, mais voici quelque chose qui fonctionne pour moi, comme décrit ci-dessus. Il y a probablement un meilleur moyen 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

0 votes

Cela semble être une solution supérieure, et cela a bien fonctionné, merci.

3 votes

Excellente solution. Mais elle peut être optimisée davantage : au lieu de la méthode attributes_names, nous pouvons utiliser changes.keys, de sorte que la deuxième fois, seuls les attributs modifiés peuvent être dépouillés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X