So Many Ways to Update a Map with Elixir!
Tags: elixir, phoenix, learning, thisweekilearned
Published: 2020-07-14 14:55:37

Last week I got a pleasant refresher on updating maps. As I type this it sounds like such a basic thing, updating a map, but I guess I haven't done it all that much in this project. Maps are used everywhere, but for the most part I'm taking existing maps and not really manipulating them, just passing them along to be used in an insert or update function. 

In my case I actually wanted to update a map before it got to the changeset, to change the shape of the data. It's actually a follow-up from the work I did around [this](https://dev.to/noelworden/more-custom-validation-work-manipulating-a-keyword-list-to-and-from-a-map-p9b) post. Not being able to recall from memory how to update a map, I went to the ol' search engine, and that's where I came across this little gem:

{% gist https://gist.github.com/noelworden/94075915e671e4617c1f59231a7ff60d %}

It's so elegant it honestly made me smile. Now, there are of course other ways to update a map, from the [docs](https://hexdocs.pm/elixir/Map.html#functions) here are four:

{% gist https://gist.github.com/noelworden/9ad0e7e335f1e3d52abef8f282074b7e %}

Since I learn by example, I'll write them all out, starting with a map to work with:

{% gist https://gist.github.com/noelworden/543f84da611cd3d66f31bd0135a5a671 %}

Let's start with the shorthand syntax, if the key exists, it is updated:

{% gist https://gist.github.com/noelworden/dec34fc03127834366d200ab42644e0d %}

And if the key doesn't already exist in the map, it will throw an error:

{% gist https://gist.github.com/noelworden/985e187ee46f16e96e9010bb34422464 %}

Next is `Map.put/3`, it's pretty straightforward, if the key already exists it updates the value:


{% gist https://gist.github.com/noelworden/82907fbabfd68374c6f160b471dd1b5b %}

Or, if the key doesn't exist, it adds the new key value pair to the map:

{% gist https://gist.github.com/noelworden/c18b6aa6ae8d9c5801280730cb48e846 %}

Then there is `Map.replace!/4`. This one replaces the value stored, but only if the key exists in the map, otherwise a `KeyError` exception is raised.

The happy path:

{% gist https://gist.github.com/noelworden/2e2c9ba7840d40f56c384c74785009e3 %}

And when the key is not present in the map:

{% gist https://gist.github.com/noelworden/fb769a5af92ba83861cf3d9d28228e61 %}

Then there is `Map.update!/3`, notice the bang (`!`). This updates the key with the provided function, if the key isn't present it raises a `KeyError` exception.

Adding to the existing string of `:charlie`:


{% gist https://gist.github.com/noelworden/0dcb9e04e77fe23de8e6e1e465436c07 %}

And when the `:delta` key doesn't exist in the map:


{% gist https://gist.github.com/noelworden/7d11203b0baa6b36544087e78f125d25 %}

And, last but not least, `Map.update/4`. This is similar to the non-bang version, but instead of throwing an error if the key doesn't exist, it adds it to the map:


{% gist https://gist.github.com/noelworden/e647ba7af12a32b4d4f7d43411b97545 %}

One more note about this function, if the key does not exist in the map, it will be added and assigned the value provided as the third argument, and the function provided as the fourth argument will be ignored:


{% gist https://gist.github.com/noelworden/c27db108558c9847bfc67e186ca213ce %}

Again, since the key `delta:` did not already exist, it used the third argument `"four"` as the value, and did not utilize the function at all. 

These are all great, and have their unique use cases, but one drawback they all have is that if several keys need to be updated, the only way to do that is to repeat the function with piping:

{% gist https://gist.github.com/noelworden/d655d8d0b33d458f1826ff9ca381655a %}

{% gist https://gist.github.com/noelworden/44469a679afb6287c168d781c75f2adf %}

And that is where the shorthand syntax really shines, multiple fields can be updated in the one expression:

{% gist https://gist.github.com/noelworden/231e49c2277a0c6c8300724b5f2d2518 %}

So clean! :heart_eyes: 

As I noted at the beginning of this post, I needed to intercept and update the `attrs` before they were sent to the `changeset`, in order to cast some data into a different shape. My code to do that looks like this:

{% gist https://gist.github.com/noelworden/1f16071add4113168ce9d3d883841681 %}

It's not often that it's necessary to sneak into the `attrs` to tweak data before it gets inserted into the database. As a matter of fact it might be a code smell to some, but it was necessary in this case. I hope this post comes in handy the next time you find yourself needing to update a map, regardless of the scenario.

------

This post is part of an ongoing *This Week I Learned* [series](https://dev.to/noelworden/beginning-of-a-blog-series-5aj3). I welcome any critique, feedback, or suggestions in the comments.