How do I insert multiple records in multiple tables in a single db transaction?
You can use Ash.DataLayer — ash v2.15.8 to call multiple actions inside a transaction.
You just have to return the notifications and publish them after the transaction Notifiers — ash v2.15.8
Thanks @barnabasJ.
With your answer, I could move one step forward.
Now, the question is: in case of failure, I call Ash.DataLayer.rollback(resource, term), right? I suppose that term is any identifier that I wanna return to specify which step could not be completed in the transaction, right? If my considerations are ok, the Ash.DataLayer.transaction will return, in case of failure, {:error, term}, right? And so does the run function.
With the setup above, when I call the action, I get the following error (only in the case of failure/rollback, in case of success, it works):
rollback []
↳ Garagem.Operacional.Estadia.run_0_generated_A916B576240B8B6C9282520D0B5CEE0D/2, at: lib/garagem/operacional/resources/estadia.ex:117
** (Ash.Error.Unknown) Unknown Error
* unknown error: :error_on_step_X
(ash 2.15.8) lib/ash/api/api.ex:2183: Ash.Api.unwrap_or_raise!/3
(stdlib 4.3.1.1) erl_eval.erl:744: :erl_eval.do_apply/7
(stdlib 4.3.1.1) erl_eval.erl:492: :erl_eval.expr/6
(elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
Could you help me out?
Are you doing this from inside of an action? Can I see the action you’re writing?
Sure.
action :faturar, :map do
argument :estadia, :map, allow_nil?: false
argument :terminal, :map, allow_nil?: false
argument :parceiro_modalidade, :map, allow_nil?: false
argument :calculo, :map, allow_nil?: false
argument :valor_cliente_faturado, :decimal, allow_nil?: false
argument :valor_parceiro_faturado, :decimal, allow_nil?: false
argument :vouchers, :integer, allow_nil?: false
argument :registrado_em, :utc_datetime_usec, allow_nil?: false
run(fn input, _context ->
%{"inicio_em" => first_periodo_inicio_em} = List.first(input.arguments.calculo.periodos)
%{"fim_em" => last_periodo_fim_em} = List.last(input.arguments.calculo.periodos)
Ash.DataLayer.transaction(EstadiaFaturamento, fn ->
with {:ok, estadia_faturamento_1, _} <- EstadiaFaturamento.create(%{
estadia_id: 10, # <- on purpose to fire an error
terminal_id: input.arguments.terminal.id,
parceiro_modalidade_id: input.arguments.parceiro_modalidade.id,
periodo_inicio_em: first_periodo_inicio_em,
periodo_fim_em: last_periodo_fim_em,
valor_cliente_calculado: input.arguments.calculo.valor_cliente,
valor_cliente_faturado: input.arguments.valor_cliente_faturado,
valor_parceiro_calculado: input.arguments.calculo.valor_parceiro,
valor_parceiro_faturado: input.arguments.valor_parceiro_faturado,
registrado_em: input.arguments.registrado_em,
vouchers: input.arguments.vouchers,
periodos: input.arguments.calculo.periodos
}, return_notifications?: true)
do
{:ok, estadia_faturamento_1}
else
_ ->
Ash.DataLayer.rollback(EstadiaFaturamento, :error_on_step_X)
end
end)
end)
end
You can do
action :foo, :bar do
transaction? true
run fn _, _ ->
...
end
end
in that case.
The error from the transaction will be raised if you call Resource.action!
vs Resource.action
EDIT:
And if you want to return an error, you’d return an error like {:error, error}
from the action. Take a look at the error handling guide for more on that front. You would potentially want to create your own Ash error using def_ash_error
.
My bad. Great. Thank you very much.