Rails before validation strip whitespace najlepsze praktyki

Chciałbym, aby mój model użytkownika wyczyścił niektóre dane wejściowe przed zapisem. Na razie wystarczy proste usuwanie spacji. Więc aby uniknąć ludzi rejestrujących się z "Harry" i udawać, że jest "Harry", na przykład.

Zakładam, że dobrym pomysłem jest zrobienie tego przed walidacją, aby validates_uniqueness_of mógł uniknąć przypadkowych duplikatów.

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

Jednak ten kod zawiera błąd ArgumentError: wrong number of arguments (0 for 1). Założyłem, że oddzwanianie zostaną przekazane wartości.

Również: czy to rozbieranie to dobry pomysł? A może powinienem raczej zweryfikować spację i powiedzieć użytkownikowi, że "Harry" zawiera nieprawidłową spację (chcę zezwolić na "Harry Potter", ale nie "Harry\s\sPotter").

Edit: jak zaznaczono w komentarzu, mój kod jest zły (dlatego pytałem o. o.). Upewnij się, że przeczytałeś zaakceptowaną odpowiedź oprócz mojego pytania o poprawny kod i aby uniknąć tych samych błędów, które popełniłem.

Author: berkes, 2010-07-16

13 answers

Nie wierzę, że to działa. Prawdopodobnie chcesz zamiast tego napisać swoją metodę w ten sposób:

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

Możesz uczynić go bardziej dynamicznym, jeśli chcesz użyć czegoś podobnego self.columns, ale w tym tkwi sedno sprawy.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-18 01:27:21

Istnieje kilka klejnotów, które robią to automatycznie. Te perełki działają w podobny sposób do tworzenia wywołania zwrotnego w before_validation. Jeden dobry klejnot jest na 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
Stripping jest często dobrym pomysłem. Szczególnie w przypadku wiodących i końcowych spacji. Użytkownik często tworzy spacje końcowe podczas kopiowania / wklejania wartości do formularza. Z nazwami i innymi ciągami identyfikującymi możesz również chcieć wycisnąć ciąg. Aby "Harry Potter" stał się "Harry Potter "(opcja squish w klejnocie).
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-06 13:50:55

Odpowiedź Charliego jest dobra, ale jest trochę gadatliwości. Tu jest wersja ciaśniejsza:

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

Powód, dla którego używamy

self.foo = "bar"

Zamiast

foo = "bar"

W kontekście obiektów ActiveRecord jest to, że Ruby interpretuje te ostatnie jako przypisanie zmiennej lokalnej. Po prostu ustawi zmienną foo w zakresie metody, zamiast wywoływać metodę " foo="obiektu.

Ale jeśli wywołujesz metodę, nie ma niejednoznaczności. Tłumacz wie, że nie jesteś odnosi się do zmiennej lokalnej o nazwie foo, ponieważ jej nie ma. Więc na przykład z:

self.foo = foo + 1

Musisz użyć "self" do przypisania, ale nie do odczytu bieżącej wartości.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-27 10:24:08

Chciałbym dodać jedną pułapkę, którą możesz doświadczyć w powyższych rozwiązaniach "before_validations". Weźmy ten przykład:

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

Oznacza to, że masz niespójne zachowanie oparte na tym, czy twój obiekt został zapisany, czy nie. Jeśli chcesz rozwiązać ten problem, proponuję inne rozwiązanie twojego problemu: nadpisanie odpowiednich metod settera.

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

Podoba mi się również to podejście, ponieważ nie zmusza do włączania usuwania wszystkich atrybutów, które go obsługują-w przeciwieństwie do attribute_names.each wspomniany wcześniej. Ponadto, nie wymaga wywołań zwrotnych.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-17 13:20:15

Podoba mi się odpowiedź Karla, ale czy jest sposób, aby to zrobić bez odwoływania się do każdego z atrybutów Po nazwie? To znaczy, czy istnieje sposób, aby po prostu uruchomić przez atrybuty modelu i wywołać pasek na każdym z nich (jeśli reaguje na tę metodę)?

