Rails: Quelle est la bonne façon de valider les liens (URLs)?

je me demandais comment valider au mieux les URLs dans les Rails. Je pensais utiliser une expression régulière, mais je ne suis pas sûr que ce soit la meilleure pratique.

et, si je devais utiliser un regex, quelqu'un pourrait-il me le suggérer? Je suis encore nouveau à Regex.

111
demandé sur user489998 2011-08-24 01:38:07

20 réponses

valider une URL est une tâche délicate. C'est aussi une très large demande.

Que voulez-vous faire exactement? Voulez-vous valider le format de l'URL, l'existence, ou quoi? Il y a plusieurs possibilités, selon ce que vous voulez faire.

une expression régulière peut valider le format de L'URL. Mais même une expression régulière complexe ne peut pas garantir que vous avez affaire à une URL valide.

Par exemple, si vous prenez une simple expression régulière, elle rejettera probablement l'hôte suivant

http://invalid##host.com

mais il permettra

http://invalid-host.foo

qui est un hôte valide, mais pas un domaine valide, si vous considérez les gtld existants. En effet, la solution fonctionnerait si vous voulez valider le nom d'hôte, pas le domaine car le suivant est un nom d'hôte valide

http://host.foo

ainsi que le suivant

http://localhost

maintenant, laissez-moi vous donner quelques solutions.

si vous voulez valider un domaine, alors vous devez oublier les expressions régulières. La meilleure solution actuellement disponible est la liste de suffixe publique, une liste maintenue par Mozilla. J'ai créé une bibliothèque Ruby pour analyser et valider les domaines par rapport à la liste de suffixe publique, et elle s'appelle PublicSuffix .

si vous voulez valider le format D'un URI/URL, alors vous pouvez souhaitez utiliser des expressions régulières. Au lieu d'en chercher un, utilisez la méthode Ruby URI.parse intégrée.

require 'uri'

def valid_url?(uri)
  uri = URI.parse(uri) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

Vous pouvez même décider de le rendre plus restrictives. Par exemple, si vous voulez que l'URL soit une URL HTTP/HTTPS, alors vous pouvez rendre la validation plus précise.

require 'uri'

def valid_url?(url)
  uri = URI.parse(url)
  uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

bien sûr, il ya des tonnes d'améliorations que vous pouvez appliquer cette méthode, y compris la vérification d'un chemin ou d'un schéma.

dernier but vous pouvez également empaqueter ce code dans un validateur:

class HttpUrlValidator < ActiveModel::EachValidator

  def self.compliant?(value)
    uri = URI.parse(value)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end

  def validate_each(record, attribute, value)
    unless value.present? && self.class.compliant?(value)
      record.errors.add(attribute, "is not a valid HTTP URL")
    end
  end

end

# in the model
validates :example_attribute, http_url: true
121
répondu Simone Carletti 2017-01-15 23:14:04

j'utilise une doublure à l'intérieur de mes modèles:

validates :url, :format => URI::regexp(%w(http https))

, je pense, est assez bon et simple à utiliser. En outre, il devrait être théoriquement équivalent à la méthode de Simone, car il utilise le même regexp interne.

91
répondu Matteo Collina 2011-08-23 22:04:26

suivant L'idée de Simone, vous pouvez facilement créer votre propre validateur.

class UrlValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    begin
      uri = URI.parse(value)
      resp = uri.kind_of?(URI::HTTP)
    rescue URI::InvalidURIError
      resp = false
    end
    unless resp == true
      record.errors[attribute] << (options[:message] || "is not an url")
    end
  end
end

et ensuite utiliser

validates :url, :presence => true, :url => true

dans votre modèle.

51
répondu jlfenaux 2014-09-25 11:43:12

