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

Application URN #318

Merged
merged 3 commits into from
Oct 31, 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
10 changes: 9 additions & 1 deletion app/models/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,20 @@ def mark_as_qa!
unique_by: %i[application_id status])
end

after_create :set_urn

validates(:application_date, presence: true)
validates(:application_route, presence: true)
validates(:date_of_entry, presence: true)
validates(:start_date, presence: true)
validates(:subject, presence: true)
validates(:visa_type, presence: true)
validates(:applicant, presence: true)
validates(:urn, uniqueness: true)
validates(:urn, uniqueness: true, allow_blank: true)

private

def set_urn
update(urn: Urn.new(self).urn) if urn.blank?
end
end
81 changes: 81 additions & 0 deletions app/models/application/urn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#
# build a pseudo random urns list with the existing urns removed from that list
# and we calculate the global index of the current application based on list sorted by created_at
# then we get the urn located at the determined application index
#

class Application::Urn
class << self
def reset_urns
const_set(:URNS, build_urns)
nil
end

def build_urns
{
"teacher" => build_list("TE"),
"salaried_trainee" => build_list("ST"),
}.freeze
end

def build_list(code)
su_size = LENGTH.to_s.size
Array
.new(LENGTH) { [PREFIX, code, sprintf("%0##{su_size}d", _1)].join }
.drop(1)
.shuffle!
end
end

LENGTH = 99_999
PREFIX = "IRP".freeze
URNS = build_urns

def initialize(application)
@application = application
end

def urn
MUTEX.synchronize do
increase_suffix if urns_exhausted?
available_urns[application_index]
end
end

private

def increase_suffix
self.class.const_set(:LENGTH, "#{self.class::LENGTH}9".to_i)
self.class.reset_urns
end

def urns_exhausted?
application_index > available_urns.size
end

def application_index
return @application_index if @application_index

@application_index = FindApplicationIndexQuery
.new(application_id: @application.id)
.execute
.to_a
.dig(0, "application_index")

raise(ArgumentError, "application not found") unless @application_index

@application_index -= 1 # FindApplicationIndexQuery returns a index starting at 1
@application_index -= used_urns.size # removes the existing applications to have a correct index
@application_index
end

def available_urns
@available_urns ||= URNS.fetch(@application.application_route) - used_urns
end

def used_urns
@used_urns ||= Application.where(application_route: @application.application_route).pluck(:urn)
end

MUTEX = Mutex.new
end
115 changes: 0 additions & 115 deletions app/models/urn.rb

This file was deleted.

33 changes: 33 additions & 0 deletions app/queries/applications_index_query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class ApplicationsIndexQuery
delegate :to_sql, to: :query

def execute
ApplicationRecord.connection.execute(to_sql)
end

def query
@query ||= applications
.project(projection)
.order(applications[:created_at].asc)
end

def projection
[
applications[:id],
row_number.as("application_index"),
]
end

def row_number
Arel::Nodes::Over.new(
Arel::Nodes::NamedFunction.new("ROW_NUMBER", []),
Arel.sql("(ORDER BY created_at ASC)"),
)
end

private

def applications
Application.arel_table
end
end
37 changes: 37 additions & 0 deletions app/queries/find_application_index_query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class FindApplicationIndexQuery
delegate :to_sql, to: :query
attr_reader :application_id

def initialize(application_id:)
@application_id = application_id
end

def execute
ApplicationRecord.connection.execute(to_sql)
end

def query
@query ||= manager
.project(projection)
.from(from_clause)
.where(where_clause)
end

def projection
[Arel.star]
end

def from_clause
ApplicationsIndexQuery.new.query.as("list")
end

def where_clause
Arel.sql("list.id").eq(application_id)
end

private

def manager
@manager ||= Arel::SelectManager.new(Arel::Table.engine)
end
end
1 change: 0 additions & 1 deletion app/services/submit_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def create_application
date_of_entry: form.date_of_entry,
start_date: form.start_date,
subject: SubjectStep.new(form).answer.formatted_value,
urn: Urn.next(form.application_route),
visa_type: form.visa_type,
)
end
Expand Down
7 changes: 0 additions & 7 deletions config/initializers/urn.rb

This file was deleted.

1 change: 0 additions & 1 deletion spec/factories/applications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
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.next(application_route) }

factory :teacher_application do
application_route { "teacher" }
Expand Down
35 changes: 35 additions & 0 deletions spec/models/application/urn_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require "rails_helper"

RSpec.describe Application::Urn do
describe "constants" do
it { expect(described_class::LENGTH).to eq(99_999) }
it { expect(described_class::PREFIX).to eq("IRP") }
it { expect(described_class::URNS.keys).to contain_exactly("teacher", "salaried_trainee") }
it { expect(described_class::URNS.dig("teacher", 1)).to include("IRPTE") }
it { expect(described_class::URNS.fetch("teacher").size).to eq(99_998) }
it { expect(described_class::URNS.dig("salaried_trainee", 1)).to include("IRPST") }
it { expect(described_class::URNS.fetch("salaried_trainee").size).to eq(99_998) }
end

describe "urn" do
subject(:service) { described_class.new(application) }

context "teacher" do
let(:application) { create(:teacher_application) }

it { expect(service.urn).to include("IRPTE") }
end

context "salaried_trainee" do
let(:application) { create(:salaried_trainee_application) }

it { expect(service.urn).to include("IRPST") }
end

context "missing application" do
let(:application) { build(:salaried_trainee_application) }

it { expect { service.urn }.to raise_error(ArgumentError, "application not found") }
end
end
end
9 changes: 7 additions & 2 deletions spec/models/application_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@
end

describe "#urn" do
it "is blank before creation" do
application = described_class.new
let(:application) { build(:application) }

it "is blank before creation" do
expect(application.urn).to be_blank
end

it "is set after creation" do
application.save
expect(application.urn).not_to be_blank
end
end

describe ".search" do
Expand Down
Loading