Подтвердить что ты не робот

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.). Пожалуйста, убедитесь, что вы прочитали принятый ответ в дополнение к моему вопросу о правильном коде и избегаете тех же ошибок, которые я сделал.

4b9b3361

Ответ 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

Надеюсь, что это поможет кому-то!

Ответ 8

StripAttributes Gem

Я использовал 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