Actually, I think I found your problem. Multi has changed behavior since 2.1.6.
here are the relevant docs for you: Ecto.Multi – Ecto v2.1.6
Do you see the problem? it says “The function…receives changes so far as an argument.”
So you should pass only changes_so_far
and not pass repo
.
like this:
Multi.run(multi_acc, String.to_atom("user#{index}"), fn _changes_so_far ->
{:ok, add_user(user_attrs)}
end)
and also here:
|> Multi.run(:ensure_all_ok, fn changes_so_far ->
@ConstantBall Does that fix it for you?
Ecto.Multi
in Ecto 3 has had a breaking change if you come from version 2.
I cannot believe I missed that. Thanks for catching it. I’m no longer getting an error on Multi.run
, although I’m now getting a WithClauseError
after the |> Repo.transaction()
stating that no clause matching: %User{...}
which I have not been able to make sense of. I don’t see why it would calling add_user/1
given that I haven’t changed the code.
I can help you better if you paste the full error verbatim.
Can you IO.inspect
what your with
expression is returning in add_user/1
?
You can do something like this:
with {roles, user_attrs} = Map.pop(user_attrs, :roles),
{:ok, user} <-
%User{}
|> User.changeset(user_attrs)
|> Repo.insert(),
_ <- add_roles(user, roles),
_ <- add_extension(user, user_attrs) do
{:ok, user} |> IO.inspect(label: "OK response")
else
{:error, changeset} = error ->
error
|> IO.inspect(label: "changeset error")
anything_else ->
IO.inspect(anything_else, label: "anything else")
end
1 Like
Adding IO.inspect
in the with
expression as you suggest and using the following input data where it’s expected to fail because of the second and fourth users (due to duplicate emails and usernames, respectively):
"""
"One Test", onetest@example.com, one.test
"Test, Two", onetest@example.com, two
"Three Test", threetest@example.com, threetest
"Test, Four", fourtest@example.com, threetest
"""
The output is the following:
Anything else: %Accounts.User{
email: "onetest@example.com",
password: "one.test99",
name: "One Test",
username: "one.test",
...
}
Changeset error: {:error,
#Ecto.Changeset<
action: :insert,
changes: %{
account: #Ecto.Changeset<action: :insert, changes: %{}, errors: [],
data: #Accounts.Account<>, valid?: true>,
email: "onetest@example.com",
name: "Two Test",
password: "two99",
username: "two",
},
errors: [email: {"has already been taken", []}],
data: #Accounts.User<>,
valid?: false
>}
** (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
iex:164: ImportUsers.add_user/1
iex:142: anonymous fn/2 in ImportUsers.add_users/1
(ecto) lib/ecto/multi.ex:422: Ecto.Multi.apply_operation/5
(elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/multi.ex:412: anonymous fn/5 in Ecto.Multi.apply_operations/5
The “anything else” is your problem. It’s a return value not expected by your with
expression. I don’t know what part of your code is returning the %User{}
struct but that’s why this problem is happening.
To put it another way, add_user/1
isn’t working in the way you expect. It needs to always return {:ok, something}
or {:error, something}
. This isn’t happening because one of the function in your with
clause is returning %User{}
struct.
You can debug it yourself from here. IO.inspect
everything and find out why your with
clause is returning a bare %User{}
struct.
P.S. this is your "with
clause":
{roles, user_attrs} = Map.pop(user_attrs, :roles),
{:ok, user} <-
%User{}
|> User.changeset(user_attrs)
|> Repo.insert(),
_ <- add_roles(user, roles),
_ <- add_extension(user, user_attrs)
so try something like this:
{roles, user_attrs} = Map.pop(user_attrs, :roles) |> IO.inspect(label: "with clause function 0"),
{:ok, user} <-
%User{}
|> User.changeset(user_attrs)
|> Repo.insert() |> IO.inspect(label: "with clause function 1"),
_ <- add_roles(user, roles) |> IO.inspect(label: "with clause function 2"),
_ <- add_extension(user, user_attrs) |> IO.inspect(label: "with clause function 3")
2 Likes
All of your help has been greatly appreciated. Thank you.
1 Like