<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="354720" data-post-id="354720">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="BrightEyesDavid" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/BrightEyesDavid/120/25042_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  BrightEyesDavid
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><a href="https://hexdocs.pm/pathex/lenses.html#for-any-value-in-nested-structure" rel="noopener nofollow ugc">This section of the docs</a> shows what I think I’m after, but I’m rather lost and have not managed to replace any nested value yet.</p>
<p>I have a nested structure of unknown levels, generated from Ecto results and <a href="https://hexdocs.pm/arbor/readme.html#usage" rel="noopener nofollow ugc">Arbor</a>. Simplified:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">%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: []
    }
  ]
}
</code></pre>
<p>There’s always one root Post struct.</p>
<p>I need to update a couple of keys’ values (e.g., <code>:show_reply_form</code> to <code>true</code>) for the potentially nested struct/map that has a given <code>id</code>. The <code>id</code> is coming from a user-triggered LiveView event value.</p>
<p>As part of stumbling through, it seemed as if I needed to change this line from the documentation:</p>
<pre><code>combine(fn recursive -&gt; some() ~&gt; (recursive() ||| matching()) end)
</code></pre>
<p>to the following in order to avoid errors:</p>
<pre><code>combine(fn recursive -&gt; some() ~&gt; (recursive ||| matching(_)) end)
</code></pre>
<p>Here’s what I’m trying:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">target_id = 4

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

test = Pathex.set!(post_tree, path_to_post, "x") # just testing replacement
</code></pre>
<p>It’s not erroring, but nothing is replaced.</p>
<p>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:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  # 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, &amp;(&amp;1.id == target_id)) do
      index when is_integer(index) -&gt;
        # One of the children is the target
        new_children = List.update_at(children, index, &amp;(set_post_data(&amp;1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
      _ -&gt;
        # None of the children are the target; recurse
        new_children = Enum.map(children, &amp;(set_post_data(&amp;1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
    end
  end
</code></pre>
<pre data-code-wrap="elixir"><code class="lang-elixir">    new_values = %{
      show_reply_form: true,
    }
    post_tree = set_post_data(socket.assigns.post, target_id, new_values)
</code></pre>
<p>So I thought I’d see if I could use Pathex. But I’m very open to using a better function than mine, too. <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>Thanks.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="354720" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/pathex-a-library-for-performing-fast-actions-with-nested-data-structures/47754/22">Post #21</a>
	                </div>
	            </div>
              <div id="likers-container-354720" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="354720"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #21"></div>
  </section>
</div>
    <div class="postbit" id="363967" data-post-id="363967">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Asd" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Asd
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Here you go</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule X do
  import Pathex
  import Pathex.Combinator
  import Pathex.Lenses

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

  def set_show_reply_form_to_true(tree, post_id) do
    Pathex.set!(tree, post_lens(post_id) ~&gt; path(:show_reply_form), true)
  end
end
</code></pre>
<p>And try it out with this</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">
%{
  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: []
        }
      ]
    }
  ]
}
|&gt; X.set_show_reply_form_to_true(4)
|&gt; IO.inspect()
</code></pre>
<hr>
<p>A few words about this cryptic thing</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">combine(fn recursive -&gt;
  matching(%{id: ^post_id}) ||| path(:children) ~&gt; some() ~&gt; recursive
end)

</code></pre>
<p>I know that <code>combine</code> is hard to get correctly from the first try, especially when combined with logical operators (like <code>|||</code>), but my advise here is to always think of it as of a recursive tree traversal.</p>
<p>Once you get into the node, it might be the node you were looking for (hence the <code>matching(%{id: ^post_id})</code>, but if it is not (hence the <code>|||</code>), you need to check it’s children. First you need to find where the children are (under <code>children</code> key, hense the <code>path(:children)</code>), 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 <code>~&gt; some()</code>), and then you need to treat them just like you did (hence the <code>~&gt; recursive</code>)</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="363967" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/pathex-a-library-for-performing-fast-actions-with-nested-data-structures/47754/23">Post #22</a>
	                </div>
	            </div>
              <div id="likers-container-363967" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="363967"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>