Add http(s) to URL if it's not there?

前端 未结 7 1938
抹茶落季
抹茶落季 2020-12-08 19:27

I\'m using this regex in my model to validate an URL submitted by the user. I don\'t want to force the user to type the http part, but would like to add it myself if it\'s n

相关标签:
7条回答
  • 2020-12-08 20:03

    Preface, justification and how it should be done

    I hate it when people change model in a before_validation hook. Then when someday it happens that for some reason models need to be persisted with save(validate: false), then some filter that was suppose to be always run on assigned fields does not get run. Sure, having invalid data is usually something you want to avoid, but there would be no need for such option if it wasn't used. Another problem with it is that every time you ask from a model is it valid these modifications also take place. The fact that simply asking if a model is valid may result in the model getting modified is just unexpected, perhaps even unwanted. There for if I'd have to choose a hook I'd go for before_save hook. However, that won't do it for me since we provide preview views for our models and that would break the URIs in the preview view since the hook would never get called. There for, I decided it's best to separate the concept in to a module or concern and provide a nice way for one to apply a "monkey patch" ensuring that changing the fields value always runs through a filter that adds a default protocol if it is missing.

    The module

    #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
    

    In your model

    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"
    

    As a concern

    If for some reason, you'd rather use the Rails Concern pattern it is easy to convert the above module to a concern module (it is used in an exactly similar way, except you use 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. The above approaches were tested with Rails 3 and Mongoid 2.
    P.P.S If you find this method redefinition and aliasing too magical you could opt not to override the method, but rather use the virtual field pattern, much like password (virtual, mass assignable) and encrypted_password (gets persisted, non mass assignable) and use a sanitize_url (virtual, mass assignable) and url (gets persisted, non mass assignable).

    0 讨论(0)
提交回复
热议问题