il y a aussi validate_url gem (qui est juste un joli papier d'emballage pour la solution Addressable::URI.parse ).

il suffit d'ajouter

gem 'validate_url'

à votre Gemfile , puis dans les modèles, vous pouvez

validates :click_through_url, url: true
23
répondu dolzenko 2013-10-29 13:46:59

cette question est déjà résolue, mais qu'importe, je propose la solution que j'utilise.

le regexp fonctionne très bien avec toutes les urls que j'ai rencontrées. La méthode setter est de prendre soin si aucun protocole n'est mentionné (supposons http://).

et finalement, nous essayons de récupérer la page. Peut-être que je devrais accepter les redirections et pas seulement HTTP 200 OK.

# app/models/my_model.rb
validates :website, :allow_blank => true, :uri => { :format => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix }

def website= url_str
  unless url_str.blank?
    unless url_str.split(':')[0] == 'http' || url_str.split(':')[0] == 'https'
        url_str = "http://" + url_str
    end
  end  
  write_attribute :website, url_str
end

et...

# app/validators/uri_vaidator.rb
require 'net/http'

# Thanks Ilya! http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
# Original credits: http://blog.inquirylabs.com/2006/04/13/simple-uri-validation/
# HTTP Codes: http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/classes/Net/HTTPResponse.html

class UriValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    raise(ArgumentError, "A regular expression must be supplied as the :format option of the options hash") unless options[:format].nil? or options[:format].is_a?(Regexp)
    configuration = { :message => I18n.t('errors.events.invalid_url'), :format => URI::regexp(%w(http https)) }
    configuration.update(options)

    if value =~ configuration[:format]
      begin # check header response
        case Net::HTTP.get_response(URI.parse(value))
          when Net::HTTPSuccess then true
          else object.errors.add(attribute, configuration[:message]) and false
        end
      rescue # Recover on DNS failures..
        object.errors.add(attribute, configuration[:message]) and false
      end
    else
      object.errors.add(attribute, configuration[:message]) and false
    end
  end
end
14
répondu Stefan Pettersson 2012-11-05 14:18:58

vous pouvez également essayer valid_url gem qui permet des URLs sans le schéma, vérifie la zone de domaine et ip-hostnames.

ajoutez-le à votre Gemfile:

gem 'valid_url'

et puis dans le modèle:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end
10
répondu Roman Ralovets 2014-11-18 23:07:13

Juste mes 2 cents:

before_validation :format_website
validate :website_validator

private

def format_website
  self.website = "http://#{self.website}" unless self.website[/^https?/]
end

def website_validator
  errors[:website] << I18n.t("activerecord.errors.messages.invalid") unless website_valid?
end

def website_valid?
  !!website.match(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-=\?]*)*\/?$/)
end

EDIT: modification de l'expression régulière pour rechercher paramètre d'url.

10
répondu lafeber 2015-03-03 08:36:22

la solution qui a fonctionné pour moi était:

validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i

j'ai essayé d'utiliser une partie de l'exemple que vous avez joint mais je supporte l'url comme ceci:

remarquez L'utilisation de A et Z car si vous utilisez ^ et $ vous verrez cette sécurité d'avertissement des validateurs de Rails.

 Valid ones:
 'www.crowdint.com'
 'crowdint.com'
 'http://crowdint.com'
 'http://www.crowdint.com'

 Invalid ones:
  'http://www.crowdint. com'
  'http://fake'
  'http:fake'
8
répondu heriberto perez 2015-06-26 12:42:21

