【问题标题】:Rails before_validation strip whitespace best practicesRails before_validation 去除空格最佳实践
【发布时间】:2011-03-17 03:11:18
【问题描述】:

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

我认为在验证之前执行此剥离是个好主意,这样 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: wrong number of arguments (0 for 1)。我假设回调将传递值。

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

编辑:正如评论中指出的那样,我的代码是错误的(这就是我问这个问题 a.o. 的原因)。除了我对正确代码的问题之外,请确保您阅读接受的答案,并避免我犯的同样错误。

【问题讨论】:

  • 对于其他偶然发现此问题的人 - before_validation 没有 :only 选项。并且回调不带参数。请参阅答案了解更多详情。

标签: ruby-on-rails validation model


【解决方案1】:

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

【讨论】:

  • 我添加了一个除非 self.name.blank?在它们后面,以避免剥离 NIL 值。
  • 根据你的班级,你可以考虑ruby def strip_whitespace self.email = email.strip end
  • @berkes - 我最好添加if self.name.respond_to?(:strip)
  • 我意识到这已经很老了,但我想指出两点。首先,我更喜欢self.name = self.name.strip unless self.name.nil?,而不是self.name.try(&amp;:strip!)。但如果你真的想从头到尾删除空格,我发现self.name.gsub! /(\A\s*|\s*\z)/, '' 是最可靠的。
  • @chad_ 你为什么不发布你的答案并给出一些解释?
【解决方案2】:

有几个 gem 可以自动执行此操作。这些 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

剥离通常是个好主意。特别是对于前导和尾随空格。用户在将值复制/粘贴到表单时经常会创建尾随空格。使用名称和其他标识字符串,您可能还需要压缩字符串。这样“Harry    Potter”将变为“Harry Potter”(宝石中的挤压选项)。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我正在使用它,但使用 changed.each 而不是 attributes_names 将其限制为已更改的字段。
    【解决方案4】:

    我想补充一个您在使用上述“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 不同。此外,不需要回调。

    【讨论】:

    【解决方案5】:

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

    希望对某人有所帮助!

    【讨论】:

    • respond_to?() 是一种魅力!!!我使用的是 squish 而不是 strip,因为它还会在字符串内部转换几个空格
    【解决方案6】:

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

    这将是可取的,因此我不必在更改模型时更新 remove_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
    

    结束

    【讨论】:

    • 这看起来像是一个出色的解决方案,而且效果很好,谢谢
    • 很好的解决方案。但它可以进行更多优化:我们可以使用 changes.keys 代替 attributes_names 方法,因此第二次只能剥离更改的属性。
    【解决方案7】:

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

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

    【讨论】:

    • 该死,我以前从未注意到这种方法。一个有用的!尽管值得注意的是,您可能并不总是想使用squish 而不是strip,例如对于像博客文章这样的长文本,您可能希望保留用户的内部空白。
    • 小心这个,因为它会删除任何多个空格并删除任何换行符。在接受用户输入时,大多数时候肯定不是您想要的,而且比仅仅剥离前导和尾随空格更具侵入性。
    【解决方案8】:

    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
    

    【讨论】:

    • 感谢该 gem 的链接,但它似乎只适用于 Rails 3.2
    • @rmcshary 从当前的.gemspec 来看,它看起来可以在 Rails 3 - Rails 5 上运行:"activemodel", "&gt;= 3.0", "&lt; 6.0"
    • @JoshuaPinter 感谢您提供的信息,我想它已更新。
    【解决方案9】:

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

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

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

    【讨论】:

      【解决方案10】:

      从 Ruby 2.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/

      【讨论】:

        【解决方案11】:

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

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

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

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

        优点:

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

        缺点:

        • 通过表单以外的任何其他方式提交的数据(例如,通过 API)将不会被修剪
        • 没有像 squish 这样的高级功能(但您可以自己添加)
        • 如 cmets 中所述,如果 JS 被禁用,则不起作用(但谁为此编码?)

        【讨论】:

        • 不错的补充。谢谢。虽然这也会捕获密码,但人们可能会故意添加一个空格。之后,该空间被神奇地移除。恕我直言,任何密码字段都应从 JS 修剪中排除。
        • 不管客户端有什么验证/清理,后端必须自己做。
        【解决方案12】:

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

        另外,.strip 仅删除前导和尾随空格。如果您希望不接受带有两个空格的“哈利波特”,则必须使用正则表达式,或者更简单地说,您可以调用 .split,它会删除空格,然后用一个空格重新连接字符串。

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

        【讨论】:

        • 我的猜测是strip_whitespace 正在抛出错误。您不会将值传递给验证回调,因为记录已通过。我不相信你也能做到:only 风格。
        • 关于中间的两个空格:是的,这就是我稍后将在验证中添加的内容。关于抛出错误的方法:即 strip_whitespace 本身。
        【解决方案13】:

        另一个 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
        

        【讨论】:

          【解决方案14】:

          更好的选择是覆盖 setter 方法并使用value.squish。它更干净,您不必使用 before_validation:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-10-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多