Permalink
Browse files

url will now be converted after validations, testcases added

  • Loading branch information...
1 parent 7cfc11e commit c364dcea3be052d7844b5695ce23cd8784473b0f @molte committed Jul 13, 2009
Showing with 171 additions and 34 deletions.
  1. +18 −5 README.rdoc
  2. +48 −25 lib/acts_as_url.rb
  3. +82 −1 test/acts_as_url_test.rb
  4. +23 −3 test/test_helper.rb
View
@@ -14,20 +14,33 @@ A common usecase would be in blog comments where you want the user to be able to
>> comment = Comment.new
=> #<Comment:0x245fed8 ... >
- # The protocol is added before the comment is saved to the database.
- >> comment.website = 'example.com/directory/file.html'
- => "example.com/directory/file.html"
+The protocol is added just before the comment is saved to the database (as a before_save callback).
+ >> comment.website = 'example.com/path/to/file.html'
+ => "example.com/path/to/file.html"
+
+ >> comment.save
+ => true
>> comment.website
- => "http://example.com/directory/file.html"
+ => "http://example.com/path/to/file.html"
- # The protocol is already given and is therefore not added, however a trailing slash is appended.
+In this case protocol is already given and is therefore not added, however a trailing slash is appended.
>> comment.website = 'https://example.org'
=> "https://example.org"
+ >> comment.save
+ => true
+
>> comment.website
=> "https://example.org/"
+The url will be validated before it is saved to the database.
+ >> comment.website = 'not a url'
+ => "not a url"
+
+ >> comment.invalid?
+ => true
+
== Options
By default allowed protocols are http and https, but you can set it specificly for an attribute as a hash value (with the attribute as the key):
View
@@ -4,40 +4,31 @@ def self.included(base)
base.send(:extend, ClassMethods)
end
+ mattr_accessor :default_protocols
+ self.default_protocols = %w( http https )
+
module ClassMethods
- DEFAULT_ALLOWED_PROTOCOLS = %w( http https )
-
- URL_SUBDOMAINS_PATTERN = '([-\w]+\.)+'
+ URL_SUBDOMAINS_PATTERN = '(?:[-\w]+\.)+'
URL_TLD_PATTERN = '[a-z]{2,6}'
- URL_PORT_PATTERN = '(:\d{1,5})?'
+ URL_PORT_PATTERN = '(?::\d{1,5})?'
+
+ attr_accessor :protocols
def acts_as_url(*attributes)
- protocols = attributes.last.is_a?(Hash) ? attributes.pop : {}
+ self.protocols = attributes.extract_options!
attributes += protocols.keys
attributes.each do |attribute|
# Set default protocols
- protocols[attribute] ||= DEFAULT_ALLOWED_PROTOCOLS
- # Append "://" to each protocol
- protocols[attribute] = protocols[attribute].to_a.collect { |p| p + '://' }
+ self.protocols[attribute] ||= ActsAsUrl.default_protocols
- add_url_validation(attribute, protocols[attribute])
+ add_url_validation(attribute, self.protocols[attribute].to_a)
- # Define writer method
- define_method((attribute.to_s + '=').to_sym) do |url|
- # Don't convert an empty string to a url
- write_attribute(attribute, url) and return if url.blank?
-
- # Get provided protocol if any
- provided_protocol = protocols[attribute].reject { |p| !url.starts_with?(p) }.first
- protocol_included = !provided_protocol.nil?
-
- # Make sure the host name is appended by a slash
- url += '/' unless (protocol_included ? url[provided_protocol.length, url.length - provided_protocol.length] : url).include?('/')
- # Add protocol to url if missing
- url = protocols[attribute].first + url unless protocol_included
-
- write_attribute(attribute, url)
+ # Define before save callback
+ callback_name = "convert_#{attribute}_to_url".to_sym
+ before_save callback_name
+ define_method callback_name do
+ write_attr_as_url(attribute)
end
# Define reader method
@@ -52,11 +43,43 @@ def url.to_uri
url
end
end
+
+ send(:include, InstanceMethods)
end
private
def add_url_validation(attribute, protocols)
- validates_format_of(attribute, :with => /\A(#{protocols.join('|')})#{URL_SUBDOMAINS_PATTERN + URL_TLD_PATTERN + URL_PORT_PATTERN}\/\S*\z/, :allow_blank => true)
+ validates_format_of(attribute, :with => /\A(?:(?:#{protocols.join('|')}):\/\/)?#{URL_SUBDOMAINS_PATTERN + URL_TLD_PATTERN + URL_PORT_PATTERN}(?:\/\S*)?\z/, :allow_blank => true)
+ end
+
+ end
+
+ module InstanceMethods
+
+ private
+ def write_attr_as_url(attribute)
+ url = self.send(attribute)
+ self.send((attribute.to_s + '=').to_sym, convert_to_url(attribute, url))
+ end
+
+ def convert_to_url(attribute, url)
+ # Don't try to convert an empty string to a url
+ return url if url.blank?
+
+ # Get provided protocol if any
+ provided_protocol = self.class.protocols[attribute].to_a.reject { |p| !url.starts_with?(p + '://') }.first
+ protocol_included = !provided_protocol.nil?
+
+ # Make sure the host name is appended by a slash
+ url += '/' unless (protocol_included ? url_without_protocol(url, provided_protocol + '://') : url).include?('/')
+ # Add protocol to url if missing
+ url = self.class.protocols[attribute].to_a.first + '://' + url unless protocol_included
+
+ return url
+ end
+
+ def url_without_protocol(url, protocol)
+ url[protocol.length, url.length - protocol.length]
end
end
View
@@ -1,5 +1,86 @@
require 'test_helper'
-class ActsAsUrlTest < ActiveSupport::TestCase
+class ActsAsUrlTest < Test::Unit::TestCase # ActiveSupport::TestCase
+ load_schema
+
+ class Item < ActiveRecord::Base
+ acts_as_url :website, :repository => 'git'
+ end
+
+ def test_schema_has_loaded_correctly
+ assert_not_nil Item.all
+ end
+
+ # Validation tests
+ def test_validation_when_no_protocol_or_path
+ @item.website = 'example.com'
+ assert @item.valid?
+ end
+
+ def test_validation_when_no protocol_but_path
+ @item.website = 'example.info/directory/page.html?query=true&still=true#hash'
+ assert @item.valid?
+ end
+
+ def test_validation_when_protocol
+ @item.website = 'https://example.com/'
+ assert @item.valid?
+ end
+
+ def test_validation_when_subdomains
+ @item.website = 'dk.subdomain.example.net'
+ assert @item.valid?
+ end
+
+ def test_validation_when_port
+ @item.website = 'http://example.com:8080/'
+ assert @item.valid?
+ end
+
+ def test_validation_when_no_tld
+ @item.website = 'example'
+ assert @item.invalid?
+ end
+
+ def test_validation_when_wrong_protocol
+ @item.website = 'ftp://example.com/'
+ assert @item.invalid?
+ end
+
+ def test_validation_when_blank
+ @item.website = ''
+ assert @item.valid?
+ end
+
+ # Convertion tests
+ def test_default_convertion
+ @item.website = 'example.com'
+ assert_equal 'example.com', @item.website
+
+ @item.save
+ assert_equal 'http://example.com/', @item.website
+ end
+
+ def test_custom_convertion
+ @item.repository = 'github.com/molte/acts_as_url.git'
+ @item.save
+ assert_equal 'git://github.com/molte/acts_as_url.git', @item.repository
+ end
+
+ def test_convertion_when_protocol_provided
+ @item.website = 'https://eksempel.dk'
+ @item.save
+ assert_equal 'https://eksempel.dk/', @item.website
+ end
+
+ def test_convertion_when_blank
+ @item.website = ''
+ @item.save
+ assert @item.website.blank?
+ end
+
+ def setup
+ @item = Item.new
+ end
end
View
@@ -1,3 +1,23 @@
-require 'rubygems'
-require 'active_support'
-require 'active_support/test_case'
+ENV['RAILS_ENV'] = 'test'
+ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
+
+require 'test/unit'
+require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
+
+# require 'rubygems'
+# require 'active_support'
+# require 'active_support/test_case'
+# require 'active_record'
+# require File.dirname(__FILE__) + '/../init'
+
+
+
+def load_schema
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
+
+ db_adapter = 'mysql'
+ ActiveRecord::Base.establish_connection(config[db_adapter])
+ load(File.dirname(__FILE__) + "/schema.rb")
+ require File.dirname(__FILE__) + '/../init.rb'
+end

0 comments on commit c364dce

Please sign in to comment.