I have a feeling there's probably a better way to do this, but I couldn't find it in the history.
This would let you do something like this:
iex> users = [%{name: "john", age: 27, employment: %{title: "Developer", salary: 100}}, %{name: "meg", age: 23, employment: %{title: "Manager", salary: 200}}]
iex> get_in(users, [Access.all(), Access.keys([:name, employment: [:title]])])
[%{employment: %{title: "Developer"}, name: "john"}, %{name: "meg", employment: %{title: "Manager"}}]
With keys partially implemented like this
def keys(keys) do
fn
:get, data, next ->
next.(take(data, keys))
:get_and_update, data, next ->
values = take(data, keys)
case next.(values) do
{values, :pop} -> {values, Map.drop(data, keys)}
{values, updates} -> {values, updates}
end
end
end
And then that `take` function something like this:
@doc """
## Examples
iex> Access.take(%{name: "john", age: 52}, [:name, :age, :height])
%{name: "john", age: 52}
iex> Access.take(%{name: "john", age: 52, employment: %{title: "Developer", salary: 100}}, [:name, :height, employment: [:title]])
%{name: "john", employment: %{title: "Developer"}}
"""
def take(container, keys) when is_list(keys) do
cond do
Enum.all?(keys, &is_atom/1) and is_map(container) ->
Map.take(container, keys)
is_map(container) ->
keys
|> Enum.group_by(&is_atom/1)
|> Enum.reduce(%{}, fn
{true, keys}, map ->
Map.merge(map, Map.take(container, keys))
{false, pairs}, map ->
Map.merge(
map,
for({k, v} <- pairs, into: %{}, do: {k, take(Map.get(container, k), v)})
)
end)
end
end
def take(_container, keys) when is_list(keys) do
raise ArgumentError, "Access.take expects the keys to be a list of atoms or keyword list, got: " <> inspect(keys)
end