Je me demandais comment valider au mieux les URL dans 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 une regex, quelqu'un pourrait-il m'en suggérer une? Je suis encore nouveau sur Regex.
Valider une URL est un travail délicat. C'est aussi une demande très large.
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 traitez avec une URL valide.
Par exemple, si vous prenez une expression régulière simple, elle rejettera probablement l'hôte suivant.
http://invalid##Host.com
mais cela permettra
http://invalid-Host.foo
c'est un hôte valide, mais pas un domaine valide si vous considérez les TLD existants. En effet, la solution fonctionnerait si vous voulez valider le nom d’hôte, et non 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 souhaitez valider un domaine, vous devez oublier les expressions régulières. La meilleure solution disponible à l'heure actuelle est la liste de suffixes publics, une liste maintenue par Mozilla. J'ai créé une bibliothèque Ruby) pour analyser et valider les domaines par rapport à la liste de suffixes publics. Elle s'appelle PublicSuffix .
Si vous souhaitez valider le format d'un URI/URL, vous pouvez utiliser des expressions régulières. Au lieu d’en rechercher un, utilisez la commande intégrée Ruby URI.parse
méthode.
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 restrictif. Par exemple, si vous souhaitez que l'URL soit une URL HTTP/HTTPS, vous pouvez alors 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, vous pouvez appliquer de nombreuses améliorations à cette méthode, notamment la vérification d'un chemin ou d'un schéma.
Enfin et surtout, vous pouvez également intégrer 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 dans mes modèles:
validates :url, format: URI::regexp(%w[http https])
Je pense est assez bon et simple à utiliser. De plus, elle devrait être théoriquement équivalente à la méthode de Simone, car elle utilise la même expression rationnelle en 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 wrapper de Nice pour Addressable::URI.parse
Solution).
Ajoutez simplement
gem 'validate_url'
à votre Gemfile
, puis dans les modèles, vous pouvez
validates :click_through_url, url: true
On a déjà répondu à cette question, mais bon sang, je propose la solution que j'utilise.
L'expression rationnelle fonctionne bien avec toutes les URL que j'ai rencontrées. La méthode de définition est à prendre en compte si aucun protocole n'est mentionné (supposons http: //).
Et finalement, nous essayons de récupérer la page. Peut-être devrais-je 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 autorise les URL sans le schéma, vérifie la zone de domaine et les noms d'hôte ip.
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 centimes:
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 rationnelle pour qu'elle corresponde aux URL du paramètre.
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 certains des exemples que vous avez joints, mais j'appuie l'URL comme suit:
Notez l'utilisation de A et Z car si vous utilisez ^ et $, vous verrez cet avertissement de sécurité de Rails validateurs.
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 (je devais valider les URL dans un Rails app) mais je devais faire face à l'exigence supplémentaire d'URL Unicode (par exemple, http://кц.рф
) ...
J'ai recherché quelques solutions et suis tombé sur les éléments suivants:
URI.parse
. Vérifiez la réponse de Simone Carletti pour plus de détails. Cela fonctionne bien, mais pas pour les URL unicode.URI.parse
mais en utilisant le addressable
gem au lieu du URI
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 . En attendant, j'ai poussé une fourchette mise à jour qui peut être trouvée 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 toujours des URI HTTP étranges qui sont analysés comme des adresses valides.
http://google
http://.com
http://ftp://ftp.google.com
http://ssh://google.com
Voici un numéro pour le addressable
gem qui couvre les exemples.
J'utilise une légère variation sur solution de lafeber ci-dessus . Il interdit 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éfixage de schéma, ce qui dans certains cas n’est pas ce que vous souhaitiez (par exemple, si vous souhaitez autoriser vos utilisateurs à épeler rapidement des URL dans des formulaires tels que Twitter.com/username
)
J'utilise la gemme 'activevalidators' et elle fonctionne plutôt bien (pas seulement pour la validation des URL)
vous pouvez le trouver ici
Tout est documenté, mais une fois la gemme ajoutée, vous voudrez ajouter les quelques lignes suivantes dans un initialiseur, par exemple: /config/environments/initializers/active_validators_activation.rb
# Activate all the validators
ActiveValidators.activate(:all)
(Remarque: vous pouvez remplacer: tout par: url ou: peu importe si vous souhaitez simplement valider des types de valeurs spécifiques)
Et puis 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 cela devrait être le cas
Vous pouvez valider plusieurs URL en utilisant quelque chose comme:
validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
Récemment, j'ai eu le même problème et j'ai trouvé un moyen de contourner les URL 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 url. La deuxième partie s'assurera que l'URL existe en envoyant une demande.
https://github.com/perfectline/validates_url est un joyau simple et agréable qui fera à peu près tout pour vous
Si vous souhaitez 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'
}
Vous pouvez utiliser regex pour cela, pour moi ça marche bien celui-ci:
(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
La validation d'URL ne peut pas être gérée simplement à l'aide d'une expression régulière, car le nombre de sites Web ne cesse de croître et de nouveaux systèmes de dénomination de domaine continuent à apparaître.
Dans mon cas, j'écris simplement un validateur personnalisé qui vérifie la réponse.
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 vers 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
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
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 souhaitez valider l'URL. Y compris juste pour les options.