Rails before_validation剥离空白最佳实践

ber*_*kes 54 validation model ruby-on-rails

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

我认为在验证之前进行这种剥离是个好主意,这样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
Run Code Online (Sandbox Code Playgroud)

但是,此代码附带错误ArgumentError:错误的参数数量(0表示1).我假设回调将传递值.

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

编辑:正如评论中所指出的,我的代码是错误的(这就是我问问题的原因).除了我的问题之外,请确保您阅读接受的答案以获取正确的代码,并避免我犯的错误.

Kar*_*arl 55

我不相信这样的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
Run Code Online (Sandbox Code Playgroud)

如果你想要使用类似的东西self.columns,你可以让它变得更有活力,但这是它的要点.

  • 我添加了一个除非self.name.blank?在他们身后,避免剥离NIL值. (6认同)
  • 我意识到这是非常古老的,但我想指出两件事:首先,除了self.name.nil?`之外,我不是`self.name = self.name.strip,而是更喜欢`self.name .尝试(&:条)`.但是如果你真的想要从开头和结尾删除空格,我会找到`self.name.gsub!/(\ A\s*|\s*\z)/,''`最可靠. (4认同)

hol*_*lli 43

有几个宝石可以自动完成.这些宝石的工作方式类似于在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
Run Code Online (Sandbox Code Playgroud)

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


小智 27

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

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
Run Code Online (Sandbox Code Playgroud)

我们使用的原因

self.foo = "bar"
Run Code Online (Sandbox Code Playgroud)

代替

foo = "bar"
Run Code Online (Sandbox Code Playgroud)

在ActiveRecord对象的上下文中,Ruby将后者解释为局部变量赋值.它只会在方法范围中设置foo变量,而不是调用对象的"foo ="方法.

但是如果你正在调用一种方法,那就没有歧义了.解释器知道你没有引用一个名为foo的局部变量,因为没有.例如:

self.foo = foo + 1
Run Code Online (Sandbox Code Playgroud)

你需要使用"self"进行赋值,但不要读取当前值.

  • 我正在使用它,但使用`changed.each`而不是`attributes_names`将其限制为已更改的字段. (2认同)

emr*_*ass 19

我想补充一点你可能会遇到的上述"before_validations"解决方案的陷阱.举个例子:

u = User.new(name: " lala")
u.name # => " lala"
u.save
u.name # => "lala"
Run Code Online (Sandbox Code Playgroud)

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

class User < ActiveRecord::Base
  def name=(name)
    write_attribute(:name, name.try(:strip))
  end
end
Run Code Online (Sandbox Code Playgroud)

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


Cha*_*zak 9

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

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

结束

  • 好的解决方案 但它可以更优化:而不是attributes_names方法,我们可以使用changes.keys,所以第二次只能更改属性可以被剥离. (3认同)

emp*_*lls 9

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

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

  • **小心这个**,因为它会删除任何多个空格并删除任何换行符。在接受用户输入时,大多数时候当然不是你想要的,而且比仅仅去除前导和尾随空格更具侵入性。 (2认同)

Aja*_*jay 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
Run Code Online (Sandbox Code Playgroud)

希望能帮助别人!


art*_*dev 9

Starting with Ruby 2.3.0 you can use the Safe Navigation Operator(&.)

before_validation :strip_whitespace

def strip_whitespace
  self.name&.strip!
  self.email&.strip!
  self.nick&.strip!
end
Run Code Online (Sandbox Code Playgroud)

宝石: https:
//github.com/rmm5t/strip_attributes/
https://github.com/holli/auto_strip_attributes/


spi*_*ann 9

Ruby on Rails 7.1 引入了ActiveRecord::Base::normalizes允许定义数据清理回调的功能,如下所示:

normalizes :name, :email, :nick, with: ->(value) { value.strip }
Run Code Online (Sandbox Code Playgroud)

此方法能够处理nil值,并且当值为 时,默认情况下不会调用规范化 lambda nil。这意味着不需要额外的检查nil

当也应该对nil值进行标准化时,您可以设置apply_to_nil: true, 来执行类似的操作

normalize :name, apply_to_nil: true, with: ->(name) { name&.strip || 'NN' }
Run Code Online (Sandbox Code Playgroud)

如果设置了 a ,它将name通过去除空格来标准化属性name,否则会将 the 设置name为字符串"NN"


Ram*_*are 8

StripAttributes Gem

我使用了strip_attributes.它非常棒且易于实现.

默认行为

class DrunkPokerPlayer < ActiveRecord::Base
  strip_attributes
end
Run Code Online (Sandbox Code Playgroud)

默认情况下,这将仅删除前导和尾随空格,并将作用于模型的所有属性.这是理想的,因为它不具有破坏性,并且不需要您指定需要条带化的属性.

运用 except

# all attributes will be stripped except :boxers
class SoberPokerPlayer < ActiveRecord::Base
  strip_attributes :except => :boxers
end
Run Code Online (Sandbox Code Playgroud)

运用 only

# only :shoe, :sock, and :glove attributes will be stripped
class ConservativePokerPlayer < ActiveRecord::Base
  strip_attributes :only => [:shoe, :sock, :glove]
end
Run Code Online (Sandbox Code Playgroud)

运用 allow_empty

# Empty attributes will not be converted to nil
class BrokePokerPlayer < ActiveRecord::Base
  strip_attributes :allow_empty => true
end
Run Code Online (Sandbox Code Playgroud)

运用 collapse_spaces

# Sequential spaces in attributes will be collapsed to one space
class EloquentPokerPlayer < ActiveRecord::Base
  strip_attributes :collapse_spaces => true
end
Run Code Online (Sandbox Code Playgroud)

使用正则表达式

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
Run Code Online (Sandbox Code Playgroud)


Mat*_*lly 6

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

class MyModel
  def email=(value)
    super(value.try(:strip))
  end
end
Run Code Online (Sandbox Code Playgroud)

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


bro*_*okr 5

如果您最担心用户在前端表单中错误输入数据,那么这是另一种方法......

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

如果您尚未包含整个树,则将该文件包含在 application.js 中。

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

优点:

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

缺点:

  • 不会删除通过表单以外的任何其他方式(例如,通过 API)提交的数据
  • 没有挤压等高级功能(但您可以自己添加)
  • 正如评论中提到的,如果禁用 JS,则不起作用(但谁为此编写代码?)