I am working with a legacy database. I want to insert a record and update a field in the same record using the inserted_at timestamp.
Specifically I have a field named order_date which I want to assign the value of the date part in inserted_at.
I tried using a Multi.insert followed by a Multi.update but the field is not updated.
This is a simplified version of my code:
statements.ex
def create_account_statement
Multi.new()
|> Multi.insert(:account_header, &insert_account_header/1)
|> Multi.update(:order_date, &update_order_date/1)
|> Repo.transaction()
end
defp insert_account_header(%{patient: patient}) do
AccountHeader.new()
|> AccountHeader.update_patient_changeset(patient.oid)
end
defp update_order_date(%{account_header: account_header}) do
AccountHeader.update_order_date_changeset(account_header, account_header.transactiondate)
end
account_header.ex
def new() do
%__MODULE__{}
end
def update_patient_changeset(changeset, patient_oid) do
change(changeset, %{patient_oid: patient_oid})
end
def update_order_date_changeset(changeset, datetime) do
change(changeset, %{order_date: datetime})
end
The transaction runs with any error but the order_date is nil.
Is this the right way to do this or am I totally lost?
Well in that case IO.inspect or dbg to the rescue! Put them in update_order_date_changeset and update_order_date and you’ll see if the value is the right one?
You say “in a test”, but IMO that does not really assert that the transactiondate field is actually set within your code, does it ? Maybe your test is setup in a way that allows this field to be set in the context of your test…
Is transactiondate a field auto-magically set by the database in some way ? Doesn’t it need e.g. read_after_writesto be set to true to read the actual value ?
In any case, to debug that, I would start e.g. by adding some instrumentation at the moment the :order_date step runs, replacing the Multi.update in create_account_statement with a Multi.run :
def create_account_statement
Multi.new()
|> Multi.insert(:account_header, &insert_account_header/1)
|> Multi.run(:order_date, &instrumented_update_fun/2)
|> Repo.transaction()
end
defp instrumented_update_fun(repo, changes) do
with ok = {:ok, updated} <- changes |> update_order_date() |> repo.update do
# ... this is strictly equivalent to `Multi.update` and you can now add
# some instrumentation code here : IO.inspect, dbg, IEx.pry, ...
ok
end
end
You can also try to trace back where that nil value comes from by letting the caller raise if datetime is nil at any point, e.g by adding a guard to the update_order_date_changeset function :
def update_order_date_changeset(changeset, datetime) when not is_nil(datetime) do
change(changeset, %{order_date: datetime})
end
(and then do the same thing within the caller, if applicable)
I appreciate a lot your help and I am ashamed that everything was my mistake. The code works as expected. As a last resort I enabled SQL logging in the test environment and noticed that the insert and update were exactly as I expected.
The problem, is that at the end of the Multi (which I not posted previously) I was returning the result from the :account_header step instead of using the result of the :order_date step.
This is the wrong code
def create_account_statement
Multi.new()
|> Multi.insert(:account_header, &insert_account_header/1)
|> Multi.update(:order_date, &update_order_date/1)
|> Repo.transaction()
|> case do
{:ok, %{account_header: account_header}} ->
{:ok, account_header}
end
end
And this is the fixed code:
def create_account_statement
Multi.new()
|> Multi.insert(:account_header, &insert_account_header/1)
|> Multi.update(:order_date, &update_order_date/1)
|> Repo.transaction()
|> case do
{:ok, %{order_date: account_header}} ->
{:ok, account_header}
end
end
The problem is that I added a new step and kept using the result from the previous step.
I thank you again for your help.
Best Regards,
Hector