Rails before_validation полоса пробелов лучшие практики
Я бы хотел, чтобы моя модель User дезактивировала некоторые данные перед сохранением. На данный момент простейшие пробельные пробелы.
Поэтому, чтобы избежать регистрации людей с "Гарри" и, например, притвориться "Гарри" .
Я предполагаю, что это хорошая идея сделать это удаление перед проверкой, так что validates_uniqueness_of может избежать случайных дубликатов.
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
Однако этот код содержит ошибку ArgumentError: неправильное количество аргументов (0 для 1). Я предположил, что обратный вызов будет передан значения.
Также: это зачистка на самом деле хорошая идея? Или я должен скорее обосноваться в космосе и сказать пользователю, что "Гарри" содержит недействительный пробел (я хочу разрешить "Гарри Поттер", но не "Гарри\s\sPotter" ).
Изменить: Как указано в комментарии, мой код неправильный (вот почему я задавал вопрос a.o.). Пожалуйста, убедитесь, что вы прочитали принятый ответ в дополнение к моему вопросу о правильном коде и избегаете тех же ошибок, которые я сделал.
Ответы
Ответ 1
Я не верю, что before_validation
работает так. Вероятно, вы захотите написать свой метод следующим образом:
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
Вы можете сделать его более динамичным, если хотите использовать что-то вроде self.columns
, но суть его.
Ответ 2
Есть несколько драгоценных камней, чтобы сделать это автоматически. Эти самоцветы работают аналогично созданию обратного вызова в before_validation. Одна хорошая жемчужина на 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
Раздевание часто является хорошей идеей. Специально для ведущих и конечных пробелов. Пользователь часто создает конечные пробелы при копировании/вставке значения в форму. С именами и другими идентифицирующими строками вы также можете захотеть сжать строку. Так что "Гарри Поттер" станет "Гарри Поттером" (хлюпающий вариант в жемчужине).
Ответ 3
Чарли отвечает хорошо, но там немного многословия. Здесь более плотная версия:
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
Причина, по которой мы используем
self.foo = "bar"
вместо
foo = "bar"
в контексте объектов ActiveRecord заключается в том, что Ruby интерпретирует последнее как назначение локальной переменной. Он просто установит переменную foo в области вашего метода вместо вызова метода "foo =" вашего объекта.
Но если вы вызываете метод, нет никакой двусмысленности. Интерпретатор знает, что вы не имеете в виду локальную переменную foo, потому что ее нет. Так, например, с помощью:
self.foo = foo + 1
вам нужно использовать "self" для назначения, но не читать текущее значение.
Ответ 4
Я хотел бы добавить одну ловушку, которую вы можете испытать с помощью решений "before_validations" выше. Возьмите этот пример:
u = User.new(name: " lala")
u.name # => " lala"
u.save
u.name # => "lala"
Это означает, что у вас непоследовательное поведение, основанное на сохранении вашего объекта или нет. Если вы хотите обратиться к этому вопросу, я предлагаю другое решение вашей проблемы: переписывание соответствующих методов настройки.
class User < ActiveRecord::Base
def name=(name)
write_attribute(:name, name.try(:strip))
end
end
Мне также нравится этот подход, потому что он не заставляет вас включать удаление всех атрибутов, которые его поддерживают, в отличие от attribute_names.each
, упомянутых ранее. Кроме того, не требуется никаких обратных вызовов.
Ответ 5
Мне нравится ответ Карла, но есть ли способ сделать это, не ссылаясь на каждый из атрибутов по имени? То есть, есть ли способ просто запустить атрибуты модели и полосу вызова на каждом из них (если он отвечает на этот метод)?
Это было бы желательно, поэтому мне не нужно обновлять метод remove_whitespace всякий раз, когда я меняю модель.
UPDATE
Я вижу, что Карл подразумевал, что вы, возможно, захотите сделать подобное. Я не сразу понял, как это можно сделать, но вот что-то, что работает для меня, как описано выше. Вероятно, это лучший способ сделать это, но это работает:
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
конец
Ответ 6
Вместо этого мы можем написать более эффективный метод более общий, независимо от того, какой тип атрибутов может иметь объект (может иметь 3 строковых типа, несколько булевых, несколько числовых)
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
Надеюсь, что это поможет кому-то!
Ответ 7
Если у вас есть доступ к ActiveSupport, используйте squish вместо strip.
http://api.rubyonrails.org/classes/String.html#method-i-squish
Ответ 8
Я использовал strip_attributes. Это действительно потрясающе и легко реализовать.
Поведение по умолчанию
class DrunkPokerPlayer < ActiveRecord::Base
strip_attributes
end
По умолчанию это приведет к стиранию ведущего и конечного пробелов и будет действовать на все атрибуты модели. Это идеально, потому что это не разрушительно и не требует, чтобы вы указывали, какие атрибуты должны быть полосатыми.
Использование 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
Использование 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
Ответ 9
Здесь альтернативный подход, если вы в основном обеспокоены тем, что пользователи ошибочно вводят данные в свои интерфейсные формы...
# app/assets/javascripts/trim.inputs.js.coffee
$(document).on "change", "input", ->
$(this).val $(this).val().trim()
Затем включите файл в ваш application.js, если вы еще не включили все дерево.
Это гарантирует, что каждый вход удаляется перед удалением ведущего и конечного пробелов, прежде чем он будет отправлен для сохранения в Rails. Он привязан к document
и делегирован входам, поэтому любые входы, добавленные на страницу позже, также будут обработаны.
Плюсы:
- Не требуется перечислять отдельные атрибуты по имени
- Не требует метапрограммирования
- Не требует внешних зависимостей библиотеки
Минусы:
- Данные, переданные любым другим способом, чем формы (например, через API), не будут обрезаны
- Не имеет расширенных функций вроде squish (но вы можете добавить это сами)
- Как упоминалось в комментариях, не работает, если JS отключен (но кто координирует это?)
Ответ 10
Переопределение методов записи атрибутов - еще один хороший способ. Например:
class MyModel
def email=(value)
super(value.try(:strip))
end
end
Тогда любая часть приложения, устанавливающая значение, будет лишена его, включая assign_attributes и т.д.
Ответ 11
Хотя я мог бы использовать аналогичный подход к ответам Карла, я предпочитаю более краткий синтаксис с меньшим количеством назначений:
def strip_whitespace
self.name.try(:strip!)
self.email.try(:strip!)
self.nick.try(:strip!)
end
Ответ 12
Так как я еще не могу прокомментировать, я должен спросить здесь: какой метод предоставляет ArgumentError? strip
, или responds_to?
Кроме того, .strip
удаляет только ведущие и завершающие пробелы. Если вы хотите, чтобы "Гарри Поттер" с двумя пробелами не принимался, вам нужно было бы использовать регулярное выражение или, проще говоря, вы могли бы вызвать .split, который удаляет пробелы, и повторно конкатенировать строку с одним пробелом.
Что касается того, что удаление является хорошей идеей, я не вижу проблемы, когда это просто ведущее/конечное пустое пространство. Если между словами есть несколько пробелов между словами, я бы уведомил пользователя вместо автоматического удаления лишних пробелов и предоставил пользователю логин, который не является тем, что они отправили.
Ответ 13
Другой вариант драгоценного камня - attribute_normalizer:
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
: strip Полоса пропускает начальные и конечные пробелы.
normalize_attribute :author, :with => :strip
Ответ 14
Начиная с Ruby 2.3.0 вы можете использовать оператор безопасной навигации (&.)
before_validation :strip_whitespace
def strip_whitespace
self.name&.strip!
self.email&.strip!
self.nick&.strip!
end
GEMS:
https://github.com/rmm5t/strip_attributes/
https://github.com/holli/auto_strip_attributes/