j'ai rencontré le même problème récemment (j'avais besoin de valider les urls dans une application de Rails) mais j'ai dû faire face à l'exigence supplémentaire des urls unicode (par exemple http://кц.рф )...

j'ai cherché quelques solutions et je suis tombé sur ce qui suit:

5
répondu severin 2013-07-05 07:56:15

Voici une version mise à jour du validateur posté par David James . Il a été publié par Benjamin Fleischer . Pendant ce temps, j'ai poussé une fourchette mise à jour qui peut être trouvé ici .

require 'addressable/uri'

# Source: http://gist.github.com/bf4/5320847
# Accepts options[:message] and options[:allowed_protocols]
# spec/validators/uri_validator_spec.rb
class UriValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    uri = parse_uri(value)
    if !uri
      record.errors[attribute] << generic_failure_message
    elsif !allowed_protocols.include?(uri.scheme)
      record.errors[attribute] << "must begin with #{allowed_protocols_humanized}"
    end
  end

private

  def generic_failure_message
    options[:message] || "is an invalid URL"
  end

  def allowed_protocols_humanized
    allowed_protocols.to_sentence(:two_words_connector => ' or ')
  end

  def allowed_protocols
    @allowed_protocols ||= [(options[:allowed_protocols] || ['http', 'https'])].flatten
  end

  def parse_uri(value)
    uri = Addressable::URI.parse(value)
    uri.scheme && uri.host && uri
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError, TypeError
  end

end

...

require 'spec_helper'

# Source: http://gist.github.com/bf4/5320847
# spec/validators/uri_validator_spec.rb
describe UriValidator do
  subject do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :url
      validates :url, uri: true
    end.new
  end

  it "should be valid for a valid http url" do
    subject.url = 'http://www.google.com'
    subject.valid?
    subject.errors.full_messages.should == []
  end

  ['http://google', 'http://.com', 'http://ftp://ftp.google.com', 'http://ssh://google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is a invalid http url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.full_messages.should == []
    end
  end

  ['http:/www.google.com','<>hi'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['www.google.com','google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['ftp://ftp.google.com','ssh://google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("must begin with http or https")
    end
  end
end

veuillez noter qu'il existe encore D'étranges URIs HTTP qui sont interprétés comme des adresses valides.

http://google  
http://.com  
http://ftp://ftp.google.com  
http://ssh://google.com

voici un question pour les addressable gem qui couvre les exemples.

4
répondu JJD 2017-05-23 12:03:03

j'utilise une légère variation sur solution de lafeber au-dessus de . Il supprime les points consécutifs dans le nom d'hôte (comme par exemple dans www.many...dots.com ):

%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,6}(/.*)?\Z"i

URI.parse semble exiger le préfixe du schéma, ce qui dans certains cas n'est pas ce que vous voulez (par exemple si vous voulez permettre à vos utilisateurs d'écrire rapidement des URLs sous des formes telles que twitter.com/username )

3
répondu Franco 2017-05-23 12:34:44

j'ai utilisé le 'activevalidators' gem et il fonctionne assez bien (pas seulement pour la validation des urls)

vous pouvez le trouver ici

tout est documenté, mais fondamentalement une fois que le gem ajouté, vous voudrez ajouter les quelques lignes suivantes dans un initialiseur say : /config/environments/initializers/active_validators_activation.rb

# Activate all the validators
ActiveValidators.activate(:all)

(Note: Vous pouvez remplacer :tous par :l'url ou :peu importe si vous voulez juste pour valider des types spécifiques de valeurs)

et puis de retour dans votre modèle quelque chose comme ça

class Url < ActiveRecord::Base
   validates :url, :presence => true, :url => true
end

maintenant redémarrez le serveur et ça devrait être ça

2
répondu Arnaud Bouchot 2015-11-07 15:04:24

vous pouvez valider plusieurs urls en utilisant quelque chose comme:

validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
1
répondu Damien Roche 2013-03-28 16:17:46

https://github.com/perfectline/validates_url est un joli petit bijou qui fera à peu près tout pour vous

1
répondu stuartchaney 2014-03-27 22:55:04

récemment, j'ai eu ce même problème et j'ai trouvé un travail autour pour des urls valides.

validates_format_of :url, :with => URI::regexp(%w(http https))
validate :validate_url
def validate_url

  unless self.url.blank?

    begin

      source = URI.parse(self.url)

      resp = Net::HTTP.get_response(source)

    rescue URI::InvalidURIError

      errors.add(:url,'is Invalid')

    rescue SocketError 

      errors.add(:url,'is Invalid')

    end



  end

la première partie de la méthode validate_url est suffisante pour valider le format d'url. La seconde partie s'assure que l'url existe en envoyant une requête.

1
répondu Dilnavaz 2014-11-28 15:18:59

et comme module

module UrlValidator
  extend ActiveSupport::Concern
  included do
    validates :url, presence: true, uniqueness: true
    validate :url_format
  end

  def url_format
    begin
      errors.add(:url, "Invalid url") unless URI(self.url).is_a?(URI::HTTP)
    rescue URI::InvalidURIError
      errors.add(:url, "Invalid url")
    end
  end
end

et puis juste include UrlValidator dans n'importe quel modèle pour lequel vous voulez valider les URLs. Juste y compris pour les options.

0
répondu MCB 2014-12-17 01:07:13

la validation D'URL ne peut pas être traitée simplement en utilisant une Expression régulière car le nombre de sites Web continue de croître et de nouveaux systèmes de noms de domaine continuent d'apparaître.

dans mon cas, j'écris simplement un validateur personnalisé qui vérifie si la réponse est réussie.

class UrlValidator < ActiveModel::Validator
  def validate(record)
    begin
      url = URI.parse(record.path)
      response = Net::HTTP.get(url)
      true if response.is_a?(Net::HTTPSuccess)   
    rescue StandardError => error
      record.errors[:path] << 'Web address is invalid'
      false
    end  
  end
end

Je valide l'attribut path de mon modèle en utilisant record.path . Je pousse également l'erreur sur le nom d'attribut respectif en utilisant record.errors[:path] .

vous pouvez simplement remplacer ceci par n'importe quel nom d'attribut.

ensuite, j'appelle simplement le validateur personnalisé dans mon modèle.

class Url < ApplicationRecord

  # validations
  validates_presence_of :path
  validates_with UrlValidator

end
0
répondu Noman Ur Rehman 2017-01-13 15:10:01

vous pouvez utiliser regex pour ceci, pour moi fonctionne bien celui-ci:

(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
0
répondu spirito_libero 2017-06-08 11:59:16

si vous voulez une validation simple et un message d'erreur personnalisé:

  validates :some_field_expecting_url_value,
            format: {
              with: URI.regexp(%w[http https]),
              message: 'is not a valid URL'
            }
0
répondu Caleb 2018-07-17 23:13:44

j'ai aimé monkeypatch le module URI pour ajouter le valide? méthode

à l'intérieur config/initializers/uri.rb

module URI
  def self.valid?(url)
    uri = URI.parse(url)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end
end
0
répondu Blair Anderson 2018-08-21 01:33:18