Rails before_validation 去掉空格的最佳实践

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

我认为在验证之前进行这种剥离是一个好主意,这样 validates _ 惟一性 _ 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”包含无效的空间(我想允许“ Harry Potter”,但不允许“ Harry s sPotter”)。

编辑: 正如注释中指出的那样,我的代码是错误的(这就是为什么我要问 a.o.)。请确保您除了阅读我的问题的正确代码,并避免同样的错误,我接受的答案。

56223 次浏览

因为我还不能注释,所以我必须在这里问: 哪个方法给出了 ArgumentError? strip还是 responds_to?

此外,.strip只删除前导空格和尾随空格。如果您希望不接受带有两个空格的“ Harry Potter”,那么您可以使用正则表达式,或者更简单地说,您可以调用。它删除空格,并用单个空格重新连接字符串。

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

我不相信 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这样的东西,你可以使它更加动态,但这就是它的要点。

我喜欢 Karl 的回答,但是有没有办法不通过名称来引用每个属性呢?也就是说,是否有一种方法可以直接运行模型属性并对每个属性调用 Strip (如果它响应该方法的话) ?

这样做是可取的,因此无论何时更改模型,我都不必更新 move _ whitespace 方法。

更新

我看到 Karl 暗示你可能想做这种事。我当时并不知道怎样才能做到这一点,但是如上所述,这里有一些对我有效的方法。也许还有更好的办法,但这个办法行得通:

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

结束

有几个宝石可以自动完成这项工作。这些 gem 的工作方式类似于在 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

脱衣舞通常是个好主意。特别是对于前导和后跟空格。用户在将值复制/粘贴到窗体时通常会创建尾随空格。对于名称和其他标识字符串,您可能还需要压缩字符串。这样,“哈利波特”将成为“哈利波特”(压选项在宝石)。

查理的回答很好,但是有点冗长。下面是一个更简洁的版本:

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

如果您最担心的是用户在前端表单中错误地输入数据,那么这里有一种替代方法..。

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

然后,如果还没有包含整个树,那么在 application.js 中包含该文件。

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

优点:

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

缺点:

  • 除表格(如通过 API)以外的其他方式提交的数据将不会被修改
  • 没有像捏这样的高级功能(但是你可以自己添加)
  • 正如注释中提到的,如果禁用 JS 就不能工作(但是谁为此编码呢?)

我想添加一个陷阱,您可能会在上面的“ 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。而且,不需要回调。

如果您可以访问 ActiveSupport,请使用 squish 而不是 Strip。

Http://api.rubyonrails.org/classes/string.html#method-i-squish

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

希望这能帮到别人!

覆盖属性 write 方法是另一种好方法,例如:

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

然后,设置该值的应用程序的任何部分都将删除该值,包括委派 _ 属性等等。

StripAttributesGem

我用的是 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

另一个 gem 选项是 属性 _ 规范化程序:

# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher

: 带条将带条前导和后跟空格。

normalize_attribute  :author, :with => :strip

从 Ruby2.3.0开始,您可以使用安全导航操作符(& .)

before_validation :strip_whitespace


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

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

更好的替代方法是覆盖 setter 方法并使用 value.squish。它比较干净,而且你不必使用 before _ valid:

class User < ActiveRecord::Base
def name=(value)
super(value.squish)
end
end