diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ea5798 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +Gemfile.lock diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..b4e2a20 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ef69dd9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Yury Hapanovich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..36284a6 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# ValidatesUrlFormat + +This gem helps to validate URLs using ActiveModel. + +## Installation + +Add this to your `Gemfile`: + +```ruby +gem 'validates_url_format' +``` +And then execute: + +```sh +bundle install +``` + +Or install it yourself: + +```sh +gem install validates_url_format +``` + +## Usage + +### With ActiveRecord +```ruby +class Model < ActiveRecord::Base + attr_accessor :url, :second_url + + validates_url_format_of :url, allow_blank: true + validates :second_url, url_format: { allow_blank: true } +end +``` + +### With ActiveModel + +```ruby +class Model + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, allow_blank: true +end +``` + +Configuration options: +- :messages - A custom error messages hash. Default is: + DEFAULT_MESSAGES = { + valid_url: 'is a valid URL', + invalid_url: 'is not a valid URL', + nil_or_blank_url: 'is nil or blank URL', + invalid_scheme: 'a URL has invalid scheme', + invalid_userinfo: 'a URL has invalid user info', + local_url: 'is a local URL', + space_symbol: 'a URL has space symbol', + public_suffix: 'a URL is invalid by public suffix' + } +- :allow_nil - If set to true, skips this validation if the attribute is nil (default is false). +- :allow_blank - If set to true, skips this validation if the attribute is blank (default is false). +- :schemes - Array of URI schemes to validate against. (default is ['http', 'https']) +- :public_suffix - If set to true, validates domain name by public suffix. (default is false) +- :no_local - If set to true, filtrates local adresses. (default is false) + +### Plain Ruby + +```ruby +ValidatesUrlFormat::Validator.new(options).validate(value) +``` +Returns hash { valid: (true or false), message: message_symbol } +Message symbols: :valid_url, :invalid_url, :nil_or_blank_url, :invalid_scheme, + :invalid_userinfo, :local_url, :space_symbol, :public_suffix +Options: +- :schemes - Array of URI schemes to validate against. (default is ['http', 'https']) +- :public_suffix - If set to true, validates domain name by public suffix. (default is false) +- :no_local - If set to true, filtrates local adresses. (default is false) + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/validates_url_format. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/validates_url_format.rb b/lib/validates_url_format.rb new file mode 100644 index 0000000..fbe2979 --- /dev/null +++ b/lib/validates_url_format.rb @@ -0,0 +1,54 @@ +require 'active_model' +require 'validates_url_format/validator' + +module ActiveModel + module Validations + class UrlFormatValidator < ActiveModel::EachValidator + DEFAULT_MESSAGES = { + valid_url: 'is a valid URL', + invalid_url: 'is not a valid URL', + nil_or_blank_url: 'is nil or blank URL', + invalid_scheme: 'a URL has invalid scheme', + invalid_userinfo: 'a URL has invalid user info', + local_url: 'is a local URL', + space_symbol: 'a URL has space symbol', + public_suffix: 'a URL is invalid by public suffix' + } + DEFAULT_SCHEMES = %w(http https) + + def initialize(options) + options[:messages] = (options[:messages] || {}).reverse_merge(DEFAULT_MESSAGES) + options.reverse_merge!(no_local: false, public_suffix: false) + + super(options) + end + + def validate_each(record, attribute, value) + return record.errors.add(attribute, options.dig(:messages, :invalid_url), value: value) unless value.is_a?(String) + validation_result = ValidatesUrlFormat::Validator.new(options).validate(value) + record.errors.add(attribute, options.dig(:messages, validation_result[:message]), value: value) unless validation_result[:valid] + end + end + + module ClassMethods + # Validates whether the value of the specified attribute is valid url. + # + # class Model + # include ActiveModel::Validations + # validates_url_format_of :homepage, allow_blank: true, schemes: ['ftp'] + # end + # + # Configuration options: + # :messages - A custom error messages (default is DEFAULT_MESSAGES). + # :allow_nil - If set to true, skips this validation if the attribute is nil (default is false). + # :allow_blank - If set to true, skips this validation if the attribute is blank (default is false). + # :schemes - Array of URI schemes to validate against. (default is ['http', 'https']) + # :public_suffix - If set to true, validates domain name by public suffix. (default is false) + # :no_local - If set to true, filtrates local adresses. (default is false) + + def validates_url_format_of(*attr_names) + validates_with UrlFormatValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/lib/validates_url_format/validator.rb b/lib/validates_url_format/validator.rb new file mode 100644 index 0000000..b98a3a8 --- /dev/null +++ b/lib/validates_url_format/validator.rb @@ -0,0 +1,124 @@ +require 'public_suffix' +require 'ipaddr' + +module ValidatesUrlFormat + class Validator + IPv4_PART = /\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]/ # 0-255 + IPv4_REGEXP = %r{\A(#{IPv4_PART}(\.#{IPv4_PART}){3})\z} + IPv6_REGEXP = %r{ + ( + ([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}| # 1:2:3:4:5:6:7:8 + ([0-9a-fA-F]{1,4}:){1,7}:| # 1:: 1:2:3:4:5:6:7:: + ([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}| # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + ([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}| # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + ([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}| # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + ([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}| # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + ([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}| # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + [0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})| # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + :((:[0-9a-fA-F]{1,4}){1,7}|:)| # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}| # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) + ::(ffff(:0{1,4}){0,1}:){0,1} + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + ([0-9a-fA-F]{1,4}:){1,4}: + ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3} + (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) + ) + }x + ACCEPTED_SCRIPTS = '\p{Common}\p{Latin}\p{Cyrillic}\p{Arabic}\p{Georgian}' + # A TLD's maximum length is 63 characters. https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_domain_name + DOMAINNAME_REGEXP = %r{ + \A(xn--)?[#{ACCEPTED_SCRIPTS}_]+([-._][#{ACCEPTED_SCRIPTS}]+)*\.[^\d&&[#{ACCEPTED_SCRIPTS}]]{2,63}\.?\z + }x + ONE_LEVEL_DOMAINNAME_REGEX = %r{\A[^.&&[#{ACCEPTED_SCRIPTS}]]*\z} + USERINFO_REGEXP = %r{\A[^:&&[#{ACCEPTED_SCRIPTS}]]+:?[#{ACCEPTED_SCRIPTS}]*\z} + + LOCAL_TOP_DOMAINS = %W(local localhost intranet internet internal private corp home lan) + + DEFAULT_SCHEMES = %w(http https) + + attr_accessor :options + + def initialize(options = {}) + @options = options + end + + def validate(value) + return result(false, :nil_or_blank_url) if value.blank? + + schemes = (options[:schemes] || DEFAULT_SCHEMES).map(&:to_s) + encoded_value = URI.encode(value) + uri = URI.parse(encoded_value) + host = uri && uri.host && URI.decode(uri.host) + scheme = uri && uri.scheme&.downcase + + return result(false, :invalid_scheme) unless host && scheme && schemes.include?(scheme) + return result(false, :invalid_userinfo) unless uri.userinfo.nil? || uri.userinfo.match?(USERINFO_REGEXP) + + case host + when IPv6_REGEXP + # TODO: Add IPv6 local addresses filtration + result(true, :valid_url) + when IPv4_REGEXP + return result(false, :local_url) if filter_local? && ipv4_local_address?(host) + + result(true, :valid_url) + when ONE_LEVEL_DOMAINNAME_REGEX + return result(false, :invalid_url) unless domainname_local_address?(host) + return result(false, :local_url) if filter_local? + + result(true, :valid_url) + when DOMAINNAME_REGEXP + return result(false, :space_symbol) if value.include?(' ') + return result(false, :local_url) if filter_local? && domainname_local_address?(host) + return result(false, :public_suffix) if check_by_publicsuffix? && !PublicSuffix.valid?(host, :default_rule => nil) + + result(true, :valid_url) + else + result(false, :invalid_url) + end + rescue URI::InvalidURIError + result(false, :invalid_url) + end + + private + + def result(valid, message) + { valid: valid, message: message } + end + + def not_allowed_nil_or_blank?(value) + (value.nil? && !options[:allow_nil]) || + (value.blank? && !options[:allow_blank]) + end + + def filter_local? + options[:no_local] + end + + def check_by_publicsuffix? + options[:public_suffix] + end + + def ipv4_local_address?(value) + ip = IPAddr.new(value) + # 127.0.0.0 - 127.255.255.255 loopback + # 10.0.0.0 - 10.255.255.255 private + # 192.168.0.0 - 192.168.255.255 private + # 172.16.0.0 - 172.31.255.255 private + # 169.254.0.0 - 169.254.255.255 link-local + return true if ip.loopback? || ip.private? || ip.link_local? + return true if ip == '0.0.0.0' # unknown or non-applicable target + return true if ip == '255.255.255.255' # local broadcast + + false + end + + def domainname_local_address?(value) + top_level_domain = value.split('.').last + return true if LOCAL_TOP_DOMAINS.include?(top_level_domain) + + false + end + end +end diff --git a/lib/validates_url_format/version.rb b/lib/validates_url_format/version.rb new file mode 100644 index 0000000..2e5cf9d --- /dev/null +++ b/lib/validates_url_format/version.rb @@ -0,0 +1,3 @@ +module ValidatesUrlFormat + VERSION = '0.1.0' +end diff --git a/spec/models/model.rb b/spec/models/model.rb new file mode 100644 index 0000000..7b1e2de --- /dev/null +++ b/spec/models/model.rb @@ -0,0 +1,7 @@ +class Model + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url +end diff --git a/spec/models/model_allow_blank.rb b/spec/models/model_allow_blank.rb new file mode 100644 index 0000000..8e0b680 --- /dev/null +++ b/spec/models/model_allow_blank.rb @@ -0,0 +1,7 @@ +class ModelAllowBlank + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, allow_blank: true +end diff --git a/spec/models/model_allow_nil.rb b/spec/models/model_allow_nil.rb new file mode 100644 index 0000000..cdc3ed6 --- /dev/null +++ b/spec/models/model_allow_nil.rb @@ -0,0 +1,7 @@ +class ModelAllowNil + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, allow_nil: true +end diff --git a/spec/models/model_custom_scheme.rb b/spec/models/model_custom_scheme.rb new file mode 100644 index 0000000..7dc983d --- /dev/null +++ b/spec/models/model_custom_scheme.rb @@ -0,0 +1,7 @@ +class ModelCustomScheme + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, schemes: ['ftp'] +end diff --git a/spec/models/model_no_local.rb b/spec/models/model_no_local.rb new file mode 100644 index 0000000..e164f88 --- /dev/null +++ b/spec/models/model_no_local.rb @@ -0,0 +1,7 @@ +class ModelNoLocal + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, no_local: true +end diff --git a/spec/models/model_on_create.rb b/spec/models/model_on_create.rb new file mode 100644 index 0000000..ff5b6f9 --- /dev/null +++ b/spec/models/model_on_create.rb @@ -0,0 +1,7 @@ +class ModelOnCreate + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, on: :create +end diff --git a/spec/models/model_public_suffix.rb b/spec/models/model_public_suffix.rb new file mode 100644 index 0000000..6c55b45 --- /dev/null +++ b/spec/models/model_public_suffix.rb @@ -0,0 +1,7 @@ +class ModelPublicSuffix + include ActiveModel::Validations + + attr_accessor :url + + validates_url_format_of :url, public_suffix: true +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..2a482d0 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,19 @@ +require 'rspec' +require 'active_record' +require 'validates_url_format' + +ActiveRecord::Migration.verbose = false +ActiveRecord::Base.establish_connection( + 'adapter' => 'sqlite3', + 'database' => ':memory:' +) + +autoload :Model, 'models/model' +autoload :ModelNoLocal, 'models/model_no_local' +autoload :ModelAllowBlank, 'models/model_allow_blank' +autoload :ModelAllowNil, 'models/model_allow_nil' +autoload :ModelCustomScheme, 'models/model_custom_scheme' +autoload :ModelPublicSuffix, 'models/model_public_suffix' +autoload :ModelOnCreate, 'models/model_on_create' + +RSpec.configure(&:disable_monkey_patching!) diff --git a/spec/url_format_validator_spec.rb b/spec/url_format_validator_spec.rb new file mode 100644 index 0000000..5cc146b --- /dev/null +++ b/spec/url_format_validator_spec.rb @@ -0,0 +1,218 @@ +RSpec.describe 'URL format validation using Active Record' do + before(:all) do + ActiveRecord::Schema.define(version: 1) do + create_table :models, force: true do |table| + table.column :url, :string + end + end + end + + after(:all) do + ActiveRecord::Base.connection.drop_table(:models) + end + + let!(:model) { Model.new } + + [ + 'http://example.com', + 'https://example.com', + 'http://d124.example.com', + 'http://333.example.com', + 'http://example345.com', + 'http://example.com/', + 'http://www.example.com/', + 'http://sub.domain.example.com/', + 'http://bbc.co.uk', + 'http://example.com?foo', + 'http://example.com?url=http://example.com', + 'http://example.com:8000', + 'http://www.sub.example.com/page.html?foo=bar&baz=%23#anchor', + 'http://example.com/~user', + 'http://example.xy', + 'http://example.museum', + 'http://1.0.255.249', + 'http://1.2.3.4:80', + 'HttP://example.com', + 'https://example.com', + 'http://xn--rksmrgs-5wao1o.nu', # Punycode + 'http://example.com.', # Explicit TLD root period + 'http://example.com./foo', + 'http://example.cancerresearch', + 'http://example.solutions', + 'http://_test.example.com', + 'http://test.exa_mple.com', + 'http://кириллица.рф', + 'http://тест.бел', + 'http://test.қаз', + 'http://example.გე', + 'http://foo_bar.com', + 'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', + 'http://[2001:DB8::1]', + 'http://[::ffff:c000:0280]', + 'http://1k.by', + 'http://11.22.com', + 'http://1.1.1.com', + 'http://2.2.2.2.com', + 'http://user:pass@example.com', + 'http://user:@example.com', + 'http://u:u:u@example.com', # password has : inside + 'http://u@example.com', # userinfo contains only username + 'http://localhost:3128', + 'http://ecom.com/pa^h?foo=bar' + ].each do |url| + it "allows url #{url}" do + model.url = url + expect(model).to be_valid + end + end + + [ + nil, 1, "", " ", "url", + "www.example.com", # without scheme + 'mailto:foo@example.org', + 'http://', # only a scheme + 'http:/', # without a host + "http://ex ample.com", # space in the hostname + "http://example.com/foo bar", + "http://example.com/some/? doodads=ok", # space in the querystring + 'http://256.0.0.1', # wrong number in ip + 'http://e?om.com', # wrong symbol + 'ftp://localhost', # wrong scheme + "http://example", # without top level domain + "http://example.c", # too short TLD length + 'http://example.toolongtlddddddddddddddddddddddddddddddddddddddddddddddddddddddd', # A TLD length is 64 characters + ["https://foo.com", "https://bar.com"], # an array of urls + 'http://[2001:0db8:85a3:0000:0000:8a2e:7334]' # 7 blocks in ipv6 address + ].each do |url| + it "does not allow url #{url}" do + model.url = url + expect(model).not_to be_valid + end + end + + it 'returns a default error message' do + model.url = 'http://invalid' + model.valid? + expect(model.errors[:url]).to eq(['is not a valid URL']) + end + + context 'with no_local: true' do + let!(:model) { ModelNoLocal.new } + + [ + 'http://127.1.1.1', + 'http://10.1.1.1', + 'http://172.20.1.1', + 'http://192.168.1.1', + 'http://0.0.0.0', + 'http://255.255.255.255', + 'http://169.254.0.0', + 'http://example.local', + 'http://example.test.localhost', + 'http://example.intranet', + 'http://example.internal', + 'http://example.corp', + 'http://example.home', + 'http://example.lan', + 'http://example.private', + 'http://localhost:3008' + ].each do |url| + it "does not allow local url #{url}" do + model.url = url + expect(model).not_to be_valid + end + end + end + + context 'with allow_nil: true' do + let!(:model) { ModelAllowNil.new } + + it 'allows nil url' do + model.url = nil + expect(model).to be_valid + end + + it 'does not allow blank url' do + model.url = '' + expect(model).not_to be_valid + end + end + + context 'with allow_blank: true' do + let!(:model) { ModelAllowBlank.new } + + it 'allows blank url' do + model.url = '' + expect(model).to be_valid + end + + it 'allows nil url' do + model.url = nil + expect(model).to be_valid + end + end + + context 'with custom schemes' do + let!(:model) { ModelCustomScheme.new } + let(:url) { 'ftp://example.com' } + + it 'allows url with custom scheme' do + model.url = url + expect(model).to be_valid + end + end + + context 'with public_suffix: true' do + let!(:model) { ModelPublicSuffix.new } + + [ + 'http://example.com', + 'http://d124.example.com', + 'http://333.example.com', + 'http://example345.com', + 'http://www.example.com/', + 'http://sub.domain.example.com/', + 'http://bbc.co.uk', + 'http://www.sub.example.com', + 'http://example.museum', + 'http://xn--rksmrgs-5wao1o.nu', # Punycode + 'http://example.com.', # Explicit TLD root period + 'http://example.cancerresearch', + 'http://example.solutions', + 'http://_test.example.com', + 'http://test.exa_mple.com', + 'http://кириллица.рф', + 'http://тест.бел', + 'http://test.қаз', + 'http://example.გე', + 'http://foo_bar.com', + 'http://1k.by', + 'http://11.22.com', + 'http://1.1.1.com', + 'http://2.2.2.2.com' + ].each do |url| + it "allows url #{url}" do + model.url = url + expect(model).to be_valid + end + end + + context 'when private domain' do + let(:url) { 'http://blogspot.com' } + + it 'does not allow' do + model.url = url + expect(model).not_to be_valid + end + end + + context 'when url with not listed TLD' do + let(:url) { 'http://example.tldnotlisted' } + + it 'does not allow' do + model.url = url + expect(model).not_to be_valid + end + end + end +end diff --git a/spec/validates_url_format/validator_spec.rb b/spec/validates_url_format/validator_spec.rb new file mode 100644 index 0000000..e5f05ee --- /dev/null +++ b/spec/validates_url_format/validator_spec.rb @@ -0,0 +1,35 @@ +RSpec.describe 'URL format validation' do + let(:url) { 'http://example.com' } + let(:options) { {} } + + subject { ValidatesUrlFormat::Validator.new(options).validate(url) } + + it 'returns success valid status and valid message' do + expect(subject).to eq({ valid: true, message: :valid_url }) + end + + context 'when wrong url' do + let(:url) { 'ftp://example.com' } + + it 'returns failed valid status and error message' do + expect(subject).to eq({ valid: false, message: :invalid_scheme }) + end + end + + context 'when options passed' do + let(:url) { 'ftp://example.com' } + let(:options) { { schemes: ['ftp'] } } + + it 'validates considering options' do + expect(subject).to eq({ valid: true, message: :valid_url }) + end + end + + context 'nil value' do + let(:url) { nil } + + it 'returns failed valid status and error message' do + expect(subject).to eq({ valid: false, message: :nil_or_blank_url }) + end + end +end diff --git a/validates_url_format.gemspec b/validates_url_format.gemspec new file mode 100644 index 0000000..7cf7618 --- /dev/null +++ b/validates_url_format.gemspec @@ -0,0 +1,28 @@ +require_relative 'lib/validates_url_format/version' + +Gem::Specification.new do |spec| + spec.name = 'validates_url_format' + spec.version = ValidatesUrlFormat::VERSION + spec.authors = ['Yury Hapanovich', 'EComCharge'] + spec.email = ['yury.gapanovich@ecomcharge.com'] + + spec.summary = 'Library for validating urls using ActiveModel.' + spec.description = 'Library for validating urls using ActiveModel.' + spec.homepage = 'https://github.com/uragap/validates_url_format' + spec.license = 'MIT' + spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/uragap/validates_url_format' + spec.metadata['changelog_uri'] = 'https://github.com/uragap/validates_url_format' + + spec.files = `git ls-files`.split("\n") + spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + spec.require_paths = ['lib'] + + spec.add_dependency 'activerecord', '>= 3.2' + spec.add_dependency 'public_suffix' + spec.add_development_dependency 'sqlite3' + spec.add_development_dependency 'pry-byebug' + spec.add_development_dependency 'rspec', '~> 3.0' +end