josevalim

josevalim

Creator of Elixir

Pattern matching is the answer. :slight_smile: Instead of:

if user && user.address && user.address.city do
  ...
else
  ...
end

do:

case user do
  %{address: %{city: city}} when is_binary(city) -> ...
  _ -> ...
end
20
Post #21
sudostack

sudostack OP

Thanks @josevalim for sharing the idea for the below as well. This works better in our Slime partials:

  @doc """
  Save navigation for structs:

  struct_get_in user, [:address, :city]
  """
  def struct_get_in(data, key) when is_atom(key) do
    struct_get_in(data, [key])
  end
  def struct_get_in(data, keys) when is_list(keys) do
    {first, last} = Enum.split(keys, -1)
    first = Enum.map(first, &Access.key(&1, %{}))
    last  = Enum.map(last, &Access.key(&1, nil))

    get_in data, first ++ last
  end
1
Post #22
ku1ik

ku1ik

For cases where we may get either a struct or nil, this is another way of handling it, without case or extra function heads:

struct_or_nil
|> Kernel.||(%{})
|> Map.get(:a_key) # works for both struct and bare map
1
Post #23
LostKobrakai

LostKobrakai

Use Access.get. It can handle nil inputs.

1
Post #24
ku1ik

ku1ik

Well, Access.get requires implementing Access behaviour for struct.

0
Post #25
LostKobrakai

LostKobrakai

Which is not a bad idea given how you seem to be accessing data of of it.

0
Post #26
josevalim

josevalim

Creator of Elixir

One year later but the answer to your question is get_in+Access.key. :slight_smile:

9
Post #27
tomekowal

tomekowal

@josevalim I don’t believe that is true.

If you have something like this:

o = %OuterStruct{
  inner: %InnerStruct{
    field: 1
  }
}

get_in o, [Access.key(:inner), Access.key(:field)] #returns 1

o = %OuterStruct{
  inner: nil
}

get_in o, [Access.key(:inner), Access.key(:field)]
# returns
** (BadMapError) expected a map, got: nil
    (elixir 1.12.3) lib/map.ex:464: Map.get(nil, :pairing, :a)
    (elixir 1.12.3) lib/access.ex:459: anonymous fn/5 in Access.key/2

This happens because Access.key returns default only for a missing key. The :inner key is not missing, it is just set to nil so it is passed to next accessor.

1
Post #28
josevalim

josevalim

Creator of Elixir

I get a different behaviour:

iex(2)> defmodule S do
...(2)> defstruct [:inner]
...(2)> end
iex(3)> get_in %S{inner: nil}, [Access.key(:inner), Access.key(:field)]
nil
iex(4)> get_in %S{inner: nil}, [Access.key(:unknown), Access.key(:field)]
nil

But i am on v1.13-rc.0. Maybe we fixed something?

2
Post #29