Setting nested map in state using cast

I am trying to set agent state as:

Agent.cast(agent, fn %{stream: stream, status: %{total: total, entries: entries}} = state ->
      %{state.status | :entries => entries + 1}
    end)

However, I got error:

anonymous fn(%{entries: 1, total: 5000}) in MyApp.CsvsController.write_ecto_rows_schools/4

Can anyone explain please?

that’s not an error. Can you post more of the console output?

You probably missed something with copy/pasting the error, because it doesn’t say anything about the error itself, but I think the issue is that you return the status map instead of the complete state.

If you want to update nested maps, you can use the put_in or update_in functions.

Seconding @hubertlepicki’s comment, it would be helpful if you provided the full error.

Based on context however, you have a pattern in your anonymous function that is %{stream: stream, status: %{total: total, entries: entries}} = state. The error is that the state does not meat this pattern.

This is likely because %{state.status | :entries => entries + 1} does not return the state, it returns the updated status map. you want to use put_in for updating deeply within a nested map.

Thanks, using ‘put_in’ solved the problem as:

Agent.cast(agent, fn %{stream: stream, status: %{total_entries: total_entries, processed_entries: processed_entries}} = state ->
  put_in state, ["status", :processed_entries], processed_entries + just_processed_entries
end) 

but, the state is not being changed

There’s an answer I can give, but I think it’s more important that you try to use some basic debugging techniques to find out what is wrong. Do you know what the result of put_in state, ["status", :processed_entries], processed_entries + just_processed_entries is? Did you try to find out? If so, how?

It has worked when I did:

> Agent.cast(agent, fn %{"stream" => stream, "status" => %{total_entries: total_entries, processed_entries: processed_entries}} = stream_info ->
>       put_in(stream_info, ["status", :processed_entries], processed_entries + just_processed_entries)
>     end)

I am not sure why

EDIT: I see I misinterpreted the remark about the state not being changed. See the answer from @benwilson512

Since Elixir’s variables are immutable, every update in a data structure creates a new copy. This means that if a child element is updated in a nested data structure, the parent is updated too. So in this case the state is changed because you updated its status.

BTW, Elixir also provides put_in and update_in macro’s for slightly more convenient syntax. Using the put_in macro you can write this:

put_in state.status.processed_entries, processed_entries + just_processed_entries

or you can use the update_in macro:

update_in state.status.processed_entries, &(&1 + just_processed_entries)

This way you don’t even need to extract the processed_entries variables first.

1 Like

"status" is not the same as :status. Your put_in is using strings. You may want to become familiar with basic elixir datatypes before trying to use agents.

1 Like