tanshuku 0.0.11 → 0.0.13

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
2
  SHA256:
3
- metadata.gz: 58228d834716ae9f1a9415212f4976816aeaf61bd6ff2a1e92672a62bd202a93
4
- data.tar.gz: e8ecd16d19d73cdff428f08cd5a3b801e59934fa86d69fa1b182e0da40da1feb
3
+ metadata.gz: 1748ebeb12ae7c094ef0cd34386f6db676b8363c50423b39bf571e92093a1217
4
+ data.tar.gz: c9d4513adbf7bd8e87d1d218801b108c7a72e4681185d4b7f60705eefff722ce
5
5
  SHA512:
6
- metadata.gz: f786879cc78304a288487079e4b2e218be8df2d7c6df4edc8f629ca672369fa583998e2b44bc4d62876b1f242dd24ae0f67201a1a85f391d20fb26dd07d80ca1
7
- data.tar.gz: 2b7af5d5181be59774b1d6ae13e12df32347c862cec776139a4ba31da153a38fb63959380e8bebcc931fa707b658799b33eb5a248a7bf765ba77dc9de3397bf9
6
+ metadata.gz: b18fa0edf40682988bf7c51055f2a59cc100af34ea50ccf52bd9ca320b34a95dd76f9bd864f57e98e4909dbec904da954e02d0b1e4cadd2ff42119f1956cee47
7
+ data.tar.gz: 0fa9fe03d277660ba8d440429f8f58686d3587dc09db02788ffc79ddeff2e34ef995500b0fde39e19cb841f026e7ce4b918c06ff504120fbf4203bae54cfbe5f
data/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # Tanshuku
2
+
3
+ Tanshuku is a simple and small Rails engine that makes it easier to shorten URLs.
4
+
5
+ ## Key Features
6
+
7
+ * Generates a unique key for a URL.
8
+ * The uniqueness is ensured at database level.
9
+ * Creates no additional shortened URL record if the given URL has already been shortened.
10
+ * You can create an additional record for the same URL with using a different namespace from existing records'.
11
+ * Checks its existence considering the performance.
12
+ * Provides a Rails controller and action for finding a shortened URL record and redirecting to its original, i.e., non-shortened, URL.
13
+ * The redirection is done with 301 HTTP status.
14
+
15
+ ## Usage
16
+
17
+ ### 1. Mount `Tanshuku::Engine`
18
+
19
+ For example, the following code generates a routing `` GET `/t/:key` to `Tanshuku::UrlsController#show` ``. When your Rails app receives a request to `/t/abcdefghij0123456789`, `Tanshuku::UrlsController#show` will be called and a `Tanshuku::Url` record with a key `abcdefghij0123456789` will be found. Then the request will be redirected to the `Tanshuku::Url` record's original URL.
20
+
21
+ ```rb
22
+ # config/routes.rb
23
+ Rails.application.routes.draw do
24
+ mount Tanshuku::Engine, at: "/t"
25
+ end
26
+ ```
27
+
28
+ **Note**: You can customize the path `/t` as you like.
29
+
30
+ ### 2. Configure Tanshuku (Optional)
31
+
32
+ **Note**: This step is optional.
33
+
34
+ **Note**: The initializer file can be generated by `bin/rails generate tanshuku:install`. See the "[Installation](#installation)" section for more information.
35
+
36
+ #### `config.default_url_options`
37
+
38
+ Tanshuku uses configured `config.default_url_options` when generating shortened URLs.
39
+
40
+ Default value is `{}`.
41
+
42
+ The following example means that the configured host and protocol are used. Shortened URLs will be like `https://example.com/t/abcdefghij0123456789`.
43
+
44
+ ```rb
45
+ # config/initializers/tanshuku.rb
46
+ Tanshuku.configure do |config|
47
+ config.default_url_options = { host: "example.com", protocol: :https }
48
+ end
49
+ ```
50
+
51
+ #### `config.exception_reporter`
52
+
53
+ If an exception occurs when shortening a URL, Tanshuku reports it with configured `config.exception_reporter` object.
54
+
55
+ Default value is `Tanshuku::Configuration::DefaultExceptionReporter`. It logs the exception and the original URL with `Rails.logger.warn`.
56
+
57
+ Value of `config.exception_reporter` should respond to `#call` with keyword arguments `exception:` and `original_url:`.
58
+
59
+ The following example means that an exception and a URL will be reported to [Sentry](https://sentry.io/).
60
+
61
+ ```rb
62
+ # config/initializers/tanshuku.rb
63
+ Tanshuku.configure do |config|
64
+ config.exception_reporter =
65
+ lambda { |exception:, original_url:|
66
+ Sentry.capture_exception(exception, tags: { original_url: })
67
+ }
68
+ end
69
+ ```
70
+
71
+ ### 3. Generate shortened URLs
72
+
73
+ #### Basic cases
74
+
75
+ You can generate a shortened URL with `Tanshuku::Url.shorten`. For example:
76
+
77
+ ```rb
78
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abcdefghij0123456789"
79
+ ```
80
+
81
+ [`config.default_url_options`](#configdefault_url_options) is used for the shortened URL.
82
+
83
+ **Note**: If a `Tanshuku::Url` record for the given URL already exists, no additional record will be created and always the existing record is used.
84
+
85
+ ```rb
86
+ # When no record exists for "https://google.com/", a new record will be created.
87
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abcde0123456789fghij"
88
+
89
+ # When a record already exists for "https://google.com/", no additional record will be created.
90
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abcde0123456789fghij"
91
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abcde0123456789fghij"
92
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abcde0123456789fghij"
93
+ ```
94
+
95
+ #### Shortening a URL with ad hoc URL options
96
+
97
+ You can specify URL options to `Tanshuku::Url.shorten`. For example:
98
+
99
+ ```rb
100
+ Tanshuku::Url.shorten("https://google.com/", url_options: { host: "verycool.example.com" })
101
+ #=> "https://verycool.example.com/t/0123456789abcdefghij"
102
+
103
+ Tanshuku::Url.shorten("https://google.com/", url_options: { protocol: :http })
104
+ #=> "http://example.com/t/abcde01234fghij56789"
105
+ ```
106
+
107
+ #### Shortening a URL with a namespace
108
+
109
+ You can create additional records for the same URL with specifying a namespace.
110
+
111
+ ```rb
112
+ # When no record exists for "https://google.com/", a new record will be created.
113
+ Tanshuku::Url.shorten("https://google.com/") #=> "https://example.com/t/abc012def345ghi678j9"
114
+
115
+ # Even when a record already exists for "https://google.com/", an additional record will be created if namespace is
116
+ # specified.
117
+ Tanshuku::Url.shorten("https://google.com/", namespace: "a") #=> "https://example.com/t/ab01cd23ef45gh67ij89"
118
+ Tanshuku::Url.shorten("https://google.com/", namespace: "b") #=> "https://example.com/t/a0b1c2d3e4f5g6h7i8j9"
119
+ Tanshuku::Url.shorten("https://google.com/", namespace: "c") #=> "https://example.com/t/abcd0123efgh4567ij89"
120
+
121
+ # When the same URL and the same namespace is specified, no additional record will be created.
122
+ Tanshuku::Url.shorten("https://google.com/", namespace: "a") #=> "https://example.com/t/ab01cd23ef45gh67ij89"
123
+ Tanshuku::Url.shorten("https://google.com/", namespace: "a") #=> "https://example.com/t/ab01cd23ef45gh67ij89"
124
+ ```
125
+
126
+ ### 4. Share the shortened URLs
127
+
128
+ You can share the shortened URLs, e.g., `https://example.com/t/abcdefghij0123456789`.
129
+
130
+ When a user clicks a link with a shortened URL, your Rails app redirects the user to its original URL.
131
+
132
+ ## Installation
133
+
134
+ ### 1. Enable Tanshuku
135
+
136
+ Add `gem "tanshuku"` to your application's `Gemfile`.
137
+
138
+ ```rb
139
+ # Gemfile
140
+ gem "tanshuku"
141
+ ```
142
+
143
+ ### 2. Generate setup files
144
+
145
+ Execute a shell command as following:
146
+
147
+ ```sh
148
+ bin/rails generate tanshuku:install
149
+ ```
150
+
151
+ ### 3. Apply generated migration files
152
+
153
+ Execute a shell command as following:
154
+
155
+ ```sh
156
+ bin/rails db:migrate
157
+ ```
158
+
159
+ ## Q&A
160
+
161
+ ### What does "tanshuku" mean?
162
+
163
+ "Tanshuku" is a Japanese word "短縮." It means "shortening." "短縮URL" in Japanese means "shortened URL" in English.
164
+
165
+ ### \*\* (anything you want) isn't implemented?
166
+
167
+ Does Tanshuku have some missing features? Please [create an issue](https://github.com/kg8m/tanshuku/issues/new).
data/Rakefile CHANGED
@@ -8,3 +8,18 @@ load "rails/tasks/engine.rake"
8
8
  load "rails/tasks/statistics.rake"
9
9
 
10
10
  require "bundler/gem_tasks"
11
+ require "rspec/core/rake_task"
12
+ require "rubocop/rake_task"
13
+
14
+ RSpec::Core::RakeTask.new(:spec)
15
+ RuboCop::RakeTask.new
16
+
17
+ namespace :yard do
18
+ desc "Start YARD server"
19
+ task :server do
20
+ puts "See http://localhost:8808/"
21
+ sh "yard server --reload"
22
+ end
23
+ end
24
+
25
+ task default: %i[rubocop spec]
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tanshuku
4
+ # A Rails controller class for finding a {Tanshuku::Url} record and redirecting to its shortened URL.
4
5
  class UrlsController < ActionController::API
6
+ # Finds a {Tanshuku::Url} record from the given +key+ parameter and redirects to its shortened URL.
7
+ #
8
+ # @return [void]
9
+ #
10
+ # @raise [ActiveRecord::NotFound] If no {Tanshuku::Url} record is found for the given +key+.
5
11
  def show
6
12
  url = Url.find_by!(key: params[:key])
7
13
  redirect_to url.url, status: :moved_permanently, allow_other_host: true
@@ -7,6 +7,7 @@ require "rack"
7
7
  require "securerandom"
8
8
 
9
9
  module Tanshuku
10
+ # An +ActiveRecord::Base+ inherited class for a shortened URL. This class also have some logics for shortening URLs.
10
11
  class Url < ActiveRecord::Base
11
12
  DEFAULT_NAMESPACE = ""
12
13
 
@@ -22,7 +23,28 @@ module Tanshuku
22
23
  # duplicated. Then rescue the exception and try to retry.
23
24
  # validates :url, :hashed_url, :key, uniqueness: true
24
25
 
25
- def self.shorten(original_url, namespace: DEFAULT_NAMESPACE)
26
+ # Shortens a URL. Builds and saves a {Tanshuku::Url} record with generating a unique key for the given URL and
27
+ # namespace. Then returns the record's shortened URL with the given URL options.
28
+ #
29
+ # @note
30
+ # If a {Tanshuku::Url} record already exists, no additional record will be created and the existing record will be
31
+ # used.
32
+ # @note
33
+ # The given URL will be normalized before shortening. So for example, +shorten("https://google.com/")+ and
34
+ # +shorten("https://google.com")+ have the same result.
35
+ #
36
+ # @param original_url [String] The original, i.e., non-shortened, URL.
37
+ # @param namespace: [String] A namespace for shorteting URL. Shortened URLs are unique in namespaces.
38
+ # @param url_options: [Hash] An option for Rails' +url_for+.
39
+ #
40
+ # @return [String] A shortened URL if succeeded to shorten the original URL.
41
+ # @return [String] The original URL if failed to shorten it.
42
+ #
43
+ # @example If succeeded to shorten a URL.
44
+ # Tanshuku::Url.shorten("https://google.com/") #=> "http://localhost/t/abcdefghij0123456789"
45
+ # @example If failed to shorten a URL.
46
+ # Tanshuku::Url.shorten("https://google.com/") #=> "https://google.com/"
47
+ def self.shorten(original_url, namespace: DEFAULT_NAMESPACE, url_options: {})
26
48
  raise ArgumentError, "original_url should be present" unless original_url
27
49
 
28
50
  url = normalize_url(original_url)
@@ -35,7 +57,7 @@ module Tanshuku
35
57
  r.attributes = { url:, key: generate_key }
36
58
  end
37
59
 
38
- record.shortened_url
60
+ record.shortened_url(url_options)
39
61
  end
40
62
  # ActiveRecord::RecordNotFound is raised when the key is duplicated.
41
63
  rescue ActiveRecord::RecordNotFound => e
@@ -52,6 +74,13 @@ module Tanshuku
52
74
  original_url
53
75
  end
54
76
 
77
+ # Finds a {Tanshuku::Url} record by a non-shortened URL.
78
+ #
79
+ # @param url [String] A non-shortened URL.
80
+ # @param namespace: [String] A namespace for the URL.
81
+ #
82
+ # @return [Tanshuku::Url] A {Tanshuku::Url} instance if found.
83
+ # @reutnr [nil] +nil+ unless found.
55
84
  def self.find_by_url(url, namespace: DEFAULT_NAMESPACE)
56
85
  normalized_url = normalize_url(url)
57
86
  hashed_url = hash_url(normalized_url, namespace:)
@@ -59,27 +88,59 @@ module Tanshuku
59
88
  find_by(hashed_url:)
60
89
  end
61
90
 
62
- # Normalize a trailing slash, `?` for an empty query, and so on, and sort query keys.
91
+ # Normalizes a URL. Adds or removes a trailing slash, removes +?+ for an empty query, and so on. And sorts query
92
+ # keys.
93
+ #
94
+ # @param url [String] A non-normalized URL.
95
+ #
96
+ # @return [String] A normalized URL.
63
97
  def self.normalize_url(url)
64
98
  parsed_url = Addressable::URI.parse(url)
65
99
  parsed_url.query_values = Rack::Utils.parse_query(parsed_url.query)
66
100
  parsed_url.normalize.to_s
67
101
  end
68
102
 
103
+ # Hashes a URL with +Digest::SHA512.hexdigest+.
104
+ #
105
+ # @param url [String] A non-hashed URL.
106
+ # @param namespace: [String] A namespace for the URL.
107
+ #
108
+ # @return [String] A hashed 128-character string.
69
109
  def self.hash_url(url, namespace: DEFAULT_NAMESPACE)
70
110
  Digest::SHA512.hexdigest(namespace.to_s + url)
71
111
  end
72
112
 
113
+ # Generates a key with +SecureRandom.alphanumeric+.
114
+ #
115
+ # @return [String] A 20-character alphanumeric string.
73
116
  def self.generate_key
74
117
  SecureRandom.alphanumeric(KEY_LENGTH)
75
118
  end
76
119
 
120
+ # Reports an exception when failed to shorten a URL.
121
+ #
122
+ # @note This method calls {Tanshuku::Configuration#exception_reporter}'s +call+ and returns its return value.
123
+ #
124
+ # @param exception: [Exception] An error instance at shortening a URL.
125
+ # @param original_url: [String] The original URL failed to shorten.
126
+ #
127
+ # @return [void]
77
128
  def self.report_exception(exception:, original_url:)
78
129
  Tanshuku.config.exception_reporter.call(exception:, original_url:)
79
130
  end
80
131
 
81
- def shortened_url
82
- Tanshuku::Engine.routes.url_for(controller: "tanshuku/urls", action: :show, key:)
132
+ # The record's shortened URL.
133
+ #
134
+ # @param url_options [Hash] An option for Rails' +url_for+.
135
+ #
136
+ # @return [String] A shortened URL.
137
+ def shortened_url(url_options = {})
138
+ url_options = url_options.symbolize_keys
139
+ url_options[:controller] = "tanshuku/urls"
140
+ url_options[:action] = :show
141
+ url_options[:key] = key
142
+
143
+ Tanshuku::Engine.routes.url_for(url_options)
83
144
  end
84
145
  end
85
146
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Tanshuku
6
+ # A generator class for Tanshuku configuration files.
7
+ #
8
+ # @api private
9
+ class InstallGenerator < Rails::Generators::Base
10
+ source_root File.expand_path("../templates", __dir__)
11
+
12
+ # Generates a configuration file +config/initializers/tanshuku.rb+.
13
+ #
14
+ # @return [void]
15
+ def copy_initializer_file
16
+ copy_file "initializer.rb", "config/initializers/tanshuku.rb"
17
+ end
18
+
19
+ # Generates a migration file +db/migrate/20230220123456_create_tanshuku_urls.rb+.
20
+ #
21
+ # @return [void]
22
+ def copy_migration_file
23
+ filename = "20230220123456_create_tanshuku_urls.rb"
24
+ copy_file "../../../db/migrate/#{filename}", "db/migrate/#{filename}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ Tanshuku.configure do |config|
4
+ # Your default URL options for `url_for`. Defaults to `{}`.
5
+ # config.default_url_options = { Your configurations here }
6
+
7
+ # Your default error reporter that is used when failed to shorten a URL. Defaults to
8
+ # `Tanshuku::Configuration::DefaultExceptionReporter`. It logs the exception and the original URL with
9
+ # `Rails.logger.warn`. This value should respond to `#call` with keyword arguments `exception:` and `original_url:`.
10
+ # config.exception_reporter =
11
+ # lambda { |exception:, original_url:|
12
+ # Your configurations here
13
+ # }
14
+ end
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tanshuku
4
+ # A class for managing Tanshuku configurations.
4
5
  class Configuration
6
+ # The default error-reporter. Calls +Rails.logger.warn+.
5
7
  module DefaultExceptionReporter
8
+ # The default error-reporting procedure. Calls +Rails.logger.warn+.
9
+ #
10
+ # @param exception: [Exception] An error instance at shortening a URL.
11
+ # @param original_url: [String] The original URL failed to shorten.
12
+ #
13
+ # @return [void]
6
14
  def self.call(exception:, original_url:)
7
15
  Rails.logger.warn("Tanshuku - Failed to shorten a URL: #{exception.inspect} for #{original_url.inspect}")
8
16
  end
@@ -10,7 +18,20 @@ module Tanshuku
10
18
 
11
19
  include ActiveModel::Attributes
12
20
 
21
+ # @!attribute [rw] default_url_options
22
+ # Default URL options for Rails' +url_for+. Defaults to +{}+.
23
+ #
24
+ # @return [Hash]
25
+ # @return [void] If you set an invalid object.
13
26
  attribute :default_url_options, default: {}
27
+
28
+ # @!attribute [rw] exception_reporter
29
+ # A error-reporter class, module, or object used when shortening a URL fails. This should respond to +#call+.
30
+ # Defaults to {Tanshuku::Configuration::DefaultExceptionReporter}.
31
+ #
32
+ # @return [Tanshuku::Configuration::DefaultExceptionReporter, #call]
33
+ # A +Tanshuku::Configuration::DefaultExceptionReporter+ instance or an object that responds to +#call+.
34
+ # @return [void] If you set an invalid object.
14
35
  attribute :exception_reporter, default: DefaultExceptionReporter
15
36
  end
16
37
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tanshuku
4
+ # Tanshuku's Rails engine.
4
5
  class Engine < ::Rails::Engine
5
6
  isolate_namespace Tanshuku
6
7
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tanshuku
4
- VERSION = "0.0.11"
4
+ VERSION = "0.0.13"
5
5
  end
data/lib/tanshuku.rb CHANGED
@@ -4,13 +4,28 @@ require_relative "tanshuku/configuration"
4
4
  require_relative "tanshuku/engine"
5
5
  require_relative "tanshuku/version"
6
6
 
7
+ # Tanshuku's namespace.
7
8
  module Tanshuku
9
+ # Returns a configuration object for Tanshuku.
10
+ #
11
+ # @return [Tanshuku::Configuration]
8
12
  def self.config
9
13
  Mutex.new.synchronize do
10
14
  @config ||= Configuration.new
11
15
  end
12
16
  end
13
17
 
18
+ # Configures Tanshuku.
19
+ #
20
+ # @yieldparam config [Tanshuku::Configuration] A configuration object that is yielded.
21
+ # @yieldreturn [void]
22
+ #
23
+ # @return [void]
24
+ #
25
+ # @example
26
+ # Tanshuku.configure do |config|
27
+ # config.default_url_options = { host: "localhost", protocol: :https }
28
+ # end
14
29
  def self.configure
15
30
  yield config
16
31
  end
data/tmp/.keep ADDED
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tanshuku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - kg8m
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-10 00:00:00.000000000 Z
11
+ date: 2023-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -48,6 +48,7 @@ extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
50
  - LICENSE
51
+ - README.md
51
52
  - Rakefile
52
53
  - app/controllers/tanshuku/urls_controller.rb
53
54
  - app/models/tanshuku/url.rb
@@ -55,10 +56,13 @@ files:
55
56
  - config/locales/ja.yml
56
57
  - config/routes.rb
57
58
  - db/migrate/20230220123456_create_tanshuku_urls.rb
59
+ - lib/generators/tanshuku/install_generator.rb
60
+ - lib/generators/templates/initializer.rb
58
61
  - lib/tanshuku.rb
59
62
  - lib/tanshuku/configuration.rb
60
63
  - lib/tanshuku/engine.rb
61
64
  - lib/tanshuku/version.rb
65
+ - tmp/.keep
62
66
  homepage: https://github.com/kg8m/tanshuku
63
67
  licenses:
64
68
  - MIT