Мне было интересно, как лучше всего проверять URL-адреса в Rails. Я думал использовать регулярное выражение, но не уверен, что это лучшая практика.
И, если бы я использовал регулярное выражение, мог бы кто-нибудь предложить его мне? Я еще не знаком с Regex.
Проверка URL-адреса - сложная работа. Это также очень широкий запрос.
Что вы хотите сделать именно? Вы хотите проверить формат URL-адреса, наличие или что? В зависимости от того, что вы хотите сделать, есть несколько возможностей.
Регулярное выражение может проверять формат URL-адреса. Но даже сложное регулярное выражение не может гарантировать, что вы имеете дело с действительным URL-адресом.
Например, если вы принимаете простое регулярное выражение, оно, вероятно, отклонит следующий хост
http://invalid##host.com
но это позволит
http://invalid-host.foo
который является допустимым хостом, но не действительным доменом, если вы рассматриваете существующие TLD. Действительно, решение будет работать, если вы хотите проверить имя хоста, а не домен, потому что следующее является допустимым именем хоста
http://host.foo
а также следующий
http://localhost
Теперь позвольте мне дать вам некоторые решения.
Если вы хотите проверить домен, вам нужно забыть о регулярных выражениях. На данный момент лучшим решением является список Public Suffix List, список которого поддерживается Mozilla. Я создал библиотеку Ruby для анализа и проверки доменов в списке Public Suffix List, который называется PublicSuffix.
Если вы хотите проверить формат URI/URL-адреса, вы можете использовать регулярные выражения. Вместо того, чтобы искать один, используйте встроенный метод Ruby URI.parse
.
require 'uri'
def valid_url?(uri)
uri = URI.parse(uri) && !uri.host.nil?
rescue URI::InvalidURIError
false
end
Вы даже можете сделать это более ограничительным. Например, если вы хотите, чтобы URL-адрес был URL-адресом HTTP/HTTPS, вы можете сделать проверку достоверной.
require 'uri'
def valid_url?(url)
uri = URI.parse(url)
uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
false
end
Конечно, есть тонны улучшений, которые вы можете применить к этому методу, включая проверку пути или схемы.
И последнее, но не менее важное: вы можете также упаковать этот код в валидатор:
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
URI::HTTPS
для https uris (например: URI.parse("https://yo.com").class => URI::HTTPS
URI::HTTPS
наследуется от URI:HTTP
, поэтому я использую kind_of?
,
Я использую один вкладыш внутри своих моделей:
validates :url, :format => URI::regexp(%w(http https))
Я думаю, достаточно хорош и прост в использовании. Кроме того, теоретически он должен быть эквивалентен методу Simone, поскольку он использует внутреннее внутреннее выражение.
'http://'
соответствует приведенному выше шаблону. Смотрите: URI::regexp(%w(http https)) =~ 'http://'
http:fake
.
Следуя идее Simone, вы можете легко создать свой собственный валидатор.
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
а затем используйте
validates :url, :presence => true, :url => true
в вашей модели.
Существует также validate_url gem (это просто хорошая обложка для решения Addressable::URI.parse
).
Просто добавьте
gem 'validate_url'
на ваш Gemfile
, а затем в моделях вы можете
validates :click_through_url, url: true
http://example com
действителен для этого драгоценного камня
Этот вопрос уже ответил, но, черт возьми, я предлагаю решение, которое я использую.
Регулярное выражение отлично работает со всеми адресами, которые я встречал. Метод setter должен заботиться, если не упоминается ни один протокол (предположим, что http://).
И наконец, мы попытаемся извлечь страницу. Возможно, я должен принимать перенаправления, а не только 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
и...
# 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
Только мои 2 цента:
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: изменено регулярное выражение для соответствия URL-адресам параметров.
http://test.com/fdsfsdf?a=b
Вы также можете попробовать valid_url gem, который позволяет использовать URL без схемы, проверяет доменную зону и ip-имена хостов.
Добавьте его в свой Gemfile:
gem 'valid_url'
И затем в модели:
class WebSite < ActiveRecord::Base
validates :url, :url => true
end
Решение, которое сработало для меня, было:
validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i
Я попытался использовать какой-то пример, который вы подключили, но я поддерживаю URL-адрес следующим образом:
Обратите внимание на использование A и Z, потому что, если вы используете ^ и $, вы увидите эту предупреждающую безопасность от валидаторов 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'
"https://portal.example.com/portal/#"
. В Ruby 2.1.6 оценка висит.
В последнее время я столкнулся с той же проблемой (мне нужно было проверить URL-адреса в приложении Rails), но мне пришлось справиться с дополнительным требованием URL-адресов юникода (например, http://кц.рф
)...
Я исследовал пару решений и наткнулся на следующее:
URI.parse
. Ответьте на вопрос Симона Карлетти. Это работает нормально, но не для URL-адресов Unicode.URI.parse
, но используя addressable
драгоценный камень вместо URI
stdlib. Этот подход подробно описан здесь: http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/Addressable::URI.parse('http:///').scheme # => "http"
or Addressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю')
are perfectly ok from Addressable's point of view :(
Вот обновленная версия валидатора опубликованного Дэвидом Джеймсом. Это было опубликованное Бенджамином Флейшером. Тем временем я нажал обновленную вилку, которую можно найти здесь.
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
Обратите внимание, что по-прежнему существуют странные URL-адреса URI, которые обрабатываются как действительные адреса.
http://google
http://.com
http://ftp://ftp.google.com
http://ssh://google.com
Ниже приведена проблема для addressable
gem, которая охватывает примеры.
Я использую небольшое отклонение от lafeber решения выше.
Он запрещает последовательные точки в имени хоста (например, в www.many...dots.com
):
%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,6}(/.*)?\Z"i
URI.parse
, похоже, предусматривает префикс схемы схемы, который в некоторых случаях не является тем, что вы можете захотеть (например, если вы хотите, чтобы ваши пользователи быстро произносили URL-адреса в таких формах, как twitter.com/username
)
Я использую 'activevalidators' gem, и он работает очень хорошо (не только для проверки правильности URL)
вы можете найти здесь
Все это задокументировано, но в основном после добавления драгоценного камня вам нужно добавить следующие строки в инициализаторе:/config/environments/initializers/active_validators_activation.rb
# Activate all the validators
ActiveValidators.activate(:all)
(Примечание: вы можете заменить: all by: url или: что угодно, если вы просто хотите проверить определенные типы значений)
И затем снова в вашей модели что-то вроде этого
class Url < ActiveRecord::Base
validates :url, :presence => true, :url => true
end
Теперь Перезагрузите сервер, и он должен быть
Недавно у меня была такая же проблема, и я нашел работу для действительных URL-адресов.
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
Первая часть метода validate_url достаточно, чтобы проверить формат url. Во второй части убедитесь, что URL существует, отправив запрос.
https://github.com/perfectline/validates_url - это приятный и простой камень, который сделает для вас все что угодно.
Вы можете проверить несколько URL-адресов, используя что-то вроде:
validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
Вы можете использовать regex для этого, потому что для меня это хорошо работает:
(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
Проверка URL-адресов не может быть решена просто с помощью регулярного выражения, поскольку количество веб-сайтов продолжает расти, и новые схемы именования доменов продолжают расти.
В моем случае я просто пишу специальный валидатор, который проверяет успешный ответ.
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
Я проверяю атрибут path
моей модели, используя record.path
. Я также подталкиваю ошибку к имени соответствующего атрибута, используя record.errors[:path]
.
Вы можете просто заменить это на любое имя атрибута.
Затем я просто вызываю пользовательский валидатор в своей модели.
class Url < ApplicationRecord
# validations
validates_presence_of :path
validates_with UrlValidator
end
И как модуль
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
И затем просто include UrlValidator
в любой модели, для которой вы хотите проверить URL-адрес. Просто укажите параметры.