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:



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:



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:



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.



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.



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.



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.



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