Rails before_validation удаляет лучшие практики - PullRequest
54 голосов
/ 16 июля 2010

Я бы хотел, чтобы моя пользовательская модель очистила некоторые данные перед сохранением.Пока подойдет простое удаление пробелов.Поэтому, чтобы избежать регистрации людей в «Гарри» и притворяться, например, «Гарри».

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

Также: действительно ли это удаление является хорошей идеей?Или я должен скорее проверить на месте и сказать пользователю, что «Гарри» содержит недопустимую процедуру (я хочу разрешить «Гарри Поттер», но не «Гарри \ sPotter»).

Редактировать: Как указано вкомментарий, мой код неверен (вот почему я задавал вопрос ао).Пожалуйста, убедитесь, что вы прочитали принятый ответ в дополнение к моему вопросу для правильного кода и чтобы избежать тех же ошибок, которые я сделал.

Ответы [ 14 ]

55 голосов
/ 16 июля 2010

Я не верю, 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, но в этом суть.

43 голосов
/ 28 августа 2011

Есть несколько драгоценных камней, чтобы сделать это автоматически.Эти самоцветы работают аналогично созданию обратного вызова в 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

Раздевание часто является хорошей идеей.Специально для ведущих и конечных пробелов.Пользователь часто создает конечные пробелы при копировании / вставке значения в форму.С именами и другими идентифицирующими строками вы также можете захотеть сжать строку.Так что «Гарри Поттер» станет «Гарри Поттером» (хлюпающий вариант в жемчужине).

27 голосов
/ 06 февраля 2012

Ответ Чарли хорош, но есть немного многословия. Вот более плотная версия:

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» для присваивания, но не читать текущее значение.

18 голосов
/ 17 июня 2012

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

9 голосов
/ 25 июня 2014

Вместо этого мы можем написать лучший метод, более общий, независимо от типа атрибутов объекта (может иметь 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

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

9 голосов
/ 09 декабря 2010

Мне нравится ответ Карла, но есть ли способ сделать это, не ссылаясь на каждый атрибут по имени?То есть есть ли способ просто пройти через атрибуты модели и полосу вызова для каждого из них (если он отвечает на этот метод)?

Это было бы желательно, поэтому мне не нужно обновлять метод remove_whitespaceвсякий раз, когда я меняю модель.

ОБНОВЛЕНИЕ

Я вижу, что Карл подразумевал, что вы, возможно, захотите сделать что-то подобное.Я не сразу знал, как это можно сделать, но вот кое-что, что работает для меня, как описано выше.Возможно, есть лучший способ сделать это, но это работает:

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 голосов
/ 20 апреля 2013

Если у вас есть доступ к ActiveSupport, используйте squish вместо strip.

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

6 голосов
/ 24 сентября 2014

Полосатый атрибут 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

Использование регулярных выражений

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
4 голосов
/ 05 августа 2014

Переопределение методов записи атрибутов - еще один хороший способ. Например:

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

Тогда любая часть приложения, которая устанавливает значение, будет удалена, включая assign_attributes и т. Д.

4 голосов
/ 14 июня 2012

Вот альтернативный подход, если вас больше всего интересует неправильный ввод данных пользователями в ваших формах интерфейса ...

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

Затем включите файл в файл application.js, если вы еще не включили все дерево.

Это обеспечит удаление каждого входного и конечного пробела перед отправкой для сохранения в Rails. Он ограничен document и делегирован входам, поэтому любые входы, добавленные на страницу позже, также будут обработаны.

Плюсы:

  • Не требует перечисления отдельных атрибутов по имени
  • Не требует метапрограммирования
  • Не требует внешних библиотечных зависимостей

Минусы:

  • Данные, представленные любым другим способом, кроме форм (например, через API), не будут обрезаться
  • Не имеет расширенных функций, таких как хлюпанье (но вы можете добавить это самостоятельно)
  • Как уже упоминалось в комментариях, не работает, если JS отключен (но кто это кодирует?)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...