Class: Tanshuku::Url

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/tanshuku/url.rb

Overview

rubocop:disable Rails/ApplicationRecord

An ActiveRecord::Base inherited class for a shortened URL. This class also have some logics for shortening URLs.

Constant Summary collapse

DEFAULT_NAMESPACE =
""

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#created_atActiveSupport::TimeWithZone

Returns A timestamp when the record is created.

Returns:

  • (ActiveSupport::TimeWithZone)

    A timestamp when the record is created.


26
# File 'app/models/tanshuku/url.rb', line 26

DEFAULT_NAMESPACE = ""

#hashed_urlString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This attribute is used for uniqueness of the original URL.

Returns A hashed string of the record’s original URL.

Returns:

  • (String)

    A hashed string of the record’s original URL.


26
# File 'app/models/tanshuku/url.rb', line 26

DEFAULT_NAMESPACE = ""

#keyString

Returns A unique key for the record.

Returns:

  • (String)

    A unique key for the record.


26
# File 'app/models/tanshuku/url.rb', line 26

DEFAULT_NAMESPACE = ""

#urlString

Returns Original, i.e., non-shortened, URL of the record.

Returns:

  • (String)

    Original, i.e., non-shortened, URL of the record.


26
# File 'app/models/tanshuku/url.rb', line 26

DEFAULT_NAMESPACE = ""

Class Method Details

.find_by_url(url, namespace: DEFAULT_NAMESPACE) ⇒ Tanshuku::Url?

Finds a Tanshuku::Url record by a non-shortened URL.

Parameters:

  • url (String)

    A non-shortened URL.

  • namespace (String) (defaults to: DEFAULT_NAMESPACE)

    A namespace for the URL.

Returns:

[View source]

113
114
115
116
117
118
# File 'app/models/tanshuku/url.rb', line 113

def self.find_by_url(url, namespace: DEFAULT_NAMESPACE)
  normalized_url = normalize_url(url)
  hashed_url = hash_url(normalized_url, namespace:)

  find_by(hashed_url:)
end

.generate_keyString

Note:

This method calls Configuration#key_generator’s call and returns its return value.

Generates a unique key for a shortened URL.

Returns:

[View source]

149
150
151
# File 'app/models/tanshuku/url.rb', line 149

def self.generate_key
  Tanshuku.config.key_generator.call
end

.hash_url(url, namespace: DEFAULT_NAMESPACE) ⇒ String

Note:

This method calls Configuration#url_hasher’s call and returns its return value.

Hashes a URL.

Parameters:

  • url (String)

    A non-hashed URL.

  • namespace (String) (defaults to: DEFAULT_NAMESPACE)

    A namespace for the URL.

Returns:

[View source]

140
141
142
# File 'app/models/tanshuku/url.rb', line 140

def self.hash_url(url, namespace: DEFAULT_NAMESPACE)
  Tanshuku.config.url_hasher.call(url, namespace:)
end

.normalize_url(url) ⇒ String

Normalizes a URL. Adds or removes a trailing slash, removes ? for an empty query, and so on. And sorts query keys.

Parameters:

  • url (String)

    A non-normalized URL.

Returns:

  • (String)

    A normalized URL.

[View source]

126
127
128
129
130
# File 'app/models/tanshuku/url.rb', line 126

def self.normalize_url(url)
  parsed_url = Addressable::URI.parse(url)
  parsed_url.query_values = Rack::Utils.parse_query(parsed_url.query)
  parsed_url.normalize.to_s
end

.report_exception(exception:, original_url:) ⇒ void

Note:

This method calls Configuration#exception_reporter’s call and returns its return value.

This method returns an undefined value.

Reports an exception when failed to shorten a URL.

Parameters:

  • exception (Exception)

    An error instance at shortening a URL.

  • original_url (String)

    The original URL failed to shorten.

[View source]

161
162
163
# File 'app/models/tanshuku/url.rb', line 161

def self.report_exception(exception:, original_url:)
  Tanshuku.config.exception_reporter.call(exception:, original_url:)
end

