I’m attempting to create multiple users at once. If even one user fails to be created, I want the error to be reported and none of the users to be created. With some research, I came to find that Repo.transaction
may be able to help me with that, although I’m struggling to get it working. I’m relatively unfamiliar with Ecto, so I’m hoping for some help.
I have users
, a list of maps containing user attributes, being passed to add_users/1
. Then each map is passed to add_user/1
in order to create them. Since I’d like to create all users if all are successful or return failed users without creating any, I though the best place for the transaction
was in add_users/1
.
I included what I’ve tried, and failed with, so far. Any help is appreciated.
When the code is like this:
defp add_users(users) do
Repo.transaction(fn -> Enum.map(users, &add_user/1) end)
end
defp add_user(%{name: name, username: username, email: email, password: password} = user_attrs) do
{roles, user_attrs} = Map.pop(user_attrs, :roles)
user =
%User{}
|> User.changeset(user_attrs)
with %User{} = user <- Repo.insert!(user) do
add_roles(user, roles)
add_extension(user, user_attrs)
end
end
I get:
** (Ecto.InvalidChangesetError) could not perform insert because changeset is invalid.
(ecto) lib/ecto/repo/schema.ex:134: Ecto.Repo.Schema.insert!/4
iex:203: ImportUsers.add_user/1
(elixir) lib/enum.ex:1314: Enum."-map/2-lists^map/1-0-"/2
(ecto) lib/ecto/adapters/sql.ex:620: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
(db_connection) lib/db_connection.ex:1283: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1207: DBConnection.run_begin/3
(db_connection) lib/db_connection.ex:798: DBConnection.transaction/3
When the code is like this:
defp add_users(users) do
Repo.transaction(fn -> Enum.map(users, &add_user/1) end)
end
defp add_user(%{name: name, username: username, email: email, password: password} = user_attrs) do
{roles, user_attrs} = Map.pop(user_attrs, :roles)
user =
%User{}
|> User.changeset(user_attrs)
with {:ok, user} <- Repo.insert(user) do
add_roles(user, roles)
add_extension(user, user_attrs)
end
end
I get a tuple {:ok, [{:ok, user} | {:error, changeset}]}
, which isn’t useful since there are users being created despite others failing.
When the code is like this:
defp add_users(users) do
Repo.transaction(fn -> Enum.map(users, &add_user/1) end)
end
defp add_user(%{name: name, username: username, email: email, password: password} = user_attrs) do
{roles, user_attrs} = Map.pop(user_attrs, :roles)
user =
%User{}
|> User.changeset(user_attrs)
Repo.transaction(fn ->
with %User{} = user <- Repo.insert(user) do
add_roles(user, roles)
add_extension(user, user_attrs)
end
end)
end
I get a database connection error:
** (DBConnection.ConnectionError) transaction rolling back
(db_connection) lib/db_connection.ex:1531: DBConnection.fetch_info/1
(db_connection) lib/db_connection.ex:1232: DBConnection.transaction_meter/3
(db_connection) lib/db_connection.ex:798: DBConnection.transaction/3
(elixir) lib/enum.ex:1314: Enum."-map/2-lists^map/1-0-"/2
(elixir) lib/enum.ex:1314: Enum."-map/2-lists^map/1-0-"/2
(ecto) lib/ecto/adapters/sql.ex:620: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
(db_connection) lib/db_connection.ex:1283: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1207: DBConnection.run_begin/3