-
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.
This will serve to render a urn and hold the current state of available urns ready to be used. Add GenerateUrns service This service will create all the available urns per application route. They will be randomized, unique and the next available urn to be taken will retreived with the Urn.next method. Once all the urn are exhausted this method will raise an error and the format of the URN will need updating ie increase the size of the suffix set. Urn::MAX_SUFFIX Add rake task urn:generate This task is going to be used to ensure that when start the application service the URNs are ready to be picked. Update code to used `Urn.next` * startup.sh * submitform service * factories Fix specs for urn
- Loading branch information
1 parent
18fb279
commit 5f40c1f
Showing
13 changed files
with
299 additions
and
132 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,115 +1,46 @@ | ||
# frozen_string_literal: true | ||
|
||
# Urn represents a pseudo random Uniform Resource Name (URN) generator. | ||
# Invoking the method `next` returns a unique URN with a fixed prefix | ||
# and a random alphanumeric suffix. | ||
# == Schema Information | ||
# | ||
# Urn.configure do |c| | ||
# c.max_suffix = 11 | ||
# c.seeds = { teacher: ENV['TEACHER_URN_SEED'] } | ||
# c.urns = ->(route) { Application.where(application_route: route).pluck(:urn) } | ||
# end | ||
# Table name: urns | ||
# | ||
# Example: | ||
# id :bigint not null, primary key | ||
# code :string | ||
# prefix :string | ||
# suffix :integer | ||
# created_at :datetime not null | ||
# updated_at :datetime not null | ||
# | ||
# Urn.next('teacher') # => "IRPTE12345" | ||
# Urn.next('teacher') # => "IRPTE12345" | ||
# Urn.next('salaried_trainee') # => "IRPST12345" | ||
# | ||
class Urn | ||
class NoUrnAvailableError < StandardError; end | ||
|
||
class Config | ||
def initialize | ||
@default_prefix = "IRP" | ||
@default_max_suffix = 99_999 | ||
@default_codes = { | ||
teacher: "TE", | ||
salaried_trainee: "ST", | ||
}.with_indifferent_access | ||
@default_urns = ->(_) { [] } | ||
end | ||
|
||
attr_writer :prefix, :codes, :max_suffix, :seeds, :urns, :padding_size | ||
|
||
def prefix | ||
@prefix || @default_prefix | ||
end | ||
|
||
def codes | ||
(@codes || @default_codes).with_indifferent_access | ||
end | ||
|
||
def max_suffix | ||
@max_suffix || @default_max_suffix | ||
end | ||
|
||
def padding_size | ||
@padding_size || max_suffix.to_s.size | ||
end | ||
|
||
def seeds | ||
(@seeds || {}).with_indifferent_access | ||
end | ||
class Urn < ApplicationRecord | ||
class NoUrnAvailableError < StandardError; end | ||
|
||
def urns | ||
@urns || @default_urns | ||
end | ||
PREFIX = "IRP".freeze | ||
MAX_SUFFIX = 99_999 | ||
PADDING_SIZE = MAX_SUFFIX.to_s.size | ||
VALID_CODES = { | ||
"teacher" => "TE", | ||
"salaried_trainee" => "ST", | ||
}.freeze | ||
|
||
def self.next(route) | ||
code = VALID_CODES.fetch(route) | ||
Urn.transaction do | ||
urn = find_by!(code:) | ||
urn.destroy! | ||
urn.to_s | ||
end | ||
rescue KeyError => e | ||
Sentry.capture_exception(e) | ||
raise(ArgumentError, "Unknown route #{route}") | ||
rescue ActiveRecord::RecordNotFound => e | ||
Sentry.capture_exception(e) | ||
raise(NoUrnAvailableError, "There no more unique URN available for #{route}") | ||
end | ||
|
||
class << self | ||
def configure | ||
yield(config) | ||
end | ||
|
||
def config | ||
return @config if @config.present? | ||
|
||
@config = Config.new | ||
end | ||
|
||
def next(route) | ||
routes[route].next | ||
rescue KeyError | ||
raise(ArgumentError, "Invalid route: #{route}, must be one of #{config.codes.keys}") | ||
end | ||
|
||
private | ||
|
||
def routes | ||
@routes ||= Concurrent::Hash.new do |hash, route| | ||
hash[route] = urn_enumerator( | ||
config.codes.fetch(route), | ||
config.seeds.fetch(route, Random.new_seed), | ||
config.urns.call(route), | ||
) | ||
end | ||
end | ||
|
||
def urns(code, seed) | ||
Array | ||
.new(config.max_suffix) { formatter(code, _1) } | ||
.drop(1) | ||
.shuffle!(random: Random.new(seed)) | ||
end | ||
|
||
def formatter(code, suffix) | ||
[config.prefix, code, sprintf("%0#{config.padding_size}d", suffix)].join | ||
end | ||
|
||
def available_urns(code, seed, used_urns) | ||
urns(code, seed) - used_urns | ||
end | ||
|
||
def urn_enumerator(code, seed, used_urns) | ||
list = Concurrent::Array.new(available_urns(code, seed, used_urns)) | ||
error_msg = "you have exhausted urn for code #{code} you need to increase the size of the suffix" | ||
|
||
Enumerator.new do |yielder| | ||
list.each { yielder << _1 } | ||
|
||
raise(NoUrnAvailableError, error_msg) | ||
end | ||
end | ||
def to_s | ||
[prefix, code, sprintf("%0#{PADDING_SIZE}d", suffix)].join | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Service responsible for the generation of all urns | ||
# It will save the set of available urns based the current URN format | ||
# and store it in the database URNs table. | ||
# | ||
# The Urn model will then be able to fetch the next available unique and | ||
# random urn for application submition | ||
# | ||
# Example: | ||
# | ||
# Urn.next("teacher") # => "IRPTE12345" | ||
# Urn.next("teacher") # => "IRPTE12345" | ||
# Urn.next("salaried_trainee") # => "IRPST12345" | ||
# | ||
class GenerateUrns | ||
def self.call | ||
return if Urn.count.positive? # Do not override the current urn state | ||
|
||
Urn.transaction do | ||
Urn::VALID_CODES.each_value do |code| | ||
new(code:).generate | ||
end | ||
end | ||
end | ||
|
||
def initialize(code:) | ||
@code = code | ||
end | ||
|
||
attr_reader :code | ||
|
||
def generate | ||
data = unused_urns.map do |suffix| | ||
{ prefix: Urn::PREFIX, code: code, suffix: suffix } | ||
end | ||
Urn.insert_all(data) # rubocop:disable Rails/SkipsModelValidations | ||
end | ||
|
||
private | ||
|
||
def unused_urns | ||
generate_suffixes - existing_suffixes | ||
end | ||
|
||
def generate_suffixes | ||
Array | ||
.new(Urn::MAX_SUFFIX) { _1 } | ||
.drop(1) | ||
.shuffle! | ||
end | ||
|
||
def existing_suffixes | ||
route = Urn::VALID_CODES.key(code) | ||
Application | ||
.where(application_route: route) | ||
.pluck(:urn) | ||
.map { extract_suffix(_1) } | ||
end | ||
|
||
def extract_suffix(urn) | ||
urn.match(/\d+/)[0].to_i | ||
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
This file was deleted.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
class CreateUrns < ActiveRecord::Migration[7.0] | ||
def change | ||
create_table :urns do |t| | ||
t.string :prefix | ||
t.string :code | ||
t.integer :suffix | ||
|
||
t.timestamps | ||
end | ||
add_index :urns, :code | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace :urn do | ||
desc "generate and randomize unique urns" | ||
task generate: :environment do | ||
puts "running rake task urn:generate ..." | ||
a = Urn.count | ||
GenerateUrns.call | ||
b = Urn.count | ||
puts "#{b - a} URN created" | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
FactoryBot.define do | ||
factory :urn do | ||
prefix { "IRP" } | ||
code { %w[TE ST].sample } | ||
sequence(:suffix) { _1 } | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,8 @@ | |
|
||
def given_there_are_few_applications | ||
# Create 2 specific applications for search tests | ||
create_list(:urn, 25, code: "TE") | ||
create_list(:urn, 25, code: "ST") | ||
unique_applicant = create(:applicant, given_name: "Unique Given Name", middle_name: "Unique Middle Name", family_name: "Unique Family Name", email_address: "[email protected]") | ||
create(:application, applicant: unique_applicant, urn: "Unique Urn 1") | ||
|
||
|
@@ -70,12 +72,16 @@ def given_there_are_few_applications | |
end | ||
|
||
def given_there_is_an_application_that_breached_sla | ||
create_list(:urn, 5, code: "TE") | ||
create_list(:urn, 5, code: "ST") | ||
applicant = create(:applicant) | ||
application = create(:application, applicant:) | ||
application.application_progress.update(initial_checks_completed_at: 4.days.ago) | ||
end | ||
|
||
def given_there_are_applications_with_different_dates | ||
create_list(:urn, 5, code: "TE") | ||
create_list(:urn, 5, code: "ST") | ||
create(:application, application_progress: build(:application_progress, :initial_checks_completed, status: :initial_checks)) | ||
create(:application, application_progress: build(:application_progress, :home_office_checks_completed, status: :home_office_checks)) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
te_one: | ||
suffix: 5668 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_two: | ||
suffix: 21368 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_three: | ||
suffix: 5 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_four: | ||
suffix: 76998 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_five: | ||
suffix: 6559 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_six: | ||
suffix: 6 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_seven: | ||
suffix: 2298 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_eight: | ||
suffix: 1159 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_nine: | ||
suffix: 79298 | ||
prefix: IRP | ||
code: TE | ||
|
||
te_ten: | ||
suffix: 19549 | ||
prefix: IRP | ||
code: TE | ||
|
||
st_one: | ||
suffix: 5668 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_two: | ||
suffix: 29968 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_three: | ||
suffix: 5 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_four: | ||
suffix: 76998 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_five: | ||
suffix: 6559 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_six: | ||
suffix: 6 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_seven: | ||
suffix: 28 | ||
prefix: IRP | ||
code: ST | ||
|
||
st_eight: | ||
suffix: 159 | ||
prefix: IRP | ||
code: ST | ||
|
Oops, something went wrong.