shortener 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -0
- data/Rakefile +29 -0
- data/app/controllers/shortener/shortened_urls_controller.rb +27 -0
- data/app/helpers/shortener/shortener_helper.rb +24 -0
- data/app/models/shortener/shortened_url.rb +80 -0
- data/config/routes.rb +3 -0
- data/lib/generators/shortener/shortener_generator.rb +21 -0
- data/lib/generators/shortener/templates/migration.rb +22 -0
- data/lib/shortener.rb +17 -0
- data/lib/shortener/engine.rb +10 -0
- data/lib/shortener/version.rb +3 -0
- data/shortener.gemspec +21 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +26 -0
- data/test/dummy/config/environments/production.rb +49 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/prototype.js +6001 -0
- data/test/dummy/public/javascripts/rails.js +191 -0
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/navigation_test.rb +7 -0
- data/test/shortener_test.rb +7 -0
- data/test/support/integration_case.rb +5 -0
- data/test/test_helper.rb +22 -0
- metadata +117 -0
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
.DS_Store
|
2
|
+
log/*
|
3
|
+
tmp/**/*
|
4
|
+
bin/*
|
5
|
+
|
6
|
+
config/database.yml
|
7
|
+
db/*.sqlite3
|
8
|
+
*~
|
9
|
+
public/photos/*
|
10
|
+
\#*\#
|
11
|
+
#Ignore all log files and process ID files
|
12
|
+
*.log
|
13
|
+
*.pid
|
14
|
+
|
15
|
+
#ignore all generated pshinx config files
|
16
|
+
*.sphinx.conf
|
17
|
+
|
18
|
+
#ignore all sphinx DB files
|
19
|
+
*.spa
|
20
|
+
*.spd
|
21
|
+
*.sph
|
22
|
+
*.spi
|
23
|
+
*.spk
|
24
|
+
*.spl
|
25
|
+
*.spm
|
26
|
+
*.spp
|
27
|
+
|
28
|
+
#ignore radrails files and temp files
|
29
|
+
.project
|
30
|
+
.loadpath
|
31
|
+
._*
|
32
|
+
|
33
|
+
.bundle
|
34
|
+
*.gem
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 James P. McGrath
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
= Shortener
|
2
|
+
|
3
|
+
Shortener makes it easy to create shortened URLs for your rails application.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
You can use the latest Rails 3 gem with the latest Shortener gem. In your Gemfile:
|
8
|
+
|
9
|
+
gem 'shortener'
|
10
|
+
|
11
|
+
After you install Shortener run the generator:
|
12
|
+
|
13
|
+
rails generate shortener
|
14
|
+
|
15
|
+
This generator will create a migration to create the shortened_urls table where your shortened URLs will be stored.
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
To generate a Shortened URL object for the URL "http://dealush.com" within your controller / models do the following:
|
20
|
+
|
21
|
+
Shortener::ShortenedURL.generate("http://dealush.com")
|
22
|
+
|
23
|
+
or
|
24
|
+
|
25
|
+
Shortener::ShortenedURL.generate("dealush.com")
|
26
|
+
|
27
|
+
To generate and display a shortened URL in your application use the helper method:
|
28
|
+
|
29
|
+
shortened_url("dealush.com")
|
30
|
+
|
31
|
+
This will generate a shortened URL. store it to the db and return a string representing the shortened URL.
|
32
|
+
|
33
|
+
== Notes
|
34
|
+
|
35
|
+
This is the first release and still has some bugs. I will be releasing fixes for these bugs soon.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
|
14
|
+
Rake::TestTask.new(:test) do |t|
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.libs << 'test'
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = false
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :test
|
22
|
+
|
23
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'Shortener'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README.rdoc')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Shortener
|
2
|
+
class ShortenedUrlsController < ::ApplicationController
|
3
|
+
|
4
|
+
# find the real link for the shortened link key and redirect
|
5
|
+
def translate
|
6
|
+
# pull the link out of the db
|
7
|
+
sl = ShortenedUrl.find_by_unique_key(params[:unique_key])
|
8
|
+
|
9
|
+
if sl
|
10
|
+
# don't want to wait for the increment to happen, make it snappy!
|
11
|
+
# this is the place to enhance the metrics captured
|
12
|
+
# for the system. You could log the request origin
|
13
|
+
# browser type, ip address etc.
|
14
|
+
Thread.new do
|
15
|
+
sl.increment!(:use_count)
|
16
|
+
end
|
17
|
+
# do a 301 redirect to the destination url
|
18
|
+
head :moved_permanently, :location => sl.url
|
19
|
+
else
|
20
|
+
# if we don't find the shortened link, redirect to the root
|
21
|
+
# make this configurable in future versions
|
22
|
+
head :moved_permanently, :location => root_url
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shortener::ShortenerHelper
|
2
|
+
|
3
|
+
# generate a url from either a url string, or a shortened url object
|
4
|
+
def shortened_url(url_object, user=nil)
|
5
|
+
|
6
|
+
short_url = nil
|
7
|
+
|
8
|
+
if url_object.class != String #== ShortenedUrl
|
9
|
+
if user.nil?
|
10
|
+
short_url = url_object
|
11
|
+
else
|
12
|
+
# if the user has passed in a shortened url, with a user, then
|
13
|
+
# work out the link for the shortened url and make another with the
|
14
|
+
# passed user
|
15
|
+
short_url = ShortenedUrl.generate(shortened_url(url_object), user)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
short_url = ShortenedUrl.generate(url_object, user)
|
19
|
+
end
|
20
|
+
|
21
|
+
return short_url.nil? ? nil : shortener_translate_url(short_url.unique_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Shortener
|
2
|
+
class ShortenedUrl < ActiveRecord::Base
|
3
|
+
|
4
|
+
UNIQUE_KEY_LENGTH = 5
|
5
|
+
URL_PROTOCOL_HTTP = "http://"
|
6
|
+
|
7
|
+
REGEX_HTTP_URL = /^\s*(http[s]?:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\s*$/i
|
8
|
+
REGEX_LINK_HAS_PROTOCOL = Regexp.new('\Ahttp:\/\/|\Ahttps:\/\/', Regexp::IGNORECASE)
|
9
|
+
REGEX_EMAIL = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
|
10
|
+
|
11
|
+
validates_format_of :url, :with => REGEX_HTTP_URL, :allow_blank => true
|
12
|
+
validates_presence_of :url
|
13
|
+
validates_uniqueness_of :unique_key
|
14
|
+
|
15
|
+
belongs_to :user # allows the shortened link to be associated with a user
|
16
|
+
|
17
|
+
before_validation :clean_destination_url, :init_unique_key, :on => :create
|
18
|
+
|
19
|
+
|
20
|
+
# ensure the url starts with it protocol
|
21
|
+
def clean_destination_url
|
22
|
+
if !self.url.blank? and self.url !~ REGEX_LINK_HAS_PROTOCOL
|
23
|
+
self.url.insert(0, URL_PROTOCOL_HTTP)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_unique_key
|
28
|
+
# generate a unique key for the link
|
29
|
+
begin
|
30
|
+
# has about 50 million possible combos
|
31
|
+
self.unique_key = ShortenedUrl::generate_unique_key
|
32
|
+
end while ShortenedUrl::find_by_unique_key self.unique_key
|
33
|
+
end
|
34
|
+
|
35
|
+
# generate a shortened link from a url
|
36
|
+
# link to a user if one specified
|
37
|
+
# throw an exception if anything goes wrong
|
38
|
+
def self.generate!(orig_url, user=nil)
|
39
|
+
# don't want to generate the link if it has already been generated
|
40
|
+
# so check the datastore
|
41
|
+
uid = user.nil? ? nil : user.id
|
42
|
+
sl = ShortenedUrl.find_by_url_and_user_id(orig_url, uid)
|
43
|
+
|
44
|
+
return sl if sl
|
45
|
+
|
46
|
+
# create the shortened link, storing it
|
47
|
+
sl = ShortenedUrl.create!(:url => orig_url, :user => user)
|
48
|
+
|
49
|
+
# return the url
|
50
|
+
return sl
|
51
|
+
end
|
52
|
+
|
53
|
+
# return shortened url on success, nil on failure
|
54
|
+
def self.generate(orig_url, user=nil)
|
55
|
+
|
56
|
+
sl = nil
|
57
|
+
|
58
|
+
begin
|
59
|
+
sl = ShortenedUrl::generate!(orig_url, user)
|
60
|
+
rescue
|
61
|
+
sl = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
return sl
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# generate a random string
|
72
|
+
# future mod to allow specifying a more expansive charst, like utf-8 chinese
|
73
|
+
def self.generate_unique_key(size = UNIQUE_KEY_LENGTH)
|
74
|
+
# not doing uppercase as url is case insensitive
|
75
|
+
charset = ('a'..'z').to_a + (0..9).to_a
|
76
|
+
(0...size).map{ charset.to_a[rand(charset.size)] }.join
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class ShortenerGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
def self.source_root
|
7
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.next_migration_number(dirname)
|
11
|
+
if ActiveRecord::Base.timestamped_migrations
|
12
|
+
Time.new.utc.strftime("%Y%m%d%H%M%S")
|
13
|
+
else
|
14
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_migration_file
|
19
|
+
migration_template 'migration.rb', 'db/migrate/create_shortened_urls_table.rb'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateShortenedUrlsTable < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :shortened_urls do |t|
|
4
|
+
|
5
|
+
t.integer :user_id # we can link this to a user for interesting things
|
6
|
+
t.string :url, :null => false # the real url that we will redirect to
|
7
|
+
t.string :unique_key, :null => false # the unique key
|
8
|
+
t.integer :use_count, :null => false, :default => 0 # how many times the link has been clicked
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :shortened_urls, :unique_key # we will lookup the links in the db with this
|
14
|
+
add_index :shortened_urls, :user_id # and this
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
remove_index :shortened_urls, :unique_key
|
19
|
+
remove_index :shortened_urls, :user_id
|
20
|
+
drop_table :shortened_urls
|
21
|
+
end
|
22
|
+
end
|
data/lib/shortener.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_support/dependencies"
|
2
|
+
|
3
|
+
module Shortener
|
4
|
+
|
5
|
+
# Our host application root path
|
6
|
+
# We set this when the engine is initialized
|
7
|
+
mattr_accessor :app_root
|
8
|
+
|
9
|
+
# Yield self on setup for nice config blocks
|
10
|
+
def self.setup
|
11
|
+
yield self
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# Require our engine
|
17
|
+
require "shortener/engine"
|
data/shortener.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path("../lib/shortener/version", __FILE__)
|
2
|
+
|
3
|
+
# Provide a simple gemspec so you can easily use your enginex
|
4
|
+
# project in your rails apps through git.
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "shortener"
|
7
|
+
s.summary = "Shortener makes it easy to create shortened URLs for your rails application."
|
8
|
+
s.description = "Shortener makes it easy to create shortened URLs for your rails application."
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.version = Shortener::VERSION
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.authors = [ "James P. McGrath" ]
|
13
|
+
s.email = [ "gems@jamespmcgrath.com" ]
|
14
|
+
s.homepage = "http://jamespmcgrath.com/projects/shortener"
|
15
|
+
s.rubyforge_project = "shortener"
|
16
|
+
s.required_rubygems_version = "> 1.3.6"
|
17
|
+
s.add_dependency "activesupport" , ">= 3.0.7"
|
18
|
+
s.add_dependency "rails" , ">= 3.0.7"
|
19
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
20
|
+
s.require_path = 'lib'
|
21
|
+
end
|
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
|
+
|
4
|
+
require File.expand_path('../config/application', __FILE__)
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
Dummy::Application.load_tasks
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
require "active_model/railtie"
|
4
|
+
require "active_record/railtie"
|
5
|
+
require "action_controller/railtie"
|
6
|
+
require "action_view/railtie"
|
7
|
+
require "action_mailer/railtie"
|
8
|
+
|
9
|
+
Bundler.require
|
10
|
+
require "shortener"
|
11
|
+
|
12
|
+
module Dummy
|
13
|
+
class Application < Rails::Application
|
14
|
+
# Settings in config/environments/* take precedence over those specified here.
|
15
|
+
# Application configuration should go into files in config/initializers
|
16
|
+
# -- all .rb files in that directory are automatically loaded.
|
17
|
+
|
18
|
+
# Custom directories with classes and modules you want to be autoloadable.
|
19
|
+
# config.autoload_paths += %W(#{config.root}/extras)
|
20
|
+
|
21
|
+
# Only load the plugins named here, in the order given (default is alphabetical).
|
22
|
+
# :all can be used as a placeholder for all plugins not explicitly named.
|
23
|
+
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
24
|
+
|
25
|
+
# Activate observers that should always be running.
|
26
|
+
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
27
|
+
|
28
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
29
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
30
|
+
# config.time_zone = 'Central Time (US & Canada)'
|
31
|
+
|
32
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
33
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
34
|
+
# config.i18n.default_locale = :de
|
35
|
+
|
36
|
+
# JavaScript files you want as :defaults (application.js is always included).
|
37
|
+
# config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
|
38
|
+
|
39
|
+
# Configure the default encoding used in templates for Ruby 1.9.
|
40
|
+
config.encoding = "utf-8"
|
41
|
+
|
42
|
+
# Configure sensitive parameters which will be filtered from the log file.
|
43
|
+
config.filter_parameters += [:password]
|
44
|
+
end
|
45
|
+
end
|