shortener 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0ff66256668eb71d5b42010c191233c7346c2651
4
- data.tar.gz: f4cf3ffadd4b4f74ae6076be7f9fa13edf55b5ea
2
+ SHA256:
3
+ metadata.gz: 12594d11b56f84e7c4654bee681887dc084978604ee158c803e384c839b6a937
4
+ data.tar.gz: 8550a33e8bd06229cd66c5e9f340ba6cd9c49c77acb5481555b23f43ea659883
5
5
  SHA512:
6
- metadata.gz: c88fd4cda719d6759af9f1faa5fc70829f18ff99662f77dc10ae33f20e9debcfabd051c5fe7157a75b505f45d164752cd40ead26ed110be6c09c1427e81e382e
7
- data.tar.gz: 0e4ae113ede69db12a0b9901747bb20bb0ed4a72e51817e619524c166cc8cf1a8159f69c0352af843a48650773c3aa98e62d92a44f5e5ffcbcfd083265a0c629
6
+ metadata.gz: 911ad7cc429c015d090760690b53aa5151588f3c384090f293cd608484d0804ba3e72034c97bc31cb99730deabf5c5103045e0ffd89b809dd7c12030b732b57e
7
+ data.tar.gz: 8fe1bd76f2a97dc3a9f2b0c1cfe9b4798591f84654f40eb341724df41e1ed6b4f8ae461d54759b2059163e440cf72b81df8246a7a7715cc68cecc57d3279a1c6
@@ -0,0 +1,66 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ develop ]
6
+ pull_request:
7
+ branches: [ develop ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: true
14
+ matrix:
15
+ ruby-version:
16
+ - '2.4'
17
+ - '2.6'
18
+ - '2.7'
19
+ - '3.0'
20
+ gemfile:
21
+ - rails_4.gemfile
22
+ - rails_5.0.gemfile
23
+ - rails_5.1.gemfile
24
+ - rails_5.2.gemfile
25
+ - rails_6.0.gemfile
26
+ - rails_6.1.gemfile
27
+ - rails_7.0.gemfile
28
+ include:
29
+ - ruby-version: '2.4'
30
+ gemfile: rails_4.gemfile
31
+ bundler-version: 1
32
+ exclude:
33
+ - ruby-version: '2.6'
34
+ gemfile: rails_4.gemfile
35
+ - ruby-version: '2.7'
36
+ gemfile: rails_4.gemfile
37
+ - ruby-version: '3.0'
38
+ gemfile: rails_4.gemfile
39
+ - ruby-version: '3.0'
40
+ gemfile: rails_5.0.gemfile
41
+ - ruby-version: '3.0'
42
+ gemfile: rails_5.1.gemfile
43
+ - ruby-version: '3.0'
44
+ gemfile: rails_5.2.gemfile
45
+ - ruby-version: '2.4'
46
+ gemfile: rails_6.0.gemfile
47
+ - ruby-version: '2.4'
48
+ gemfile: rails_6.1.gemfile
49
+ - ruby-version: '2.4'
50
+ gemfile: rails_7.0.gemfile
51
+ - ruby-version: '2.6'
52
+ gemfile: rails_7.0.gemfile
53
+
54
+ env:
55
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}
56
+
57
+ steps:
58
+ - uses: actions/checkout@v2
59
+ - name: Set up Ruby
60
+ uses: ruby/setup-ruby@v1
61
+ with:
62
+ ruby-version: ${{ matrix.ruby-version }}
63
+ bundler: ${{ matrix.bundler-version }}
64
+ bundler-cache: true
65
+ - name: Run tests
66
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -12,4 +12,6 @@ bin/*
12
12
  *.swp
13
13
  Gemfile.lock
14
14
  .rvmrc
15
- .redcar/*
15
+ .redcar/*
16
+ gemfiles/*.lock
17
+ spec/dummy/tmp
data/Appraisals CHANGED
@@ -1,15 +1,32 @@
1
1
  appraise "rails_4" do
2
2
  gem "rails", "~> 4.2.10"
3
+ gem "rspec-rails", "~> 3.0"
4
+ gem "sqlite3", "~> 1.3.6"
3
5
  end
4
6
 
5
7
  appraise "rails_5.0" do
6
8
  gem "rails", "~> 5.0.7"
9
+ gem "rspec-rails", "~> 4.0"
10
+ gem "sqlite3", "~> 1.3.6"
7
11
  end
8
12
 
9
13
  appraise "rails_5.1" do
10
14
  gem "rails", "~> 5.1.6"
15
+ gem "rspec-rails", "~> 4.0"
11
16
  end
12
17
 
13
18
  appraise "rails_5.2" do
14
- gem "rails", "5.2.0"
19
+ gem "rails", "~> 5.2.0"
20
+ end
21
+
22
+ appraise "rails_6.0" do
23
+ gem "rails", "~> 6.0.0"
24
+ end
25
+
26
+ appraise "rails_6.1" do
27
+ gem "rails", "~> 6.1.0"
28
+ end
29
+
30
+ appraise "rails_7.0" do
31
+ gem "rails", "~> 7.0.0.rc1"
15
32
  end
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify any dependencies in the gemspec
4
4
  gemspec
data/README.rdoc CHANGED
@@ -1,4 +1,6 @@
1
- {<img src="https://secure.travis-ci.org/jpmcgrath/shortener.png?branch=master" alt="Build Status" />}[http://travis-ci.org/jpmcgrath/shortener] {<img src="https://codeclimate.com/github/jpmcgrath/shortener/badges/gpa.svg" />}[https://codeclimate.com/github/jpmcgrath/shortener]
1
+ {<img src="https://github.com/jpmcgrath/shortener/actions/workflows/ruby.yml/badge.svg" alt="Build Status" />}[https://github.com/jpmcgrath/shortener/actions]
2
+ {<img src="https://codeclimate.com/github/jpmcgrath/shortener/badges/gpa.svg" />}[https://codeclimate.com/github/jpmcgrath/shortener]
3
+ {<img src="https://badge.fury.io/rb/shortener.svg" alt="Gem version" />}[http://badge.fury.io/rb/shortener]
2
4
 
3
5
  = Shortener
4
6
 
@@ -14,7 +16,7 @@ The majority of the Shortener consists of three parts:
14
16
 
15
17
  === Dependencies
16
18
 
17
- Shortener is designed to work from within a Rails 3 and Rails 4 applications. It has dependancies Rails core components like ActiveRecord, ActionController, the rails routing engine and more.
19
+ Shortener is designed to work from within a Ruby on Rail applications. It has dependancies Rails core components like ActiveRecord, ActionController, the rails routing engine and more.
18
20
 
19
21
  === Ruby Version Support
20
22
 
@@ -61,15 +63,9 @@ migration:
61
63
  * The link records a count of how many times it has been “un-shortened”;
62
64
  * The link can be associated with a user, this allows for stats of the link usage for a particular user and other interesting things;
63
65
 
64
- === Future improvements:
65
-
66
- * There has not been an attempt to remove ambiguous characters (i.e. 1 l and capital i, or 0 and O etc.) from the unique key generated for the link. This means people might copy the link incorrectly if copying the link by hand;
67
- * The system could pre-generate unique keys in advance, avoiding the database penalty when checking that a newly generated key is unique;
68
- * The system could store/cache the shortened URL if the url is to be continually rendered;
69
-
70
66
  == Installation
71
67
 
72
- Shortener is compatible with Rails v3, v4 & v5. To install, add to your Gemfile:
68
+ Shortener is compatible with Rails v4, v5, & v6. To install, add to your Gemfile:
73
69
 
74
70
  gem 'shortener'
75
71
 
@@ -101,6 +97,15 @@ the upper and lower case charset, by including the following:
101
97
 
102
98
  Shortener.charset = :alphanumcase
103
99
 
100
+ If you want to use a custom charset, you can create your own combination by creating an array of possible values, such as allowing underscore and dashes:
101
+
102
+ Shortener.charset = ("a".."z").to_a + (0..9).to_a + ["-", "_"]
103
+
104
+ By default, <b>Shortener assumes URLs to be valid web URLs</b> and normalizes them in an effort to make sure there are no duplicate records generated for effectively same URLs with differences of only non-effective slash etc.
105
+ You can control this option if it interferes for any of your logic. One common case is for mobile app links or universal links where normalization can corrupt the URLs of form <tt>appname://some_route</tt>
106
+
107
+ Shortener.auto_clean_url = true
108
+
104
109
  == Usage
105
110
 
106
111
  To generate a Shortened URL object for the URL "http://example.com" within your controller / models do the following:
@@ -143,7 +148,9 @@ And to access those URLs:
143
148
 
144
149
  You can pass in your own key when generating a shortened URL. This should be unique.
145
150
 
146
- Shortener::ShortenedUrl.generate("example.com", owner: user, custom_key: "my-key")
151
+ *Important:* Custom keys can't contain characters other than those defined in *Shortener.charset*. Default is numbers and lowercase a-z (See *Configuration*).
152
+
153
+ Shortener::ShortenedUrl.generate("example.com", owner: user, custom_key: "mykey")
147
154
 
148
155
  short_url("http://example.com", custom_key: 'yourkey')
149
156
 
@@ -152,11 +159,11 @@ You can pass in your own key when generating a shortened URL. This should be uni
152
159
  You can create expirable URLs.
153
160
  Probably, most of the time it would be used with owner:
154
161
 
155
- Shortener::ShortenedUrl.generate("example.com/page", user, expires_at: 24.hours.since)
162
+ Shortener::ShortenedUrl.generate("example.com/page", owner: user, expires_at: 24.hours.since)
156
163
 
157
- You can omit owner passing nil instead:
164
+ You can omit owner:
158
165
 
159
- Shortener::ShortenedUrl.generate("example.com/page", nil, expires_at: 24.hours.since)
166
+ Shortener::ShortenedUrl.generate("example.com/page", expires_at: 24.hours.since)
160
167
 
161
168
  === Fresh Links
162
169
 
@@ -170,7 +177,7 @@ to create a fresh record, you can pass the following argument:
170
177
  === Forbidden keys
171
178
 
172
179
  You can ensure that records with forbidden keys will not be generated.
173
- In rails you can put next line into config/initializers/shortener.rb
180
+ In Rails you can put next line into config/initializers/shortener.rb
174
181
 
175
182
  Shortener.forbidden_keys.concat %w(terms promo)
176
183
 
@@ -249,6 +256,18 @@ If you want more things to happen when a user accesses one of your short urls, y
249
256
 
250
257
  *note:* If no shortened URL is found, the url will be `default_redirect` or `/`
251
258
 
259
+ === Configuring a different database for shortened_urls table
260
+
261
+ You can store a `shortened_urls` table in another database and connecting to it by creating a initializer with the following:
262
+
263
+ ```ruby
264
+ ActiveSupport.on_load(:shortener_record) do
265
+ connects_to(database: { writing: :dbname, reading: :dbname_replica })
266
+ end
267
+ ```
268
+
269
+ **Note:** Please, replace `dbname` and `dbname_replica` to match your database configuration.
270
+
252
271
  == Origins
253
272
 
254
273
  For a bit of backstory to Shortener see this {blog post}[http://jamespmcgrath.com/a-simple-link-shortener-in-rails/].
@@ -280,4 +299,4 @@ To contribute:
280
299
  6. Create a new Pull Request
281
300
  7. Ensure the build is passing
282
301
 
283
- Note: We adhere to the community driven Ruby style guide: https://github.com/bbatsov/ruby-style-guide
302
+ Note: We adhere to the community driven Ruby style guide: https://github.com/bbatsov/ruby-style-guide
@@ -0,0 +1,5 @@
1
+ class Shortener::Record < ActiveRecord::Base #:nodoc:
2
+ self.abstract_class = true
3
+ end
4
+
5
+ ActiveSupport.run_load_hooks :shortener_record, Shortener::Record
@@ -1,10 +1,10 @@
1
- class Shortener::ShortenedUrl < ActiveRecord::Base
1
+ class Shortener::ShortenedUrl < Shortener::Record
2
2
 
3
3
  REGEX_LINK_HAS_PROTOCOL = Regexp.new('\Ahttp:\/\/|\Ahttps:\/\/', Regexp::IGNORECASE)
4
4
 
5
5
  validates :url, presence: true
6
6
 
7
- before_create :generate_unique_key
7
+ around_create :generate_unique_key
8
8
 
9
9
  # allows the shortened link to be associated with a user
10
10
  if ActiveRecord::VERSION::MAJOR >= 5
@@ -15,7 +15,7 @@ class Shortener::ShortenedUrl < ActiveRecord::Base
15
15
  end
16
16
 
17
17
  # exclude records in which expiration time is set and expiration time is greater than current time
18
- scope :unexpired, -> { where(arel_table[:expires_at].eq(nil).or(arel_table[:expires_at].gt(::Time.current.to_s(:db)))) }
18
+ scope :unexpired, -> { where(arel_table[:expires_at].eq(nil).or(arel_table[:expires_at].gt(::Time.current))) }
19
19
 
20
20
  attr_accessor :custom_key
21
21
 
@@ -35,7 +35,7 @@ class Shortener::ShortenedUrl < ActiveRecord::Base
35
35
  def self.generate!(destination_url, owner: nil, custom_key: nil, expires_at: nil, fresh: false, category: nil)
36
36
  # if we get a shortened_url object with a different owner, generate
37
37
  # new one for the new owner. Otherwise return same object
38
- result = if destination_url.is_a? Shortener::ShortenedUrl
38
+ if destination_url.is_a? Shortener::ShortenedUrl
39
39
  if destination_url.owner == owner
40
40
  destination_url
41
41
  else
@@ -52,14 +52,13 @@ class Shortener::ShortenedUrl < ActiveRecord::Base
52
52
  scope = owner ? owner.shortened_urls : self
53
53
  creation_method = fresh ? 'create' : 'first_or_create'
54
54
 
55
- scope.where(url: clean_url(destination_url), category: category).send(
55
+ url_to_save = Shortener.auto_clean_url ? clean_url(destination_url) : destination_url
56
+ scope.where(url: url_to_save, category: category).send(
56
57
  creation_method,
57
58
  custom_key: custom_key,
58
59
  expires_at: expires_at
59
60
  )
60
61
  end
61
-
62
- result
63
62
  end
64
63
 
65
64
  # return shortened url on success, nil on failure
@@ -127,15 +126,24 @@ class Shortener::ShortenedUrl < ActiveRecord::Base
127
126
 
128
127
  private
129
128
 
130
- def generate_unique_key
129
+ def self.unique_key_candidate
130
+ charset = ::Shortener.key_chars
131
+ (0...::Shortener.unique_key_length).map{ charset[rand(charset.size)] }.join
132
+ end
133
+
134
+ def generate_unique_key(retries = Shortener.persist_retries)
131
135
  begin
132
136
  self.unique_key = custom_key || self.class.unique_key_candidate
133
137
  self.custom_key = nil
134
- end while self.class.exists?(unique_key: unique_key) && custom_key.blank?
135
- end
138
+ end while self.class.unscoped.exists?(unique_key: unique_key)
136
139
 
137
- def self.unique_key_candidate
138
- charset = ::Shortener.key_chars
139
- (0...::Shortener.unique_key_length).map{ charset[rand(charset.size)] }.join
140
+ yield
141
+ rescue ActiveRecord::RecordNotUnique
142
+ if retries <= 0
143
+ raise
144
+ else
145
+ retries -= 1
146
+ retry
147
+ end
140
148
  end
141
149
  end
@@ -1,7 +1,9 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 4.2.10"
6
+ gem "rspec-rails", "~> 3.0"
7
+ gem "sqlite3", "~> 1.3.6"
6
8
 
7
9
  gemspec path: "../"
@@ -1,7 +1,9 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 5.0.7"
6
+ gem "rspec-rails", "~> 4.0"
7
+ gem "sqlite3", "~> 1.3.6"
6
8
 
7
9
  gemspec path: "../"
@@ -1,7 +1,8 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 5.1.6"
6
+ gem "rspec-rails", "~> 4.0"
6
7
 
7
8
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.2.0"
5
+ gem "rails", "~> 5.2.0"
6
6
 
7
7
  gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 6.0.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 6.1.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.0.0.rc1"
6
+
7
+ gemspec path: "../"
@@ -6,8 +6,5 @@ class Shortener::Railtie < ::Rails::Railtie #:nodoc:
6
6
  ActiveSupport.on_load :active_record do
7
7
  extend Shortener::ActiveRecordExtension
8
8
  end
9
- ActiveSupport.on_load :action_view do
10
- include Shortener::ShortenerHelper
11
- end
12
9
  end
13
10
  end
@@ -1,3 +1,3 @@
1
1
  module Shortener
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
data/lib/shortener.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "active_support/dependencies"
1
+ require "active_support"
2
2
 
3
3
  module Shortener
4
4
 
@@ -19,8 +19,9 @@ module Shortener
19
19
  self.unique_key_length = 5
20
20
 
21
21
  # character set to chose from:
22
- # :alphanum - a-z0-9 - has about 60 million possible combos
23
- # :alphanumcase - a-zA-Z0-9 - has about 900 million possible combos
22
+ # :alphanum // a-z0-9 ## has about 60 million possible combos
23
+ # :alphanumcase // a-zA-Z0-9 ## has about 900 million possible combos
24
+ # ("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a + ["-", "_"] ## define a custom set
24
25
  mattr_accessor :charset
25
26
  self.charset = :alphanum
26
27
 
@@ -36,9 +37,16 @@ module Shortener
36
37
  mattr_accessor :ignore_robots
37
38
  self.ignore_robots = false
38
39
 
40
+ # persist_retries - number of retries on ActiveRecord::RecordNotUnique error
41
+ mattr_accessor :persist_retries
42
+ self.persist_retries = 3
43
+
44
+ # auto_clean_url - controls url cleaning mechanism, set it to false to disable
45
+ mattr_accessor :auto_clean_url
46
+ self.auto_clean_url = true
39
47
 
40
48
  def self.key_chars
41
- CHARSETS[charset]
49
+ charset.is_a?(Symbol) ? CHARSETS[charset] : charset
42
50
  end
43
51
  end
44
52
 
data/shortener.gemspec CHANGED
@@ -12,18 +12,18 @@ Gem::Specification.new do |s|
12
12
  s.authors = [ "James P. McGrath", "Michael Reinsch" ]
13
13
  s.email = [ "gems@jamespmcgrath.com", "michael@mobalean.com" ]
14
14
  s.homepage = "http://jamespmcgrath.com/projects/shortener"
15
- s.rubyforge_project = "shortener"
16
15
  s.required_rubygems_version = "> 2.1.0"
17
16
 
18
17
  s.add_dependency "voight_kampff", '~> 1.1.2'
19
18
 
20
19
  s.add_development_dependency "rails", '>= 3'
21
20
  s.add_development_dependency "sqlite3"
22
- s.add_development_dependency "rspec-rails", '~> 3.6.0'
21
+ s.add_development_dependency "rspec-rails"
23
22
  s.add_development_dependency "shoulda-matchers"
24
23
  s.add_development_dependency "faker"
25
24
  s.add_development_dependency "byebug"
26
25
  s.add_development_dependency "appraisal"
26
+ s.add_development_dependency "capybara"
27
27
 
28
28
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
29
29
  s.require_path = 'lib'
@@ -132,6 +132,25 @@ describe Shortener::ShortenedUrlsController, type: :controller do
132
132
  end
133
133
  end
134
134
 
135
+ context "custom charset set" do
136
+ before do
137
+ Shortener::ShortenedUrl.delete_all
138
+ Shortener.charset = ("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a + ["-", "_"]
139
+ end
140
+
141
+ after do
142
+ Shortener.charset = :alphanum
143
+ end
144
+
145
+ context 'key with valid characters' do
146
+ let(:key) { "cust-Key_123" }
147
+ let(:custom_url) { Shortener::ShortenedUrl.generate(Faker::Internet.url, custom_key: key) }
148
+ it 'allows if in custom charset' do
149
+ expect(custom_url.unique_key).to eq key
150
+ end
151
+ end
152
+ end
153
+
135
154
  context 'expired code' do
136
155
  let(:expired_url) { Shortener::ShortenedUrl.generate(Faker::Internet.url, expires_at: 1.hour.ago) }
137
156
  describe "GET show with expired code" do
File without changes
@@ -0,0 +1,3 @@
1
+ class HomeController < ApplicationController
2
+ def show; end
3
+ end
@@ -0,0 +1 @@
1
+ Example: <%= short_url("http://example.com") %>
@@ -2,8 +2,6 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Dummy</title>
5
- <%= stylesheet_link_tag :all %>
6
- <%= javascript_include_tag :defaults %>
7
5
  <%= csrf_meta_tag %>
8
6
  </head>
9
7
  <body>
@@ -1,4 +1,4 @@
1
1
  Dummy::Application.routes.draw do
2
2
  get '/:id' => "shortener/shortened_urls#show"
3
- root to: "application_controller#show"
3
+ root to: "home#show"
4
4
  end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shortener, type: :feature do
4
+ it "includes 'short_url'-helper into public namespace" do
5
+ visit root_path
6
+ expect(page).to have_content(%r{http://www.example.com/[a-zA-Z0-9]{5}})
7
+ end
8
+ end
@@ -2,7 +2,7 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe Shortener::ShortenedUrl, type: :model do
5
- it { is_expected.to belong_to :owner }
5
+ it { is_expected.to belong_to(:owner).optional }
6
6
  it { is_expected.to validate_presence_of :url }
7
7
 
8
8
  describe '#generate!' do
@@ -123,10 +123,45 @@ describe Shortener::ShortenedUrl, type: :model do
123
123
 
124
124
  context "duplicate unique key" do
125
125
  let(:duplicate_key) { 'ABCDEF' }
126
- it 'should try until it finds a non-dup key' do
126
+ context 'without retry' do
127
+ around do |spec|
128
+ tries = Shortener.persist_retries
129
+ Shortener.persist_retries = 0
130
+ spec.run
131
+ Shortener.persist_retries = tries
132
+ end
133
+
134
+ it 'finds a non-dup key' do
135
+ Shortener::ShortenedUrl.where(unique_key: duplicate_key).delete_all
136
+ Shortener::ShortenedUrl.create!(url: Faker::Internet.url, custom_key: duplicate_key)
137
+ short_url = Shortener::ShortenedUrl.create!(url: Faker::Internet.url, custom_key: duplicate_key)
138
+ expect(short_url).not_to be_nil
139
+ expect(short_url.unique_key).not_to be_nil
140
+ expect(short_url.unique_key).not_to eq duplicate_key
141
+ end
142
+
143
+ it 'unscoped query to finds a non-dup key' do
144
+ Shortener::ShortenedUrl.where(unique_key: duplicate_key).delete_all
145
+ Shortener::ShortenedUrl.create!(url: Faker::Internet.url, custom_key: duplicate_key)
146
+ short_url = Shortener::ShortenedUrl.where(
147
+ url: Faker::Internet.url, category: :test
148
+ ).create!(url: Faker::Internet.url, custom_key: duplicate_key)
149
+ expect(short_url).not_to be_nil
150
+ expect(short_url.unique_key).not_to be_nil
151
+ expect(short_url.unique_key).not_to eq duplicate_key
152
+ end
153
+ end
154
+
155
+ it 'use retry in case with DB unique constraint exception' do
127
156
  Shortener::ShortenedUrl.where(unique_key: duplicate_key).delete_all
128
157
  Shortener::ShortenedUrl.create!(url: Faker::Internet.url, custom_key: duplicate_key)
129
- short_url = Shortener::ShortenedUrl.create!(url: Faker::Internet.url, unique_key: duplicate_key)
158
+
159
+ query = double
160
+ allow(query).to receive(:exists?).and_return(false)
161
+ allow(Shortener::ShortenedUrl).to receive(:unscoped).and_return(query).once
162
+ allow(Shortener::ShortenedUrl).to receive(:unscoped).and_call_original
163
+
164
+ short_url = Shortener::ShortenedUrl.create!(url: Faker::Internet.url, custom_key: duplicate_key)
130
165
  expect(short_url).not_to be_nil
131
166
  expect(short_url.unique_key).not_to be_nil
132
167
  expect(short_url.unique_key).not_to eq duplicate_key
@@ -142,8 +177,26 @@ describe Shortener::ShortenedUrl, type: :model do
142
177
  it 'finds the shortened url from slashless oath' do
143
178
  expect(Shortener::ShortenedUrl.generate!(path)).to eq existing_shortened_url
144
179
  end
145
- it "should look up exsiting URL" do
146
- expect(Shortener::ShortenedUrl.generate!("/#{path}")).to eq existing_shortened_url
180
+
181
+ context 'with auto_clean_url enabled by default' do
182
+ it "looks up existing cleaned URL" do
183
+ expect(Shortener::ShortenedUrl.generate!("/#{path}")).to eq existing_shortened_url
184
+ end
185
+ end
186
+
187
+ context 'with auto_clean_url disabled' do
188
+ around do |spec|
189
+ tries = Shortener.auto_clean_url
190
+ Shortener.auto_clean_url = false
191
+ spec.run
192
+ Shortener.auto_clean_url = tries
193
+ end
194
+
195
+ it "does not look up existing cleaned URL" do
196
+ shortened_url = Shortener::ShortenedUrl.generate!("/#{path}")
197
+ expect(shortened_url).not_to eq existing_shortened_url
198
+ expect(shortened_url.url).to eq "/#{path}"
199
+ end
147
200
  end
148
201
  end
149
202
  end
data/spec/spec_helper.rb CHANGED
@@ -7,8 +7,10 @@ require 'rspec/rails'
7
7
  require 'shoulda/matchers'
8
8
  require 'byebug'
9
9
  require 'faker'
10
+ require 'capybara/rspec'
10
11
 
11
12
  Rails.backtrace_cleaner.remove_silencers!
13
+ ActiveRecord::Base.logger = Logger.new(STDOUT) if ENV['LOG_STDOUT']
12
14
 
13
15
  Shoulda::Matchers.configure do |config|
14
16
  config.integrate do |with|
@@ -18,8 +20,11 @@ Shoulda::Matchers.configure do |config|
18
20
  end
19
21
 
20
22
  # Run any available migration
23
+ migration_path = File.expand_path("../dummy/db/migrate/", __FILE__)
21
24
  if ActiveRecord::Migrator.respond_to?(:migrate)
22
- ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
25
+ ActiveRecord::Migrator.migrate(migration_path)
26
+ elsif Rails::VERSION::MAJOR < 6
27
+ ActiveRecord::MigrationContext.new(migration_path).migrate
23
28
  else
24
- ActiveRecord::MigrationContext.new(File.expand_path("../dummy/db/migrate/", __FILE__)).migrate
29
+ ActiveRecord::MigrationContext.new(migration_path, ActiveRecord::Base.connection.schema_migration).migrate
25
30
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shortener
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James P. McGrath
8
8
  - Michael Reinsch
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-07-27 00:00:00.000000000 Z
12
+ date: 2022-08-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: voight_kampff
@@ -57,16 +57,16 @@ dependencies:
57
57
  name: rspec-rails
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - "~>"
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 3.6.0
62
+ version: '0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - "~>"
67
+ - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 3.6.0
69
+ version: '0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: shoulda-matchers
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -123,6 +123,20 @@ dependencies:
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: capybara
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
126
140
  description: Shortener is a Rails Engine Gem that makes it easy to create and interpret
127
141
  shortened URLs on your own domain from within your Rails application. Once installed
128
142
  Shortener will generate, store URLS and "unshorten" shortened URLs for your applications
@@ -134,8 +148,8 @@ executables: []
134
148
  extensions: []
135
149
  extra_rdoc_files: []
136
150
  files:
151
+ - ".github/workflows/ruby.yml"
137
152
  - ".gitignore"
138
- - ".travis.yml"
139
153
  - Appraisals
140
154
  - Gemfile
141
155
  - MIT-LICENSE
@@ -143,11 +157,15 @@ files:
143
157
  - Rakefile
144
158
  - app/controllers/shortener/shortened_urls_controller.rb
145
159
  - app/helpers/shortener/shortener_helper.rb
160
+ - app/models/shortener/record.rb
146
161
  - app/models/shortener/shortened_url.rb
147
162
  - gemfiles/rails_4.gemfile
148
163
  - gemfiles/rails_5.0.gemfile
149
164
  - gemfiles/rails_5.1.gemfile
150
165
  - gemfiles/rails_5.2.gemfile
166
+ - gemfiles/rails_6.0.gemfile
167
+ - gemfiles/rails_6.1.gemfile
168
+ - gemfiles/rails_7.0.gemfile
151
169
  - lib/generators/shortener/shortener_generator.rb
152
170
  - lib/generators/shortener/templates/migration.rb
153
171
  - lib/shortener.rb
@@ -160,9 +178,12 @@ files:
160
178
  - spec/controllers/shortened_urls_controller_spec.rb
161
179
  - spec/dummy/.gitignore
162
180
  - spec/dummy/Rakefile
181
+ - spec/dummy/app/assets/config/manifest.js
163
182
  - spec/dummy/app/controllers/application_controller.rb
183
+ - spec/dummy/app/controllers/home_controller.rb
164
184
  - spec/dummy/app/helpers/application_helper.rb
165
185
  - spec/dummy/app/models/user.rb
186
+ - spec/dummy/app/views/home/show.html.erb
166
187
  - spec/dummy/app/views/layouts/application.html.erb
167
188
  - spec/dummy/config.ru
168
189
  - spec/dummy/config/application.rb
@@ -188,6 +209,7 @@ files:
188
209
  - spec/dummy/db/schema.rb
189
210
  - spec/dummy/public/favicon.ico
190
211
  - spec/dummy/script/rails
212
+ - spec/features/shortener_feature_spec.rb
191
213
  - spec/helpers/shortener_helper_spec.rb
192
214
  - spec/models/shortened_url_spec.rb
193
215
  - spec/models/user_spec.rb
@@ -196,7 +218,7 @@ files:
196
218
  homepage: http://jamespmcgrath.com/projects/shortener
197
219
  licenses: []
198
220
  metadata: {}
199
- post_install_message:
221
+ post_install_message:
200
222
  rdoc_options: []
201
223
  require_paths:
202
224
  - lib
@@ -211,9 +233,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
233
  - !ruby/object:Gem::Version
212
234
  version: 2.1.0
213
235
  requirements: []
214
- rubyforge_project: shortener
215
- rubygems_version: 2.5.1
216
- signing_key:
236
+ rubyforge_project:
237
+ rubygems_version: 2.7.6
238
+ signing_key:
217
239
  specification_version: 4
218
240
  summary: Shortener is a Rails Engine that makes it easy to create shortened URLs for
219
241
  your rails application.
data/.travis.yml DELETED
@@ -1,16 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.2.10
5
- - 2.3
6
- - 2.4
7
- - 2.5
8
-
9
- matrix:
10
- fast_finish: true
11
-
12
- gemfile:
13
- - gemfiles/rails_4.gemfile
14
- - gemfiles/rails_5.0.gemfile
15
- - gemfiles/rails_5.1.gemfile
16
- - gemfiles/rails_5.2.gemfile