Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pre generate all URNs #276

Merged
merged 5 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 33 additions & 30 deletions app/models/urn.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
# 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.
#
# == Schema Information
#
# Example:
# Table name: urns
#
# Urn.generate('teacher') # => "IRPTE12345"
# Urn.generate('teacher') # => "IRPTE12345"
# Urn.generate('salaried_trainee') # => "IRPST12345"
# id :bigint not null, primary key
# code :string
# prefix :string
# suffix :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Urn
attr_reader :value
attr_writer :suffix

def self.generate(applicant_type)
code = applicant_type_code(applicant_type)
PREFIX + code + Array.new(LENGTH) { CHARSET.sample }.join
end
class Urn < ApplicationRecord
class NoUrnAvailableError < StandardError; end

CHARSET = %w[0 1 2 3 4 5 6 7 8 9].freeze
PREFIX = "IRP"
LENGTH = 5
private_constant :CHARSET, :PREFIX, :LENGTH
PREFIX = "IRP".freeze
MAX_SUFFIX = 99_999
PADDING_SIZE = MAX_SUFFIX.to_s.size
VALID_CODES = {
"teacher" => "TE",
"salaried_trainee" => "ST",
}.freeze

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}")
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

def to_s
[prefix, code, sprintf("%0#{PADDING_SIZE}d", suffix)].join
end
private_methods :applicant_type_code
end
62 changes: 62 additions & 0 deletions app/services/generate_urns.rb
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
2 changes: 1 addition & 1 deletion app/services/submit_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def create_application
date_of_entry: form.date_of_entry,
start_date: form.start_date,
subject: SubjectStep.new(form).answer.formatted_value,
urn: Urn.generate(form.application_route),
urn: Urn.next(form.application_route),
visa_type: form.visa_type,
)
end
Expand Down
2 changes: 2 additions & 0 deletions bin/app-startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ set -e

# run migrations
bundle exec rails db:migrate
# Front load urn generation
bundle exec rake urn:generate

# add seed data in review environment
if [[ "$RAILS_ENV" = "review" || "$RAILS_ENV" = "development" ]]; then
Expand Down
12 changes: 12 additions & 0 deletions db/migrate/20230927092305_create_urns.rb
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
11 changes: 10 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions lib/tasks/urn.rake
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
2 changes: 1 addition & 1 deletion spec/factories/applications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
visa_type { VisaStep::VALID_ANSWERS_OPTIONS.reject { _1 == "Other" }.sample }
date_of_entry { Time.zone.today }
start_date { 1.month.from_now.to_date }
urn { Urn.generate(application_route) }
urn { Urn.next(application_route) }

factory :teacher_application do
application_route { "teacher" }
Expand Down
7 changes: 7 additions & 0 deletions spec/factories/urns.rb
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 }
suffix { Array.new(10) { _1 }.sample }
end
end
6 changes: 6 additions & 0 deletions spec/features/admin_console/applications_list_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@

def given_there_are_few_applications
# Create 2 specific applications for search tests
create_list(:urn, 5, code: "TE")
create_list(:urn, 5, 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")

Expand All @@ -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
Expand Down
90 changes: 90 additions & 0 deletions spec/fixtures/urns.yml
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

Loading