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.
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
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.
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.
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
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
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
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.
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'
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:
- la première et la plus suggérée chose est d'utiliser
URI.parse
. Consultez la réponse de Simone Carletti pour plus de détails. Cela fonctionne bien, mais pas pour les URL unicode. - le la deuxième méthode que j'ai vu était celle d'Ilya Grigorik: http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails / fondamentalement, il essaie de faire une requête à l'url; si elle fonctionne, elle est valide...
- la troisième méthode que j'ai trouvée (et celle que je préfère) est une approche similaire à
URI.parse
mais utilisant leaddressable
gem au lieu duURI
stdlib. Cette approche est détaillée ici: http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/
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.
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
)
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
vous pouvez valider plusieurs urls en utilisant quelque chose comme:
validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
https://github.com/perfectline/validates_url est un joli petit bijou qui fera à peu près tout pour vous
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.
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.
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
vous pouvez utiliser regex pour ceci, pour moi fonctionne bien celui-ci:
(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
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'
}
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