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

Here you go

defmodule X do
  import Pathex
  import Pathex.Combinator
  import Pathex.Lenses

  def post_lens(post_id) do
    combine(fn recursive ->
      matching(%{id: ^post_id}) ||| path(:children) ~> some() ~> recursive
    end)
  end

  def set_show_reply_form_to_true(tree, post_id) do
    Pathex.set!(tree, post_lens(post_id) ~> path(:show_reply_form), true)
  end
end

And try it out with this


%{
  id: 1,
  show_reply_form: false,
  children: [
    %{
      id: 2,
      show_reply_form: false,
      children: [
        %{
          id: 4,
          show_reply_form: false,
          children: []
        }
      ]
    },
    %{
      id: 3,
      show_reply_form: false,
      children: [
        %{
          id: 5,
          show_reply_form: false,
          children: []
        }
      ]
    }
  ]
}
|> X.set_show_reply_form_to_true(4)
|> IO.inspect()

A few words about this cryptic thing

combine(fn recursive ->
  matching(%{id: ^post_id}) ||| path(:children) ~> some() ~> recursive
end)

I know that combine is hard to get correctly from the first try, especially when combined with logical operators (like |||), but my advise here is to always think of it as of a recursive tree traversal.

Once you get into the node, it might be the node you were looking for (hence the matching(%{id: ^post_id}), but if it is not (hence the |||), you need to check it’s children. First you need to find where the children are (under children key, hense the path(:children)), then you need to traverse all children or just some (here I assume we can encounter post with id=4 only once, so we need to find the single one, hence we do ~> some()), and then you need to treat them just like you did (hence the ~> recursive)

1 Like