grantwest

grantwest

Concise nil safe way to access field on nested struct?

If I have nested structures, and I want to fetch a field from the a nested structure, but handle a nil case, is there a succinct way to do that? Lets say I have a Project struct which has a client field, which is itself a struct that has field name:

project = %Project{}
name = project.client.name

This works, but if client is nil we get an error. If these were maps I could do

name = get_in(project, [:client, :name]) || ""

Now we get the name if it exists, or we have an empty string. This does not work with structs because they don’t implement access. Is there a similarly concise solution for structs?

Most Liked

kevinschweikert

kevinschweikert

In Elixir 1.17 you can use get_in/1 like this get_in(struct.foo.bar)

Docs: Kernel — Elixir v1.17.0-rc.1

In case any of the keys returns nil , then nil will be returned and get_in/1 won’t traverse any further.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

It may be heretical but for all of my ecto structs at least we just went ahead and implemented the Access behavior functions so that we can just use access normally. The ergonomics are really nice.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yeah I think that moduledoc is perhaps a bit too strongly worded, because it isn’t that structs aren’t allowed to use that syntax, it’s just that it won’t work by default.

Not really, it’s worked great. In fact in particular, it works rather well with how Ecto handles associations that aren’t loaded yet. Eg:

shipment.origin_location[:customer][:name]

If you forget to Repo.preload(shipment, [origin_location: :customer]) then you get:

** (UndefinedFunctionError) function Ecto.Association.NotLoaded.fetch/2 is undefined (Ecto.Association.NotLoaded does not implement the Access behaviour.

Which is great! You didn’t load the association and so instead of treating that as just nil it errors, indicating that you need to actually load it to determine if it’s there or not.

chocolatedonut

chocolatedonut

I think it suffices to add @behaviour Access to your struct and implement the 3 @callbacks. If you only need a read access, it’s enough to merely implement

  def fetch(struct, key) do
    {:ok, Map.get(struct, key)}
  end

in your struct (without needing to also add @behaviour Access to it). Though I think you then can’t get any compiler support via @impl Access annotation on the fetch/2 function. (Somebody correct me please if otherwise.)

codeanpeace

codeanpeace

Hmm, pattern matching comes to mind…

For example, pattern matching via a case statement:

project = %Project{...}

name = case project do
  %{client: %{name: name}} -> name
  _ -> ""
end

# as one liners
name = case project, do: (%{client: %{name: name}} -> name; _ -> "")
name = case project do %{client: %{name: name}} -> name; _ -> "" end

Or alternatively, pattern matching via function heads:

project = %Project{...}
name = get_name(project)

defp get_name(%{client: %{name: name}}, do: name
defp get_name(_project), do: ""

Where Next?

Popular in Questions Top

Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement