I had the same issue as @Miserlou, and settled on the solution below:
I have an App.Helpers
module that I’ve aliased in app_web.ex
(though for just this, it probably makes more sense as AppWeb.Helpers
):
defp html_helpers do
quote do
...
alias App.Helpers
...
end
...
end
In App.Helpers
:
defmodule App.Helpers do
@moduledoc """
Global helper functions
"""
@doc """
Gets a field from a struct that can be deeply nested.
Returns nil if key is not found.
To be used in heex templates.
"""
@spec get_field(struct(), [atom()]) :: any()
def get_field(struct, keys) do
access_keys = Enum.map(keys, &Access.key(&1))
get_in(struct, access_keys)
end
...
end
Then, I use it in heex templates like so:
<%= Helpers.get_field(@foo, [:bar, :baz]) %>
It’s the same thing as:
<%= get_in(foo, [Access.key(:bar), Access.key(:baz)]) %>
but I got tired of repeatedly typing Access.key
and its verbosity when I needed to access a field in a struct returned by Ecto where the preloaded association(s) may be nil.
Edit:
Helpers.get_field/2
can be extended with a third parameter to return some other default value like so:
@spec get_field(struct(), [atom()], any()) :: any()
def get_field(struct, keys, default \\ nil) do
access_keys = Enum.map(keys, &Access.key(&1))
case get_in(struct, access_keys) do
nil -> default
other -> other
end
end
<%= Helpers.get_field(@foo, [:bar, :baz], "No data") %>