diff --git a/episode-303/url_formatter b/episode-303/url_formatter
deleted file mode 160000
index 3716c8c8..00000000
--- a/episode-303/url_formatter
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3716c8c8b652cc07e9a22926e8b708e20978ca7d
diff --git a/episode-303/url_formatter/.gitignore b/episode-303/url_formatter/.gitignore
new file mode 100644
index 00000000..4040c6c1
--- /dev/null
+++ b/episode-303/url_formatter/.gitignore
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
diff --git a/episode-303/url_formatter/.rspec b/episode-303/url_formatter/.rspec
new file mode 100644
index 00000000..4e1e0d2f
--- /dev/null
+++ b/episode-303/url_formatter/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/episode-303/url_formatter/.travis.yml b/episode-303/url_formatter/.travis.yml
new file mode 100644
index 00000000..74889f3f
--- /dev/null
+++ b/episode-303/url_formatter/.travis.yml
@@ -0,0 +1,2 @@
+rvm:
+ - 1.9.2
\ No newline at end of file
diff --git a/episode-303/url_formatter/CHANGELOG.md b/episode-303/url_formatter/CHANGELOG.md
new file mode 100644
index 00000000..f9e5c687
--- /dev/null
+++ b/episode-303/url_formatter/CHANGELOG.md
@@ -0,0 +1,3 @@
+## v0.0.1
+
+* initial release
diff --git a/episode-303/url_formatter/Gemfile b/episode-303/url_formatter/Gemfile
new file mode 100644
index 00000000..5717c8d9
--- /dev/null
+++ b/episode-303/url_formatter/Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in url_formatter.gemspec
+gemspec
diff --git a/episode-303/url_formatter/LICENSE b/episode-303/url_formatter/LICENSE
new file mode 100644
index 00000000..883e9c10
--- /dev/null
+++ b/episode-303/url_formatter/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Ryan Bates
+
+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/episode-303/url_formatter/README.md b/episode-303/url_formatter/README.md
new file mode 100644
index 00000000..f090b750
--- /dev/null
+++ b/episode-303/url_formatter/README.md
@@ -0,0 +1,34 @@
+# URL Formatter [![Build Status](https://secure.travis-ci.org/ryanb/url_formatter.png)](http://travis-ci.org/ryanb/url_formatter)
+
+Format and validate a URL attribute in Active Record. This is an example gem created for [RailsCasts episode #301](http://railscasts.com/episodes/301-extracting-a-ruby-gem).
+
+
+## Installation
+
+Add to your Gemfile and run the `bundle` command to install it.
+
+```ruby
+gem "url_formatter"
+```
+
+**Requires Ruby 1.9.2 or later.**
+
+
+## Usage
+
+Call [format_url](http://rubydoc.info/github/ryanb/url_formatter/master/UrlFormatter/ModelAdditions:format_url) in an ActiveRecord class and pass the name of the attribute you wish to format into a URL and validate.
+
+```ruby
+class Comment < ActiveRecord::Base
+ format_url :website
+end
+```
+
+This will automatically add "http://" to the beginning of the `website` attribute upon saving if no protocol is present. It will also do validation to ensure it looks like a URL.
+
+
+## Development
+
+Questions or problems? Please post them on the [issue tracker](https://github.com/ryanb/url_formatter/issues). You can contribute changes by forking the project and submitting a pull request. You can ensure the tests passing by running `bundle` and `rake`.
+
+This gem is created by Ryan Bates and is under the MIT License.
diff --git a/episode-303/url_formatter/Rakefile b/episode-303/url_formatter/Rakefile
new file mode 100644
index 00000000..c382feeb
--- /dev/null
+++ b/episode-303/url_formatter/Rakefile
@@ -0,0 +1,6 @@
+require "bundler/gem_tasks"
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new(:spec)
+
+task default: :spec
diff --git a/episode-303/url_formatter/lib/url_formatter.rb b/episode-303/url_formatter/lib/url_formatter.rb
new file mode 100644
index 00000000..5bf7023e
--- /dev/null
+++ b/episode-303/url_formatter/lib/url_formatter.rb
@@ -0,0 +1,17 @@
+require "url_formatter/version"
+require "url_formatter/model_additions"
+require "url_formatter/railtie" if defined? Rails
+
+module UrlFormatter
+ def self.format_url(url)
+ if url.to_s !~ url_regexp && "http://#{url}" =~ url_regexp
+ "http://#{url}"
+ else
+ url
+ end
+ end
+
+ def self.url_regexp
+ /^https?:\/\/([^\s:@]+:[^\s:@]*@)?[-[[:alnum:]]]+(\.[-[[:alnum:]]]+)+\.?(:\d{1,5})?([\/?]\S*)?$/iux
+ end
+end
diff --git a/episode-303/url_formatter/lib/url_formatter/model_additions.rb b/episode-303/url_formatter/lib/url_formatter/model_additions.rb
new file mode 100644
index 00000000..be40b8e9
--- /dev/null
+++ b/episode-303/url_formatter/lib/url_formatter/model_additions.rb
@@ -0,0 +1,20 @@
+module UrlFormatter
+ module ModelAdditions
+ # To format and validate a URL attribute, call format_url
+ # in any Active Record model class and pass it the name of an attribute.
+ #
+ # class User < ActiveRecord::Base
+ # format_url :website
+ # end
+ #
+ # This will add a before_validation callback to add "http://" to
+ # the attribute if a protocol doesn't exist already. It then validates the
+ # format of the URL.
+ def format_url(attribute)
+ before_validation do
+ send("#{attribute}=", UrlFormatter.format_url(send(attribute)))
+ end
+ validates_format_of attribute, with: UrlFormatter.url_regexp, message: "is not a valid URL"
+ end
+ end
+end
diff --git a/episode-303/url_formatter/lib/url_formatter/railtie.rb b/episode-303/url_formatter/lib/url_formatter/railtie.rb
new file mode 100644
index 00000000..135b01c9
--- /dev/null
+++ b/episode-303/url_formatter/lib/url_formatter/railtie.rb
@@ -0,0 +1,9 @@
+module UrlFormatter
+ class Railtie < Rails::Railtie
+ initializer 'url_formatter.model_additions' do
+ ActiveSupport.on_load :active_record do
+ extend ModelAdditions
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/episode-303/url_formatter/lib/url_formatter/version.rb b/episode-303/url_formatter/lib/url_formatter/version.rb
new file mode 100644
index 00000000..c71a9e35
--- /dev/null
+++ b/episode-303/url_formatter/lib/url_formatter/version.rb
@@ -0,0 +1,3 @@
+module UrlFormatter
+ VERSION = "0.0.2.alpha"
+end
diff --git a/episode-303/url_formatter/spec/spec_helper.rb b/episode-303/url_formatter/spec/spec_helper.rb
new file mode 100644
index 00000000..4cdbf57b
--- /dev/null
+++ b/episode-303/url_formatter/spec/spec_helper.rb
@@ -0,0 +1,2 @@
+require 'url_formatter'
+require 'supermodel'
\ No newline at end of file
diff --git a/episode-303/url_formatter/spec/url_formatter/model_additions_spec.rb b/episode-303/url_formatter/spec/url_formatter/model_additions_spec.rb
new file mode 100644
index 00000000..672a92ba
--- /dev/null
+++ b/episode-303/url_formatter/spec/url_formatter/model_additions_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+class Comment < SuperModel::Base
+ include ActiveModel::Validations::Callbacks
+ extend UrlFormatter::ModelAdditions
+ format_url :website
+end
+
+describe UrlFormatter::ModelAdditions do
+ it "adds http:// to URL upon saving" do
+ Comment.create!(website: "example.com").website.should eq("http://example.com")
+ Comment.create!(website: "http://example.com").website.should eq("http://example.com")
+ end
+
+ it "validates URL format" do
+ comment = Comment.new(website: "foo bar")
+ comment.should_not be_valid
+ comment.errors[:website].should eq(["is not a valid URL"])
+ comment.website = "example.com"
+ comment.should be_valid
+ end
+end
diff --git a/episode-303/url_formatter/spec/url_formatter_spec.rb b/episode-303/url_formatter/spec/url_formatter_spec.rb
new file mode 100644
index 00000000..496a466a
--- /dev/null
+++ b/episode-303/url_formatter/spec/url_formatter_spec.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+require 'spec_helper'
+
+describe UrlFormatter do
+ describe ".format_url" do
+ it "adds http:// to a URL if not provided" do
+ UrlFormatter.format_url("example.com").should eq("http://example.com")
+ end
+
+ it "does not add http:// to a URL if already provided" do
+ UrlFormatter.format_url("http://example.com").should eq("http://example.com")
+ end
+
+ it "returns an invalid URL unchanged" do
+ UrlFormatter.format_url("foo bar").should eq("foo bar")
+ UrlFormatter.format_url(nil).should eq(nil)
+ end
+ end
+
+ describe ".url_regexp" do
+ it "matches valid URLs" do
+ [
+ 'http://example.com/',
+ 'HTTP://E-XAMLE.COM',
+ 'https://example.co.uk./foo',
+ 'http://example.com:8080',
+ 'http://www.example.com/anything/after?slash',
+ 'http://www.example.com?anything_after=question',
+ 'http://user123:sEcr3t@example.com',
+ 'http://user123:@example.com',
+ 'http://example.com/~user',
+ 'http://1.2.3.4:8080',
+ 'http://ütf8.com',
+ ].each do |url|
+ url.should match(UrlFormatter.url_regexp)
+ end
+ end
+
+ it "does not match invalid URLs" do
+ [
+ "www.example.com",
+ "http://",
+ "http://example..com",
+ "http://e xample.com",
+ "http://example.com/foo bar",
+ "http://example", # technically valid but not what we want from user
+ "other://example.com", # we also don't want other protocols
+ ].each do |url|
+ url.should_not match(UrlFormatter.url_regexp)
+ end
+ end
+ end
+end
diff --git a/episode-303/url_formatter/url_formatter.gemspec b/episode-303/url_formatter/url_formatter.gemspec
new file mode 100644
index 00000000..49b3422b
--- /dev/null
+++ b/episode-303/url_formatter/url_formatter.gemspec
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "url_formatter/version"
+
+Gem::Specification.new do |s|
+ s.name = "url_formatter"
+ s.version = UrlFormatter::VERSION
+ s.authors = ["Ryan Bates"]
+ s.email = ["ryan@railscasts.com"]
+ s.homepage = "https://github.com/ryanb/url_formatter"
+ s.summary = %q{Format and validate a URL in Active Record}
+ s.description = %q{Example of creating a Ruby Gem for RailsCasts episode #301}
+
+ s.rubyforge_project = "url_formatter"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_development_dependency "rake"
+ s.add_development_dependency "rspec"
+ s.add_development_dependency "supermodel"
+end