-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add uniqueness constraint on applications.urn
Urn.call returns a unique urn for the application route. URN suffix is generated from a UUID and guaranties uniqueness and randomness
- Loading branch information
1 parent
9a5a754
commit a7ffb90
Showing
10 changed files
with
126 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,97 @@ | ||
# frozen_string_literal: true | ||
|
||
# Urn represents a Uniform Resource Name (URN) generator. | ||
# It generates a URN with a fixed prefix and a random alphanumeric suffix. | ||
# It generates a URN with a fixed prefix and suffix created from UUID | ||
# converted to based 8 to only contain digits. | ||
# | ||
# Suffix Algo base 8: | ||
# generate a UUID | ||
# remove any '-' chars | ||
# convert to a binary string | ||
# split in groups of 3 bits, each group is a one char in base 8; from "000" to "111" | ||
# convert each group to a char in base 10; from "0" to "7" => CHARSET | ||
# map each char to the CHARSET | ||
# | ||
# Example: | ||
# original idea: https://www.fastruby.io/blog/ruby/uuid/friendlier-uuid-urls-in-ruby.html | ||
# | ||
# Urn.generate('teacher') # => "IRPTE12345" | ||
# Urn.generate('teacher') # => "IRPTE12345" | ||
# Urn.generate('salaried_trainee') # => "IRPST12345" | ||
# Example: | ||
# | ||
# Urn.call("teacher", base: 4) => "IRPTE2332300320121012033313101030130020311212023302313031301300020132" | ||
# Urn.call("teacher", base: 8) => "IRPTE1534743322003655447026533317435036675213160" | ||
# Urn.call("teacher", base: 16) => "IRPTEA8B252DB88114FCB991BF6763262CD04" | ||
# Urn.call("teacher", base: 32) => "IRPTE13MVE7PCVHKP5S5BQDC47KK2IQ" | ||
# Urn.call("teacher", base: 64) => "IRPTEBJ_8UdgBaxHZo9QRxawrn | ||
|
||
class Urn | ||
attr_reader :value | ||
attr_writer :suffix | ||
CHARSET = %w[ | ||
0 1 2 3 4 5 6 7 | ||
8 9 A B C D E F | ||
G H I J K L M N O P Q R S T U V | ||
W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z - _ | ||
].freeze | ||
|
||
def self.generate(applicant_type) | ||
code = applicant_type_code(applicant_type) | ||
PREFIX + code + Array.new(LENGTH) { CHARSET.sample }.join | ||
PREFIX = "IRP" | ||
TEACHER_ROUTE = "teacher" | ||
TRAINEE_ROUTE = "salaried_trainee" | ||
ROUTE_MAPPING = { | ||
TEACHER_ROUTE => "TE", | ||
TRAINEE_ROUTE => "ST", | ||
}.freeze | ||
|
||
def self.call(...) | ||
service = new(...) | ||
service.generate_suffix | ||
service.urn | ||
end | ||
|
||
CHARSET = %w[0 1 2 3 4 5 6 7 8 9].freeze | ||
PREFIX = "IRP" | ||
LENGTH = 5 | ||
private_constant :CHARSET, :PREFIX, :LENGTH | ||
|
||
def self.applicant_type_code(applicant_type) | ||
case applicant_type | ||
when "teacher" | ||
"TE" | ||
when "salaried_trainee" | ||
"ST" | ||
else | ||
raise(ArgumentError, "Invalid applicant type: #{applicant_type}") | ||
end | ||
end | ||
private_methods :applicant_type_code | ||
def initialize(route, prefix: PREFIX, route_mapping: ROUTE_MAPPING, base: 64) | ||
@prefix = prefix | ||
@code = route_mapping.fetch(route) | ||
@base = base | ||
@bit_group_size = (base - 1).to_s(2).size | ||
end | ||
|
||
def urn | ||
[prefix, code, suffix].join | ||
end | ||
|
||
def generate_suffix | ||
@suffix = ( | ||
method(:binary_coded_decimal) >> | ||
method(:add_padding) >> | ||
method(:to_base) >> | ||
method(:charset_encode) | ||
).call(compact_uuid) | ||
end | ||
|
||
private | ||
|
||
attr_reader :prefix, :code, :suffix, :base, :bit_group_size | ||
|
||
def compact_uuid | ||
SecureRandom.uuid.tr("-", "") | ||
end | ||
|
||
def binary_coded_decimal(uuid) | ||
uuid.chars.map { |c| c.hex.to_s(2).rjust(4, "0") }.join | ||
end | ||
|
||
def add_padding(str) | ||
missing_chars = str.size % bit_group_size | ||
return str if missing_chars.zero? | ||
|
||
padding = "0" * missing_chars | ||
padding + str | ||
end | ||
|
||
def to_base(str) | ||
regex = %r(.{#{bit_group_size}}) | ||
str | ||
.scan(regex) | ||
.map { |x| x.to_i(2) } | ||
end | ||
|
||
def charset_encode(str) | ||
str.map { |x| CHARSET.fetch(x) } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
db/migrate/20230919103917_add_uniqueness_to_application_urn.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class AddUniquenessToApplicationUrn < ActiveRecord::Migration[7.0] | ||
def up | ||
drop_view :duplicate_applications | ||
add_index(:applications, :urn) | ||
create_view :duplicate_applications, version: 3 | ||
end | ||
|
||
def down; end | ||
end |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters