Updating Elixir structs selectively

In previous Exercism.io problem, we needed to merge two structs in a way that concatenates their list type properties, as opposed to replacing them. For example, let’s say we have a User struct like this:

defmodule User do
  defstruct name: nil, age: 18, phones: []
end

The user has a name, age and list of contact phones. Updating a user with new information is rather straightforward:

annika = %User{name: "Annika"}
%{annika | age: 21}
> %User{age: 21, name: "Annika", phones: []}

Elixir structs, as documentation states, behave as bare maps, meaning you can apply all the Map module functions to them. Since they behave like bare maps, you can also:

annika = %User{name: "Annika"}
Map.put(annika, :age, 21)
> %User{age: 21, name: "Annika", phones: []}

You can even merge two structs like this:

annika = %User{name: "Annika"}
formInput = %User{name: "Annika", age: 21}
Map.merge(annika, formInput)
> %User{age: 21, name: "Annika", phones: []}

But, this kind of merge overwrites values! Let’s try adding phones in that manner:

annika = %User{name: "Annika", phones: ["+44100200300"]}
annika |> Map.merge(%User{name: "Annika", age: 21, phones: ["+44111222333"]})
> %User{age: 21, name: "Annika", phones: ["+44111222333"]}

As you can see, this will overwrite the phones, so only last one is preserved. Map.merge/3 comes to rescue the day:

update_user = fn(user, other) ->
  Map.merge(user, other, fn(_, v1, v2) ->
    cond do
      is_list(v1) ->
        v1 ++ v2
      v2 == nil ->
        v1
      true ->
        v2
    end
  end)
end

annika = %User{name: "Annika", phones: ["+44100200300"]}
annika |> update_user.(%User{phones: ["+44111222333"]})
> %User{age: 18, name: "Annika", phones: ["+44100200300", "+44111222333"]}

What did we do here? Well, we used a combinator function for Map.merge/3 that inspects each prop and decides how to merge them selectively.

We decided to concatenate lists, use other’s user struct values when present (not nil) and fall back to original props otherwise. This allows us to update user only with some properties. The downside is that we can’t update with nil, but that’s outside of scope this time.

Happy structing 🙂

Advertisements
Tagged , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: