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

时间:2011-10-26 20:35:09

标签: ruby-on-rails ruby regex validation url

我在我的模型中使用此正则表达式来验证用户提交的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" }

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

8 个答案:

答案 0 :(得分:69)

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

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

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

答案 1 :(得分:36)

不要使用正则表达式执行此操作,使用URI.parse将其拆分,然后查看网址上是否有方案:

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

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

答案 2 :(得分: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

答案 3 :(得分:4)

根据mu的回答,这是我在模型中使用的代码。这在以下情况下运行:保存链接而无需模型过滤器。要求Super调用默认保存方法。

def link=(_link)
    u=URI.parse(_link)

    if (!u.scheme)
        link = "http://" + _link
    else
        link = _link
    end
    super(link)
end

答案 4 :(得分:4)

前言,理由以及如何完成

当人们在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

在你的模特中

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"

关注

如果由于某种原因,你宁愿使用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

P.S。用Rails 3和Mongoid 2测试了上述方法 PPS如果您发现此方法重新定义和别名过于神奇,您可以选择不覆盖该方法,而是使用虚拟字段模式,非常类似于密码(虚拟,可分配质量)和encrypted_pa​​ssword(获取持久性,非质量可分配)并使用sanitize_url(虚拟,质量可分配)和url(获取持久化,非质量可分配)。

答案 5 :(得分:1)

使用上面提到的一些正则表达式,这里有一个方便的方法来覆盖模型上的默认网址(例如,如果你的ActiveRecord模型有一个' url'列)

def url
  _url = read_attribute(:url).try(:downcase)
  if(_url.present?)
    unless _url[/\Ahttp:\/\//] || _url[/\Ahttps:\/\//]
      _url = "http://#{_url}"
    end
  end
_url
end

答案 6 :(得分:0)

我不会尝试在验证中这样做,因为它不是验证的一部分。

验证是否可选择检查;如果他们搞砸了,那将是一个验证错误,这很好。

考虑使用回调(after_createafter_validation,无论如何),如果没有协议,则使用前置协议。

(我投了其他答案;我认为他们都比我的好。但这是另一种选择:)

答案 7 :(得分:0)

我必须为同一模型上的多个列执行此操作。

  before_validation :add_url_protocol

  def add_url_protocol
    [
      :facebook_url, :instagram_url, :linkedin_url,
      :tiktok_url, :youtube_url, :twitter_url, :twitch_url
    ].each do |url_method|
      url = self.send(url_method)

      if url.present? && !(%w{http https}.include?(URI.parse(url).scheme))
        self.send("#{url_method.to_s}=", 'https://'.concat(url)) 
      end
    end
  end