Rails before_validation剥离空白最佳实践

时间:2010-07-16 19:05:31

标签: ruby-on-rails validation model

我希望我的用户模型在保存之前清理一些输入。现在一些简单的空白剥离就可以了。 因此,为了避免人们注册“哈利”并假装成“哈利”,例如。

我认为在验证之前进行剥离是个好主意,这样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)。我假设回调将传递值。

另外:这种剥离实际上是个好主意吗?或者我应该在空间上验证并告诉用户“Harry”包含无效的spacess(我想允许“Harry Potter”而不是“Harry \ s \ sPotter”)。

编辑:正如评论中指出的那样,我的代码是错误的(这就是为什么我问问题a.o.)。除了我的问题之外,请确保您已阅读接受的答案以获取正确的代码并避免出现同样的错误。

14 个答案:

答案 0 :(得分:54)

我不相信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这样的东西,你可以让它变得更有活力,但这就是它的要点。

答案 1 :(得分:41)

有几种宝石可以自动执行此操作。这些宝石的工作方式类似于在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

剥离通常是一个好主意。特别是对于前导和尾随空格。用户经常在将值复制/粘贴到表单时创建尾随空格。使用名称和其他识别字符串,您也可能想要挤压字符串。因此,“哈利波特”将成为“哈利波特”(宝石中的挤压选项)。

答案 2 :(得分:26)

查理的回答很好,但有一点点冗长。这是一个更严格的版本:

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”进行分配,但不要读取当前值。

答案 3 :(得分:18)

我想补充一点您可能会遇到的上述“before_validations”解决方案的陷阱。举个例子:

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

这意味着您根据对象是否已保存而存在不一致的行为。如果你想解决这个问题,我建议你解决另一个问题:覆盖相应的setter方法。

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

我也喜欢这种方法,因为它不会强制您为支持它的所有属性启用剥离 - 与前面提到的attribute_names.each不同。此外,不需要回调。

答案 4 :(得分:9)

我喜欢Karl的回答,但有没有办法在不引用名称的每个属性的情况下做到这一点?也就是说,有没有办法在每一个上运行模型属性和调用条(如果它响应该方法)?

这是可取的,所以每当我更改模型时,我都不必更新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

答案 5 :(得分:9)

相反,我们可以编写一个更通用的更好的方法,无论对象的属性类型是什么(可能有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

希望能有所帮助!

答案 6 :(得分:8)

如果您有权访问ActiveSupport,请使用squish而不是strip。

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

答案 7 :(得分:6)

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

使用正则表达式

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

答案 8 :(得分:4)

如果您主要关注的是用户错误输入前端表单中的数据,那么这是另一种方法......

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

如果您还没有包含整个树,请在application.js中包含该文件。

这将确保每个输入都获得领先优势。在提交由Rails保存之前删除了尾随空格。它绑定在document上,并委托给输入,因此以后添加到页面的任何输入也将被处理。

<强>优点:

  • 不要求按名称列出个别属性
  • 不需要任何元编程
  • 不需要外部库依赖

<强>缺点:

  • 除表格之外的任何其他方式提交的数据(例如,通过API)将不会被修剪
  • 没有像压扁的高级功能(但你可以自己添加)
  • 如评论中所述,如果JS被禁用(但是谁为此编码?)
  • ,则无效

答案 9 :(得分:4)

覆盖属性写入方法是另一种好方法。例如:

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

然后,设置值的应用程序的任何部分都将被剥离,包括assign_attributes等。

答案 10 :(得分:3)

虽然我可能采用类似于Karl答案的方法,但我更喜欢更简洁的语法和更少的作业:

def strip_whitespace
  self.name.try(:strip!)
  self.email.try(:strip!)
  self.nick.try(:strip!)
end

答案 11 :(得分:2)

由于我还没有评论,我将在这里问:哪个方法给出了ArgumentError? stripresponds_to?

此外,.strip仅删除前导和尾随空格。如果你想要“哈利波特”有两个空格不被接受,你要么必须使用正则表达式,要么更简单地说,你可以调用.split,删除空格,并用单个空格重新连接字符串。 / p>

如果剥离是一个好主意,当它只是前导/尾随空格时我没有看到问题。如果单词之间有多个空格,我会通知用户而不是自动删除多余的空格并给用户一个不是他们提交的登录。

答案 12 :(得分:1)

另一个宝石选项是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

答案 13 :(得分:0)

从Ruby 2.3.0开始,您可以使用安全导航操作符(&。)

before_validation :strip_whitespace

def strip_whitespace
  self.name&.strip!
  self.email&.strip!
  self.nick&.strip!
end