如果URL不存在,请将http添加到URL?

33 ruby regex validation url ruby-on-rails

我在我的模型中使用此正则表达式来验证用户提交的URL.我不想强迫用户输入http部分,但是如果它不在那里,我想自己添加它.

validates :url, :format => { :with => /^((http|https):\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix, :message => " is not valid" }
Run Code Online (Sandbox Code Playgroud)

知道我怎么能这样做吗?我对验证和正则表达式的经验很少..

Dou*_*rer 70

如果不存在,请使用前置过滤器添加它:

before_validation :smart_add_url_protocol

protected

def smart_add_url_protocol
  unless self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
    self.url = "http://#{self.url}"
  end
end
Run Code Online (Sandbox Code Playgroud)

保留您的验证,如果他们输错,他们可以更正协议.

  • 这不应该是验证,而只是一个`before_save`钩子.验证的目的是使实例无效(阻止它保存),这不是这里的情况. (3认同)
  • 你感到很困惑.这不是验证.这是在运行验证之前运行的方法. (3认同)

mu *_*ort 36

不要使用正则表达式,使用URI.parse它将它拉开,然后查看URL上是否有方案:

u = URI.parse('/pancakes')
if(!u.scheme)
    # prepend http:// and try again
elsif(%w{http https}.include?(u.scheme))
    # you're okay
else
    # you've been give some other kind of
    # URL and might want to complain about it
end
Run Code Online (Sandbox Code Playgroud)

使用URI库还可以轻松清除某人可能尝试放入URL的任何流浪废话(例如userinfo).

  • 我们正是为此使用可寻址的。然而,如果 URL 中没有方案,这有点奇怪,因为它认为主机是路径。 (2认同)

Mun*_*sim 5

接受的答案是可以接受的.但是如果字段(url)是可选的,它可能会引发错误,例如undefined method+ nil类.以下内容应解决:

def smart_add_url_protocol
  if self.url && !url_protocol_present?
    self.url = "http://#{self.url}"
  end
end

def url_protocol_present?
  self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
end
Run Code Online (Sandbox Code Playgroud)


Tim*_*imo 5

前言、理由以及应该如何做

我讨厌人们在before_validation钩子上改变模型。然后,当有一天由于某种原因模型需要使用 save(validate: false) 进行持久化时,一些本来应该始终在指定字段上运行的过滤器不会运行。当然,无效数据通常是您想要避免的事情,但如果不使用它,就不需要这样的选项。另一个问题是,每次您询问模型是否有效时,这些修改也会发生。事实上,简单地询问模型是否有效可能会导致模型被修改,这是意料之外的,甚至可能是不需要的。如果我必须选择一个钩子,我会选择before_save钩子。但是,这对我来说不起作用,因为我们为模型提供了预览视图,并且这会破坏预览视图中的 URI,因为挂钩永远不会被调用。因此,我决定最好将概念分离到模块或关注点中,并提供一种应用“猴子补丁”的好方法,确保更改字段值始终通过过滤器运行,该过滤器添加默认协议(如果是)丢失的。

该模块

#app/models/helpers/uri_field.rb
module Helpers::URIField
  def ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
    alias_method "original_#{field}=", "#{field}="
    define_method "#{field}=" do |new_uri|
      if "#{field}_changed?"
        if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
          new_uri = "#{default_protocol}://#{new_uri}"
        end
        self.send("original_#{field}=", new_uri)
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

在你的模型中

extend Helpers::URIField
ensure_valid_protocol_in_uri :url
#Should you wish to default to https or support other protocols e.g. ftp, it is
#easy to extend this solution to cover those cases as well
#e.g. with something like this
#ensure_valid_protocol_in_uri :url, "https", "https?|ftp"
Run Code Online (Sandbox Code Playgroud)

作为一个关心

如果出于某种原因,您更愿意使用 Rails Concern 模式,可以很容易地将上述模块转换为关注模块(它的使用方式完全相同,除了您使用include Concerns::URIField

#app/models/concerns/uri_field.rb
module Concerns::URIField
  extend ActiveSupport::Concern

  included do
    def self.ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
      alias_method "original_#{field}=", "#{field}="
      define_method "#{field}=" do |new_uri|
        if "#{field}_changed?"
          if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
            new_uri = "#{default_protocol}://#{new_uri}"
          end
          self.send("original_#{field}=", new_uri)
        end
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

PS 以上方法已使用 Rails 3 和 Mongoid 2 进行了测试。
PPS 如果您发现此方法的重新定义和别名太神奇,您可以选择不覆盖该方法,而是使用虚拟字段模式,就像密码一样(虚拟,可批量分配)和 crypto_password (持久化,不可批量分配)并使用 sanitize_url (虚拟,可批量分配)和 url (持久化,不可批量分配)。