Hi,
I have a web app using Phoenix live view on production it has one single end user. It’s an order system. If you like to have more idea about it you may visit here where I asked a question earlier.
One of my mistake, I had to forget to create a unique constraint for customer’s phone_number
. However my code was like this:
def get_or_create_customer_by_phone_number(phone_number) do
case Repo.get_by(Customer, phone_number: phone_number) do
nil ->
case create_customer(%{:phone_number => phone_number}) do
{:ok, customer} -> customer
{:error, %Ecto.Changeset{} = changeset} -> {:error, changeset}
end
customer ->
customer
end
end
With one single request, especially within 2 days it has created same number twice for 5 different numbers, so the 3rd order couldn’t be created because get_by
expect one single record while it returns 2; it throws an error.
Here’s one prove from db query result:
myapp_prod=# select * from customers where phone_number = '437';
id | phone_number | inserted_at | updated_at
-----+--------------+---------------------+---------------------
5 | 437 | 2020-07-07 08:22:26 | 2020-07-09 12:32:08
122 | 437 | 2020-07-09 12:01:51 | 2020-07-09 12:56:40
(2 rows)
myapp_prod=# SELECT o.id, oi.product_id, o.customer_id from Order_Items oi INNER JOIN Orders o on o.id = oi.order_id WHERE o.customer_id IN (5, 122);
id | product_id | customer_id
-----+------------+-------------
126 | 1 | 122
5 | 1 | 5
(2 rows)
myapp_prod=# update orders set customer_id = 5 where id = 126;
UPDATE 1
myapp_prod=# delete from customers where id = 122;
DELETE 1
So, after I read some documentation about race conditions, I had to drop the index for the phone_number
and created a unique index to prevent it.
defmodule MyApp.Repo.Migrations.CreateUniqueConstraintForCustomer do
use Ecto.Migration
def change do
drop index(:customers, [:phone_number])
create unique_index(:customers, [:phone_number])
end
end
then I changed to my code according to the documentation like this:
def get_or_create_customer_by_phone_number(phone_number) do
%Customer{}
|> Ecto.Changeset.change(phone_number: phone_number)
|> Ecto.Changeset.unique_constraint(:phone_number)
|> Repo.insert
|> case do
{:ok, customer} -> customer
{:error, _} -> Repo.get_by(Customer, phone_number: phone_number)
end
end
But still I do want to know how that happened? Any idea guys?
Thanks!