From 1d0601096d8bc18671b2adc0371b7dc55c5859d9 Mon Sep 17 00:00:00 2001 From: fumimowdan Date: Mon, 30 Oct 2023 15:03:22 +0000 Subject: [PATCH] Add Application::Urn This service will retreive the associated urn for an application that exists in the database. When the availabe urn set is exhausted it will increase its size automatically. --- app/models/application/urn.rb | 81 +++++++++++++++++++++++++++++ spec/models/application/urn_spec.rb | 35 +++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 app/models/application/urn.rb create mode 100644 spec/models/application/urn_spec.rb diff --git a/app/models/application/urn.rb b/app/models/application/urn.rb new file mode 100644 index 00000000..3bf3ae4b --- /dev/null +++ b/app/models/application/urn.rb @@ -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 diff --git a/spec/models/application/urn_spec.rb b/spec/models/application/urn_spec.rb new file mode 100644 index 00000000..6f2ef92f --- /dev/null +++ b/spec/models/application/urn_spec.rb @@ -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