Pathex - a library for performing fast actions with nested data structures

This section of the docs shows what I think I’m after, but I’m rather lost and have not managed to replace any nested value yet.

I have a nested structure of unknown levels, generated from Ecto results and Arbor. Simplified:

%Post{
  id: 1,
  show_reply_form: false,
  children: [
    %Post{
      id: 2,
      show_reply_form: false,
      children: [
        %Post{
          id: 4,
          show_reply_form: false,
          children: []
        }
      ]
    },
    %Post{
      id: 3,
      show_reply_form: false,
      children: []
    }
  ]
}

There’s always one root Post struct.

I need to update a couple of keys’ values (e.g., :show_reply_form to true) for the potentially nested struct/map that has a given id. The id is coming from a user-triggered LiveView event value.

As part of stumbling through, it seemed as if I needed to change this line from the documentation:

combine(fn recursive -> some() ~> (recursive() ||| matching()) end)

to the following in order to avoid errors:

combine(fn recursive -> some() ~> (recursive ||| matching(_)) end)

Here’s what I’m trying:

target_id = 4

path_to_post =
  combine(fn recursive -> some() ~> (recursive ||| matching(_)) end)
  ~> matching(%{id: _})
  ~> path(1 / :id) # ???
  ~> filtering(&(&1 == target_id))

test = Pathex.set!(post_tree, path_to_post, "x") # just testing replacement

It’s not erroring, but nothing is replaced.

I do have my own (probably horrible) function that’s working, but I think it’s not memory-efficient and runs through more of the posts than necessary:

  # Prevents recursing into children of target post, but still recurses through the rest
  defp set_post_data(%{id: post_id} = post_tree, target_id, new_values) when post_id == target_id do
    # Root post is target; no recursing required
    Map.merge(post_tree, new_values)
  end
  defp set_post_data(%{children: children} = post_tree, target_id, new_values) do
    case Enum.find_index(children, &(&1.id == target_id)) do
      index when is_integer(index) ->
        # One of the children is the target
        new_children = List.update_at(children, index, &(set_post_data(&1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
      _ ->
        # None of the children are the target; recurse
        new_children = Enum.map(children, &(set_post_data(&1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
    end
  end
    new_values = %{
      show_reply_form: true,
    }
    post_tree = set_post_data(socket.assigns.post, target_id, new_values)

So I thought I’d see if I could use Pathex. But I’m very open to using a better function than mine, too. :slight_smile:

Thanks.