I am currently working on an application that requires that every student registered in the system have a unique registration number upon registration which is auto incrementing for every registration. The registration number should be of the format "01/19" for the first student registered in 2019, "890/19" for 890th student etc.
The data for registration is uploaded from kobo data collection tool therefore it comes to my endpoint within very close time intervals as close to 200 submission can be done once.
I realized that out of 1000 records there are close to 27 duplicate registration numbers. This is how I have implemented the registration number generation logic
def registration_changeset(student, attrs) do
student |> changeset(attrs) |> Accounts.add_registration_number()
end
Then the Accounts context has the following code for adding registration number
def add_registration_number(changeset) do
struct = changeset.data.__struct__
registration_number =
case struct |> last() |> Repo.one() do
nil -> _create_new_registration_number()
resource -> _increment_registration(resource.registration_number)
end
put_change(changeset, :registration_number, registration_number)
end
I am betting on the last created student to be having the latest registration number in the above case
Is there a better way of implementing this?
You need a bit of synchronized code here :)
Create a dedicated process that will serve the single purpose: producing numbers. GenServer.handle_call/3
already does whatever you need, the process mailbox is the perfect queue and OTP would do everything for you.
defmodule MyApp.RegNumHelper do
@moduledoc false
use GenServer
def start_link(opts),
do: GenServer.start_link(__MODULE__, opts, name: name)
def add_registration_number(changeset),
do: GenServer.call(__MODULE__, {:reg_num, changeset})
@impl true
def init(opts), do: {:ok, opts}
@impl true
def handle_call({:reg_num, changeset}, _from, state) do
# your logic assigning changeset
{:reply, changeset, state}
end
end
This approach has another advantage: since the process is already stateful you do not actually need to query the DB every time. Just query it on process start and save the current number into state
.