Byłoby to pożądane, więc nie muszę aktualizować metody remove_whitespace za każdym razem, gdy zmieniam model.

UPDATE

Widzę, że Karl zasugerował, że możesz chcieć robić takie rzeczy. Nie wiedziałem od razu, jak to można to zrobić, ale tutaj jest coś, co działa dla mnie, jak opisano powyżej. Jest chyba lepszy sposób, ale to działa:
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

End

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-06 01:03:07

Jeśli masz dostęp do ActiveSupport, użyj squish zamiast 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/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-20 04:00:30

Zamiast tego możemy napisać lepszą metodę bardziej ogólną niezależnie od rodzaju atrybutów z obiektem (może mieć 3 pola typu string, kilka wartości logicznych, kilka liczbowych)

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
Mam nadzieję, że to komuś pomoże!
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-25 19:54:12

StripAttributes Gem

Użyłem strip_attributes. To naprawdę niesamowite i łatwe do wdrożenia.

Domyślne Zachowanie

class DrunkPokerPlayer < ActiveRecord::Base
  strip_attributes
end

Domyślnie spowoduje to usunięcie tylko początkowych i końcowych spacji i będzie działać na wszystkich atrybutach modelu. Jest to idealne rozwiązanie, ponieważ nie jest destrukcyjne i nie wymaga określania, które atrybuty mają być pasowane.

Za pomocą except

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

Za pomocą only

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

Za pomocą allow_empty

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

Użycie collapse_spaces

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

Using 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
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 17:49:36

Oto alternatywne podejście, jeśli chodzi głównie o użytkowników błędnie wprowadzających dane w formularzach front-end...

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

Następnie dołącz plik do aplikacji.js, jeśli nie jesteś jeszcze w tym całe drzewo.

Zapewni to, że każde wejście zostanie usunięte przed wysłaniem do zapisania przez Rails. Jest przypisany do document i delegowany do wejść, więc wszelkie wejścia dodane do strony później będą przetwarzane jako cóż.

Plusy:

  • nie wymaga wymieniania poszczególnych atrybutów po nazwie
  • nie wymaga żadnego metaprogramowania
  • nie wymaga zewnętrznych zależności bibliotek

Wady:

  • dane przesyłane w inny sposób niż formularze (np. za pośrednictwem API) nie będą przycinane
  • nie ma zaawansowanych funkcji, takich jak squish (ale możesz to dodać samemu)
  • Jak wspomniano w komentarzach, nie działa, jeśli JS jest wyłączone (ale kto do tego koduje?)
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-30 08:14:21

Nadpisanie metod zapisu atrybutów jest kolejnym dobrym sposobem. Na przykład:

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

Wtedy jakakolwiek część aplikacji, która ustawia wartość, zostanie usunięta, w tym assign_attributes i tak dalej.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-05 04:13:31

Chociaż mogę przyjąć podobne podejście do odpowiedzi Karla, wolę bardziej zwięzłą składnię z mniejszą liczbą zadań:

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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-22 19:37:21

Ponieważ nie mogę jeszcze skomentować, muszę zapytać tutaj: Która metoda daje ArgumentError? strip, LUB responds_to?

Ponadto, .strip usuwa tylko początkowe i końcowe białe znaki. Jeśli chcesz, aby "Harry Potter" z dwoma spacjami nie był akceptowany, musisz użyć wyrażenia regularnego lub, prościej, możesz zadzwonić .split, który Usuwa spacje i ponownie łączy łańcuch z pojedynczą spacją.

O ile stripping to dobry pomysł, to nie widzę problemu, gdy jest to po prostu początkowe / końcowe białe spacje. Jeśli istnieje wiele spacji między słowami, chciałbym powiadomić użytkownika, zamiast automatycznie usuwać dodatkowe spacje i dając użytkownikowi login, który nie jest tym, co przesłał.
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-14 07:48:22

Inną opcją klejnotu jest attribute_normalizer :

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

:Pasek usuwa wiodące i końcowe białe spacje.

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/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-08 16:10:14