require 'active_model' require 'active_support/i18n' require 'public_suffix' I18n.load_path += Dir[File.dirname(__FILE__) + "/locale/*.yml"] module ActiveModel module Validations class UrlValidator < ActiveModel::EachValidator RESERVED_OPTIONS = [:schemes, :no_local] def initialize(options) options.reverse_merge!(schemes: %w(http https)) options.reverse_merge!(message: :url) options.reverse_merge!(no_local: false) options.reverse_merge!(public_suffix: false) super(options) end def validate_each(record, attribute, value) schemes = [*options.fetch(:schemes)].map(&:to_s) begin uri = URI.parse(value) host = uri && uri.host scheme = uri && uri.scheme valid_suffix = !options.fetch(:public_suffix) || (host && PublicSuffix.valid?(host, :default_rule => nil)) valid_no_local = !options.fetch(:no_local) || (host && host.include?('.')) valid_scheme = host && scheme && schemes.include?(scheme) unless valid_scheme && valid_no_local && valid_suffix record.errors.add(attribute, options.fetch(:message), value: value) end rescue URI::InvalidURIError record.errors.add(attribute, :url, filtered_options(value)) end end protected def filtered_options(value) filtered = options.except(*RESERVED_OPTIONS) filtered[:value] = value filtered end end module ClassMethods # Validates whether the value of the specified attribute is valid url. # # class Unicorn # include ActiveModel::Validations # attr_accessor :homepage, :ftpsite # validates_url :homepage, allow_blank: true # validates_url :ftpsite, schemes: ['ftp'] # end # Configuration options: # * :message - A custom error message (default is: "is not a valid URL"). # * :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']+) def validates_url(*attr_names) validates_with UrlValidator, _merge_attributes(attr_names) end end end end