Hello, I’m looking to dry up some LiveView handle_info/event
callbacks that require modifying a nested data structure using update_in/3
in response to CRUD, or rather CUD, actions. The second parameter for update_in/3
is a list of keys that are often functions from the Access module e.g. Access.key!/2
and Access.filter/2
.
For the sake of a concrete example, Person has many Pets, Pet has many Toys, and there are callbacks that update a preloaded person struct assigned to the socket after a user adds, updates, and/or deletes a toy. For the add and delete callbacks, update_in/3
only has to reach into the toys list whereas the update callback goes a step further to find the specific toy within in the toy list.
How would I go about abstracting out the common parts of the traverse path?
person =
%Person{
pets: [
%Pet{
toys: [
%Toy{}
]
}
]
}
# within toy created callback
update_in(
person,
[
Access.key!(:pets),
Access.filter(&(&1.id == pet_id)),
Access.key!(:toys)
],
fn toys -> [toy | toys] end
)
# within toy deleted callback
update_in(
person,
[
Access.key!(:pets),
Access.filter(&(&1.id == pet_id)),
Access.key!(:toys)
],
fn toys -> Enum.reject(toys, &(&1.id == toy_id)) end
)
# within toy updated callback
update_in(
person,
[
Access.key!(:pets),
Access.filter(&(&1.id == pet_id)),
Access.key!(:toys),
Access.filter(&(&1.id == toy_id))
],
fn toy -> %{toy | new_attributes} end
)
# composing list of keys within toy updated callback
update_in(
person,
[
Access.key!(:pets),
Access.filter(&(&1.id == pet_id)),
Access.key!(:toys)
] ++ [Access.filter(&(&1.id == toy_id))],
fn toy -> %{toy | new_attributes} end
)
As demonstrated above, the first three elements in the second parameter list of keys are shared across all three callbacks. Is there a sensible way to refactor and abstract that out? I imagine some form of macros/quote/unquote would be involved… would the added brevity even be worth it given the increased complexity?