Phoenix Verified routes and escaping URI

Trying to do something like.

some_route = "foo/bar"
~p"#{some_route}"

Aside from the fact the compile checker complains, the / in between foo and bar gets escaped inside the ~p somewhere so the request ends up crying that foo%2Fbar can be found.

I realize this has everything to do with escapable chars in my strings.

My question is: what is the best way to go about something like this?
I ran into this issue as a result of generators and the patch assignment on the Live component for the FormComponent.

The issue was, I wanted to use a slug as a @derived param for my resource. But the patch=~p/ does not account for the update to the slug on the post in question when pushing the patch. For example.

  defp save_post(socket, :edit, post_params) do
    case Blog.update_post(socket.assigns.post, post_params) do
      {:ok, post} ->

        {:noreply,
         socket
         |> put_flash(:info, "Post updated successfully")
         |> push_patch(to:  ~p"/#{socket.assigns.patch}")}
...
    end
  end

This has no way of pulling out the new slug from the updated post value. Thus it tried to push patch to the prior value. The idea I had was to break apart the patch assignments. Something like.

  defp save_post(socket, :edit, post_params) do
    case Blog.update_post(socket.assigns.post, post_params) do
      {:ok, post} ->
        notify_parent({:saved, post})

        {:noreply,
         socket
         |> put_flash(:info, "Post updated successfully")
         |> push_patch(to:  ~p"/#{socket.assigns.patch}/#{post.slug}")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

note: p"/#{socket.assigns.patch}/#{post.slug}")

And at the time of assignment.

  <.live_component
    module={MorphicProWeb.Admin.PostLive.FormComponent}
    id={@post.id}
    title={@page_title}
    action={@live_action}
    post={@post}
    patch={"admin/posts")}
  />

Thus admin/posts turns into admin&2Fposts and fails. Also note I could not get the protocol to_params to work on the post thus I’m passing it explicitly the slug via. post.slug so there’s that too.

Any input would be great, thanks.

I believe you can do:

foo = ~w[foo bar]
dbg ~p"/#{foo}"
2 Likes

Thank you :pray: that did it.

1 Like

Thoughts about compile time warnings? My CI is not gonna like it.
And maybe this is just not the best pattern to follow.

What does the warning say exactly?

I’m also interested in what trouble you’re having with deriving params to use slug. How are you doing it?

I current have a project where pretty much everything has a slug and doing somewhat similar to you except that I push_redirect after an update. You can always check if the slug was changed to make the call of whether to patch or redirect.

warning: no route path for MorphicProWeb.Router matches "/#{socket.assigns.patch}/#{post.slug}"
  lib/morphic_pro_web/live/admin/post_live/form_component.ex:74

So basically after I made the assignment to patch.

 |> push_patch(to:  ~p"/#{socket.assigns.patch}/#{post.slug}")}

I’m not doing much other than just setting the @derive {Phoenix.Param, key: :slug}

The thing is it works.

Actually I just checked and I push_patch and it works fine even with a changing slug.

What is the value of socket.assigns.patch? It’s saying your route doesn’t exist which is ~p doing its job! Are you sure socket.assigns.patch isn’t nil or something?

       path = ~p"/#{socket.assigns.patch}/#{post.slug}"
        IO.inspect([socket.assigns.patch, post.slug, path], label: :WTF)

Renders at run time correctly but at compile time I see the warning.

If you wanted to try this I’m mostly working on a default generated form. So at assignment in the show or index via the

  <.live_component
    module={MorphicProWeb.Admin.PostLive.FormComponent}
    id={@post.id}
    title={@page_title}
    action={@live_action}
    post={@post}
    patch={~w[admin posts]}
  />

and as for the push_patch, that’s a part of default generators too.

My assumption here is that the ~w is still noting going to tell ~p what it wants at compile time.

My only guess would be that that it’s another live component that is throwing that error—that’s usually what it it is for me when I get super stuck thinking a variable is definitely populated and it’s not :sweat_smile:

I got it, I should pass a lambda instead of assigning a rendered value to the patch assigns. Then call into that lambda at run time. I bet it has to do with the way the call to ~p is composed. This way I can pass the ~p"/admin/posts" correctly at the time I define ~p. Also remember the warning is at compile time. So there is no other LV competing with it at that time. I think its just the super dynamic way that ~w get assigned to the socket.assigns that makes it hard for it to match in the router.

patch={fn %{slug: slug} -> ~p"/admin/posts/#{slug}" end}

and

socket.assigns.patch.(post)

respectfully did address the warnings and provided the logic I was looking for.

Thanks for the help @sodapopcan

1 Like

Correct. The point of verified routes is to verify the routes at compile time, which it cannot in your case. In your case, you can build the route yourself, as a string without the sigil, since verified routes is not giving you anything, or be less dynamic and pass the route ~p as parameter :slight_smile:

3 Likes