.shorten(original_url, namespace: DEFAULT_NAMESPACE, url_options: {}) ⇒ String

Note:

If a Tanshuku::Url record already exists, no additional record will be created and the existing record will be used.

Note:

The given URL will be normalized before shortening. So for example, shorten(“google.com/”) and shorten(“google.com”) have the same result.

Shortens a URL. Builds and saves a Tanshuku::Url record with generating a unique key for the given URL and namespace. Then returns the record’s shortened URL with the given URL options.

Examples:

If succeeded to shorten a URL.

Tanshuku::Url.shorten("https://google.com/")  #=> "http://localhost/t/abcdefghij0123456789"

If failed to shorten a URL.

Tanshuku::Url.shorten("https://google.com/")  #=> "https://google.com/"

With ad hoc URL options.

Tanshuku::Url.shorten("https://google.com/", url_options: { host: "verycool.example.com" })
#=> "https://verycool.example.com/t/0123456789abcdefghij"

Tanshuku::Url.shorten("https://google.com/", url_options: { protocol: :http })
#=> "http://example.com/t/abcde01234fghij56789"

With a namespace.

# When no record exists for “https://google.com/”, a new record will be created.
Tanshuku::Url.shorten("https://google.com/")  #=> "https://example.com/t/abc012def345ghi678j9"

# Even when a record already exists for “https://google.com/”, an additional record will be created if namespace
# is specified.
Tanshuku::Url.shorten("https://google.com/", namespace: "a")  #=> "https://example.com/t/ab01cd23ef45gh67ij89"
Tanshuku::Url.shorten("https://google.com/", namespace: "b")  #=> "https://example.com/t/a0b1c2d3e4f5g6h7i8j9"
Tanshuku::Url.shorten("https://google.com/", namespace: "c")  #=> "https://example.com/t/abcd0123efgh4567ij89"

# When the same URL and the same namespace is specified, no additional record will be created.
Tanshuku::Url.shorten("https://google.com/", namespace: "a")  #=> "https://example.com/t/ab01cd23ef45gh67ij89"
Tanshuku::Url.shorten("https://google.com/", namespace: "a")  #=> "https://example.com/t/ab01cd23ef45gh67ij89"

Parameters:

  • original_url (String)

    The original, i.e., non-shortened, URL.

  • namespace (String) (defaults to: DEFAULT_NAMESPACE)

    A namespace for shorteting URL. Shortened URLs are unique in namespaces.

  • url_options (Hash) (defaults to: {})

    An option for Rails’ url_for.

Returns:

  • (String)

    A shortened URL if succeeded to shorten the original URL.

  • (String)

    The original URL if failed to shorten it.

[View source]

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/models/tanshuku/url.rb', line 76

def self.shorten(original_url, namespace: DEFAULT_NAMESPACE, url_options: {})
  raise ArgumentError, "original_url should be present" unless original_url

  url = normalize_url(original_url)
  retries = 0

  begin
    transaction do
      record =
        create_or_find_by!(hashed_url: hash_url(url, namespace:)) do |r|
          r.attributes = { url:, key: generate_key }
        end

      record.shortened_url(url_options)
    end
  # ActiveRecord::RecordNotFound is raised when the key is duplicated.
  rescue ActiveRecord::RecordNotFound => e
    if retries < 10
      retries += 1
      retry
    else
      report_exception(exception: e, original_url:)
      original_url
    end
  end
rescue StandardError => e
  report_exception(exception: e, original_url:)
  original_url
end

Instance Method Details

#shortened_url(url_options = {}) ⇒ String

The record’s shortened URL.

Parameters:

  • url_options (Hash) (defaults to: {})

    An option for Rails’ url_for.

Returns:

  • (String)

    A shortened URL.

[View source]

170
171
172
173
174
175
176
177
# File 'app/models/tanshuku/url.rb', line 170

def shortened_url(url_options = {})
  url_options = url_options.symbolize_keys
  url_options[:controller] = "tanshuku/urls"
  url_options[:action] = :show
  url_options[:key] = key

  Tanshuku::Engine.routes.url_for(url_options)
end