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 +5 -5
- data/.github/workflows/ruby.yml +66 -0
- data/.gitignore +3 -1
- data/Appraisals +18 -1
- data/Gemfile +1 -1
- data/README.rdoc +34 -15
- data/app/models/shortener/record.rb +5 -0
- data/app/models/shortener/shortened_url.rb +21 -13
- data/gemfiles/rails_4.gemfile +3 -1
- data/gemfiles/rails_5.0.gemfile +3 -1
- data/gemfiles/rails_5.1.gemfile +2 -1
- data/gemfiles/rails_5.2.gemfile +2 -2
- data/gemfiles/rails_6.0.gemfile +7 -0
- data/gemfiles/rails_6.1.gemfile +7 -0
- data/gemfiles/rails_7.0.gemfile +7 -0
- data/lib/shortener/railtie.rb +0 -3
- data/lib/shortener/version.rb +1 -1
- data/lib/shortener.rb +12 -4
- data/shortener.gemspec +2 -2
- data/spec/controllers/shortened_urls_controller_spec.rb +19 -0
- data/spec/dummy/app/assets/config/manifest.js +0 -0
- data/spec/dummy/app/controllers/home_controller.rb +3 -0
- data/spec/dummy/app/views/home/show.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +0 -2
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/features/shortener_feature_spec.rb +8 -0
- data/spec/models/shortened_url_spec.rb +58 -5
- data/spec/spec_helper.rb +7 -2
- metadata +34 -12
- data/.travis.yml +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12594d11b56f84e7c4654bee681887dc084978604ee158c803e384c839b6a937
|
4
|
+
data.tar.gz: 8550a33e8bd06229cd66c5e9f340ba6cd9c49c77acb5481555b23f43ea659883
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
data/README.rdoc
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
{<img src="https://
|
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
|
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
|
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
|
-
|
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
|
164
|
+
You can omit owner:
|
158
165
|
|
159
|
-
Shortener::ShortenedUrl.generate("example.com/page",
|
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
|
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
|
@@ -1,10 +1,10 @@
|
|
1
|
-
class Shortener::ShortenedUrl <
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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)
|
135
|
-
end
|
138
|
+
end while self.class.unscoped.exists?(unique_key: unique_key)
|
136
139
|
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
data/gemfiles/rails_4.gemfile
CHANGED
data/gemfiles/rails_5.0.gemfile
CHANGED
data/gemfiles/rails_5.1.gemfile
CHANGED
data/gemfiles/rails_5.2.gemfile
CHANGED
data/lib/shortener/railtie.rb
CHANGED
data/lib/shortener/version.rb
CHANGED
data/lib/shortener.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require "active_support
|
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
|
23
|
-
# :alphanumcase
|
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"
|
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 @@
|
|
1
|
+
Example: <%= short_url("http://example.com") %>
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
146
|
-
|
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
|
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(
|
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.
|
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:
|
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:
|
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:
|
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:
|
215
|
-
rubygems_version: 2.
|
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