Search code examples
elixirphoenix-frameworkecto

Custom auto incrementing fields for string giving duplicate entries


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?


Solution

  • 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.