Rails before_validation去除空白的最佳实践

我希望我的用户模型在保存前清理一些input。 现在一些简单的空白剥离将会做。 例如,为了避免人们注册“哈利”,假装为“哈利”。

我认为在validation之前做这个剥离是一个好主意,所以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]+\.)+[az]{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)。 我认为callback将通过值。

另外:这是剥离实际上是一个好主意? 还是应该在空间上进行validation,并告诉用户“Harry”包含无效的空格(我想允许“Harry Potter”而不是“Harry \ s \ sPotter”)。

编辑:正如在评论中指出的,我的代码是错误的(这就是为什么我问这个问题)。 请确保您阅读接受的答案,除了正确的代码我的问题,并避免我犯的同样的错误。

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

有几个gem自动做这个。 这些gem的工作方式与在before_validation中创buildcallback的方式类似。 一个很好的gem是在https://github.com/holli/auto_strip_attributes

 gem "auto_strip_attributes", "~> 1.0" class User < ActiveRecord::Base auto_strip_attributes :name, :nick, :nullify => false, :squish => true auto_strip_attributes :email end 

剥离通常是一个好主意。 尤其适用于领先和尾随的空格。 用户通常在将值复制/粘贴到表单时创build尾随空格。 使用名称和其他标识string,您也可能需要挤压string。 所以“哈利波特”将成为“哈利·波特”(在gem中压制选项)。

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

 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将后者解释为局部variables赋值。 它只是在你的方法范围中设置foovariables,而不是调用对象的“foo =”方法。

但是如果你正在调用一个方法,那么就没有歧义。 解释器知道你不是指一个叫做foo的局部variables,因为没有。 所以例如:

 self.foo = foo + 1 

您需要使用“自我”进行赋值,但不要读取当前值。

我想补充一点,您可能会遇到上述“before_validations”解决scheme。 以这个例子:

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

这意味着你有一个不一致的行为,根据你的对象是否被保存。 如果你想解决这个问题,我build议你的问题的另一个解决scheme:覆盖相应的设置方法。

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

我也喜欢这种方法,因为它不强制你为所有支持它的属性启用剥离 – 不像之前提到的attribute_names.each 。 另外,不需要callback。

相反,我们可以写一个更通用的更好的方法,不pipe可能是对象的属性types(可能有3个stringtypes的字段,很less的布尔值,很less的数字)

 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 

希望能帮助别人!

我喜欢卡尔的回答,但有没有办法做到这一点,而不是通过名称引用每个属性? 也就是说,是否有办法通过模型属性和每个呼叫条(如果它响应该方法)?

这将是可取的,所以我不必更新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 

结束

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

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

如果您主要关心用户在您的前端表单中误input数据,则可以使用另一种方法。

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

如果你还没有包含整个树,那么在你的application.js文件中包含这个文件。

这将确保每个input在被Rails提交之前被删除前后的空白。 它绑定在document ,并委托给input,所以稍后添加到页面的任何input都将被处理。

优点:

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

缺点:

  • 除了表单(例如,通过API)以外的任何其他方式提交的数据将不会被修剪
  • 没有高级function,如挤压(但你可以自己添加)
  • 正如在评论中提到的,如果JS被禁用(但谁编码?)不起作用

我用strip_attributes它真的很好,很容易实现。

 Default Behavior class DrunkPokerPlayer < ActiveRecord::Base strip_attributes end Using except # all attributes will be stripped except :boxers class SoberPokerPlayer < ActiveRecord::Base strip_attributes :except => :boxers end Using only # only :shoe, :sock, and :glove attributes will be stripped class ConservativePokerPlayer < ActiveRecord::Base strip_attributes :only => [:shoe, :sock, :glove] end Using allow_empty # Empty attributes will not be converted to nil class BrokePokerPlayer < ActiveRecord::Base strip_attributes :allow_empty => true end Using collapse_spaces # Sequential spaces in attributes will be collapsed to one space class EloquentPokerPlayer < ActiveRecord::Base strip_attributes :collapse_spaces => true end Using 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 

由于我还不能发表评论,所以我不得不在这里问:哪个方法提供了ArgumentError? strip ,还是responds_to?

此外, .strip删除前导和尾随的空白。 如果你想让“哈利波特”有两个空格不被接受,你可以使用一个正则expression式,或者更简单地说,你可以调用.split来移除空格,然后用一个空格重新连接string。

至于如果剥离是一个好主意,我不会看到一个问题,当它只是领先/尾随的空白。 如果单词之间有多个空格,我会通知用户,而不是自动删除多余的空格,并给用户一个不是他们提交的login名。

重写属性写入方法是另一个好方法。 例如:

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

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

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

虽然我可能会采取类似的方法来解决Karl的问题,但我更喜欢使用更简洁的语法,并使用更less的分配:

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