Is there a cleaner way to update a deeply nested struct or map?

I’m working through some test-writing around %Plug.Conn{} and I was wondering if there is an easier syntax for editing deeply nested parts of the struct or map.

For example, I wanted to remove a key from a map, and the best I could do is something like this:

conn = conn_fixture()  # e.g. %Plug.Con{:params: %{"email" => "x@y", "and" => "other stuff"}}
params = Map.delete(conn.params, "email")
conn = Map.put(conn, :params, params)

That feels wrong for some reason. Is there a better way?

For maps specifically you can use Kernel.put_in, pop_in, update_in and friends, but for structs it’s reliant on implementation of the Access protocol and may or may not be present for your use case.

https://hexdocs.pm/elixir/Kernel.html#put_in/3

2 Likes

The Access module docs have some samples that require less synthesis, by the way, as well as some examples of some of the nifty things that you can do by using some of the Access functions in conjunction with the Kernel functions I mentioned, such as Access.all.

For your specific example, Access is handy:

pop_in conn, [Access.key(:params), :email]
# => {"x@y", new_conn}
3 Likes