Skip to content

don't enumerate a struct with Map.new in diff.ex#3970

Closed
zachdaniel wants to merge 1 commit intophoenixframework:mainfrom
zachdaniel:dont-enumerate-a-struct
Closed

don't enumerate a struct with Map.new in diff.ex#3970
zachdaniel wants to merge 1 commit intophoenixframework:mainfrom
zachdaniel:dont-enumerate-a-struct

Conversation

@zachdaniel
Copy link
Contributor

This resolves an issue encountered while upgrading to 1.1

The stack traces shows 1.2.0-dev because I cloned it locally while testing, but the error is present in prior versions.

  1) test_name (MyApp.MyTest)
     test/my_app/my_test.exs:117
     ** (EXIT from #PID<0.1149.0>) an exception was raised:
         ** (Protocol.UndefinedError) protocol Enumerable not implemented for type Ash.CiString (a struct). This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Floki.HTMLTree, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Iter, Jason.OrderedObject, LazyHTML, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Req.Response.Async, Rewrite, Stream, StreamData

     Got value:

         #Ash.CiString<"[email protected]">

             (elixir 1.18.3) lib/enum.ex:1: Enumerable.impl_for!/1
             (elixir 1.18.3) lib/enum.ex:166: Enumerable.reduce/3
             (elixir 1.18.3) lib/enum.ex:4515: Enum.map/2
             (elixir 1.18.3) lib/map.ex:262: Map.new_from_enum/2
             (phoenix_live_view 1.2.0-dev) lib/phoenix_live_view/test/diff.ex:118: anonymous fn/2 in Phoenix.LiveViewTest.Diff.resolve_templates/2
             (elixir 1.18.3) lib/map.ex:257: Map.do_map/2
             (elixir 1.18.3) lib/map.ex:257: Map.do_map/2
             (elixir 1.18.3) lib/map.ex:251: Map.new_from_map/2
             (phoenix_live_view 1.2.0-dev) lib/phoenix_live_view/test/diff.ex:74: Phoenix.LiveViewTest.Diff.deep_merge_diff/2
             (phoenix_live_view 1.2.0-dev) lib/phoenix_live_view/test/diff.ex:19: Phoenix.LiveViewTest.Diff.merge_diff/2
             (phoenix_live_view 1.2.0-dev) lib/phoenix_live_view/test/client_proxy.ex:225: Phoenix.LiveViewTest.ClientProxy.mount_view/4
             (phoenix_live_view 1.2.0-dev) lib/phoenix_live_view/test/client_proxy.ex:172: Phoenix.LiveViewTest.ClientProxy.init/1
             (stdlib 6.2.2) gen_server.erl:2229: :gen_server.init_it/2
             (stdlib 6.2.2) gen_server.erl:2184: :gen_server.init_it/6
             (stdlib 6.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

resolve_templates(Map.put(rendered, @static, Map.fetch!(template, static)), template)
end

defp resolve_templates(%struct{} = rendered, template) do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how structs were really meant to be handled here, but this seemed reasonable with limited knowledge.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show the template where this happens?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack trace didn't contain the line number or file so it took a while to track it down, sorry 😆.

It is in our app.html.heex, which looks roughly like this:

<div class="flex h-screen bg-white">
  <%= if assigns[:current_user] do %>
    <MyAppWeb.Components.Sidebar.component
      id="sidebar"
      current_user={@current_user}
    />
    <div class="flex-1 ml-64 max-w-[calc(100%-16rem)]">
      <header class="bg-white shadow-xs">
        <div class="px-4 sm:px-6 lg:px-8 py-4">
          <div class="flex items-center justify-between">
            <h1 class="text-xl font-semibold text-gray-900">
              {assigns[:page_title] || "Page title"}
            </h1>
            <div class="flex items-center">
              <%= if assigns[:current_user] do %>
                <span class="text-sm text-gray-700 mr-4">{@current_user.email}</span>
                <.link href={~p"/sign-out"} class="text-sm text-gray-700 hover:text-gray-900">
                  Sign out
                </.link>
              <% else %>
                <.link href={~p"/sign-in"} class="text-sm text-gray-700 hover:text-gray-900">
                  Sign in
                </.link>
              <% end %>
            </div>
          </div>
        </div>
      </header>
      <main class="px-4 py-8 sm:px-6 lg:px-8">
        <.flash_group flash={@flash} />
        {@inner_content}
      </main>
    </div>
  <% else %>
    <div class="flex-1">
      <main class="px-4 py-8 sm:px-6 lg:px-8">
        <.flash_group flash={@flash} />
        {@inner_content}
      </main>
    </div>
  <% end %>
</div>

The problematic area is here:

<span class="text-sm text-gray-700 mr-4">{@current_user.email}</span>

in Ash case insensitive strings have their type retained with a struct, i.e %Ash.CiString{value: "email"}, and that is the struct that gets matched in my example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that struct implements to String.Chars protocol which is what I believe is being used to display the value

SteffenDE added a commit that referenced this pull request Sep 2, 2025
SteffenDE added a commit that referenced this pull request Sep 2, 2025
@zachdaniel zachdaniel deleted the dont-enumerate-a-struct branch September 2, 2025 09:06
SteffenDE added a commit that referenced this pull request Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments