web-dev-qa-db-fra.com

Rails before_validation strip meilleures pratiques d'espace blanc

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.

54
berkes

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.

55
Karl

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).

43
holli

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.

27
Erik

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.

18
emrass

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

9
CharlieMezak

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!

9
Ajay

Si vous avez accès à ActiveSupport, utilisez squish au lieu de strip.

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

8
emptywalls

StripAttributes Gem

J'ai utilisé strip_attributes . C'est vraiment génial et facile à mettre en œuvre.

Comportement par défaut

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.

Utilisation de except

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

Utilisation de only

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

En utilisant allow_empty

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

En utilisant collapse_spaces

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

Utilisation de 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
6
Rameshwar Vyevhare

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.

4
Matt Connolly

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:

  • Ne nécessite pas de répertorier les attributs individuels par nom
  • Ne nécessite aucune métaprogrammation
  • Ne nécessite pas de dépendances de bibliothèque externes

Inconvénients:

  • Les données soumises d'une autre manière que les formulaires (par exemple via l'API) ne seront pas supprimées
  • N'a pas de fonctionnalités avancées comme squish (mais vous pouvez l'ajouter vous-même)
  • Comme mentionné dans les commentaires, ne fonctionne pas si JS est désactivé (mais qui code pour cela?)
4
brookr

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
3
chad_

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.

2
davidcelis

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
1
cweston

À 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/

0
artamonovdev