tanshuku 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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