【问题标题】:Add http(s) to URL if it's not there?如果 URL 不存在,请将 http(s) 添加到 URL?
【发布时间】:2011-12-16 01:17:38
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

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

    before_validation :smart_add_url_protocol
    
    protected
    
    def smart_add_url_protocol
      unless url[/\Ahttp:\/\//] || url[/\Ahttps:\/\//]
        self.url = "http://#{url}"
      end
    end
    

    留下你的验证,这样如果他们打错了,他们可以更正协议。

    【讨论】:

    • unless self.url[/^http?s:\/\//] 不太适合我。我不得不做unless self.url[/^http:\/\//] || self.url[/^https:\/\//]
    • 这不应该是一个验证,而只是一个before_save 钩子。验证的目的是使实例无效(阻止它保存),这里不是这种情况。
    • 你弄糊涂了。这不是验证。这是一种在运行验证之前运行的方法。
    • 我同意@koffeinfrei 的观点,即这应该在该字段已经验证之后应用,而不是之前。例如,使用该解决方案,validates :url, :presence => true 之类的东西永远不会失败,因为总是至少有 http://
    • \Ahttp(s)?:\/\/ 一个捕获http和https的正则表达式
    【解决方案2】:

    不要使用正则表达式,使用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
    

    为此使用 URI 库还可以很容易地清理任何可能有人试图放入 URL 中的杂乱无章的废话(例如 userinfo)。

    【讨论】:

    • 我们正是为此使用了 Addressable。但是,如果 URL 中没有方案,那就有点奇怪了,因为它认为主机是路径。
    • @d11wtq 我刚刚切换到 Addressable 以在 URL 中获得理智和一致的 UTF-8 支持。
    • 我必须反对这种肮脏的伎俩。它与/pancakes 这样的简单路径“工作”,但为什么有人要在路径上强制执行协议?但是,如果我们谈论的是普通人理解和编写的“网址”,那么使用 URI 将无法正确解析它们。这是因为大多数人在“网址”中省略了表示权限定义开始的双斜杠。事实上,我相信很多人认为它们属于协议定义,但事实并非如此。未完待续……(抱歉超长的双帖)
    • 当像这样的“URL”然后使用一些遵循规范的组件(如 Rubys URI)进行解析时,结果是 URI 对象中没有主机,但它被推断为整个路径。这不太可能是预期的结果,并且给人一种错误的印象,即您可以随意使用已正确解析的 URI 对象,但是如果有人要修改其任何组件,结果将令人惊讶。要将“网址”正确解析为 URI,您应始终首先确保前向破折号到位。
    • @muistooshort 好吧,您的示例缺少一些细节,但我可以看到“# prepend http:// and try again”部分的两个明显实现。更自然的解决方案u.scheme = "http"; u.to_s == "http:www.example.com" #Ooops, what happened? 或者您将其添加到原始字符串并再次解析它。 u = "http://#{orig_string}"。但是,如果有人给你一个协议相关的 URI,那么这会失败,因为你最终会得到“http:////www.example.com”并且在重新解析 u.host == nil && u.path == "//www.example.com" 之后,这是不对的,这就是为什么我认为这很危险。
    【解决方案3】:

    接受的答案很好。 但是如果字段 (url) 是可选的,它可能会引发错误,例如 undefined method + for 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
    

    【讨论】:

      【解决方案4】:

      根据 mu 的回答,这是我在模型中使用的代码。这在 :link 被保存而不需要模型过滤器时运行。需要super才能调用默认的save方法。

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

      【讨论】:

      • 顺便说一句,我想指出 interpolation 在 Ruby 中总是比 concatenation 更高效。因此,它应该是link = "http://#{_link}"
      【解决方案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
      

      在你的模型中

      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
      

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

      【讨论】:

      【解决方案6】:

      使用前面提到的一些正则表达式,这是一种覆盖模型上默认 url 的便捷方法(例如,如果您的 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
      

      【讨论】:

        【解决方案7】:

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

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

        如果还没有协议,请考虑使用回调(after_createafter_validation 等)添加协议。

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

        【讨论】:

          【解决方案8】:

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

            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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-02-15
            • 2015-10-31
            • 2014-03-06
            • 1970-01-01
            • 2012-05-11
            • 2014-11-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多