How to enable verified routes? undefined function sigil_p/2

Error when attempting to use verified routes with ~p

Hey there, I’m new to the Elixir/Phoenix eco system, and I’ve been trying to model an application from some code from an online course, but I must have somethings setup wrong, because I get errors when trying to use certain features.

Specifically, I want to use verified routes via ~p and use the <.link> syntax, but I get the following errors:

error: undefined function sigil_p/2 (expected DroneShopWeb.DroneLive to define such a function or for it to be imported, but none are available)

lib/drone_shop_web/live/drone_live.ex:48: DroneShopWeb.DroneLive.render/1

error: undefined function link/1 (expected DroneShopWeb.DroneLive to define such a function or for it to be imported, but none are available)

lib/drone_shop_web/live/drone_live.ex:27: DroneShopWeb.DroneLive.render/1

Here is my code:

defmodule DroneShopWeb.DroneLive do
  use DroneShopWeb, :live_view


  alias DroneShop.Drones

  def mount(_params, _session, socket) do
    socket =
      assign(socket,
        filter: %{type: "", prices: []},
        drones: Drones.list_drones()
      )

    {:ok, socket}
  end

  def handle_params(%{"id" => id}, _uri, socket) do
    drone = Drones.get_drone!(id)
    {:noreply, assign(socket, selected_drone: drone, page_title: drone.name)}
  end

  def handle_params(_params, _uri, socket) do
    {:noreply, assign(socket, selected_drone: hd(socket.assigns.drones))}
  end


  def render(assigns) do
    ~H"""
      <h1 class="text-6xl text-center mb-10">Inventory</h1>
      <.filter_form filter={@filter} />
      <div class="flex flex-row flex-wrap justify-center">
        <%= for drone <- @drones do %>
          <div class="max-w-sm rounded overflow-hidden shadow-lg mx-4 mt-3">
            <img class="w-full" src={drone.image}>
            <.link phx-click="more_info"  # patch={~p"/drones?#{[id: drone.id]}"} class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3">More Info</.link>
            <div class="px-6 py-4">
              <p class="font-bold text-xl mb-2"><%= drone.model %>
              </p>
              <p class="text-gray-700 text-base">
                <%= drone.description %>
              </p>
              </div>
          </div>
          <% end %>
      </div>
    """
  end

  def handle_event("more_info", %{"id" => id}, socket) do
    IO.inspect(self(), label: "More Info Click")
    drone = Drones.get_drone!(id)
    {:noreply, assign(socket, selected_drone: drone, page_title: drone.name)}
  end

  def filter_form(assigns) do
    ~H"""
    <form phx-change="filter" class="text-center mb-10">
      <div class="">
        <select name="type" class="rounded-lg">
          <%= Phoenix.HTML.Form.options_for_select(
            type_options(),
            @filter.type
          ) %>
        </select>
        <div class="prices">
          <%= for price <- ["$", "$$", "$$$"] do %>
            <input
            class="rounded"
              type="checkbox"
              name="prices[]"
              value={price}
              id={price}
              checked={price in @filter.prices}
            />
            <label for={price}><%= price %></label>
          <% end %>
          <input type="hidden" name="prices[]" value="" />
        </div>
      </div>
    </form>
    """
  end

  def handle_event("filter", %{"type" => type, "prices" => prices}, socket) do
    filter = %{type: type, prices: prices}
    drones = Drones.list_drones(filter)
    {:noreply, assign(socket, drones: drones, filter: filter)}
  end

  def handle_event("filter", %{"id" => id}, socket) do
    filter = %{id: id}
    drones = Drones.list_drones(filter)
    {:noreply, assign(socket, drones: drones, filter: filter)}
  end

  defp type_options do
    [
      "All Types": "",
      Cinematic: "Cinematic",
      FPV: "FPV"
    ]
  end
end

What version of Phoenix and LiveView are you using? They weren’t available until Phoenix 1.17.

1 Like

Please show this module.

In general sigils are just a functions with a bit of syntactic sugar (~ notation). Functions and macros are not “magically enabled”. They need to be defined or imported within a module. The only “magic” is that functions and macros defined in Kernel and Kernel.SpecialForms are automatically imported to every module. Since verified routes are not even part of Elixir core we need to define or import them.

# definition
defmodule MyLib do
  defmacro __using__(opts \\ []) do
    quote do
      def sigil_p(string, opts) do
        # …
      end
    end
  end
end

defmodule MyApp do
  # use is short for:
  # require MyLib
  # MyLib.__using__(…)
  use MyLib
end

# import
defmodule MyLib do
  def sigil_p(string, opts) do
    # …
  end 
end

defmodule MyApp do
  import MyLib
  # or use some macro with such import call
  # require MyLib
  # MyLib.macro_with_import_stuff(…)
  # or same macro, but with name __using__ and use call as above
end

When generating phoenix app with:

$ mix archive.install hex phx_new --force
$ mix phx.new drone_shop

it should create a file called drone_shop/lib/drone_shop_web.ex with such code:

defmodule DroneShopWeb do
  # …

  # this calls html_helpers private function …
  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {DroneShopWeb.Layouts, :app}

      unquote(html_helpers())
    end
  end

  # …

  # this function have some shared code for many parts of your application
  # at the bottom you should another call
  # this time to verified_routes private function
  defp html_helpers do
    quote do
      # …

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end

  # we are finally here!
  # we are using some module
  # this is the second case I mentioned i.e. import within a macro
  def verified_routes do
    quote do
      use Phoenix.VerifiedRoutes,
        endpoint: DroneShopWeb.Endpoint,
        router: DroneShopWeb.Router,
        statics: DroneShopWeb.static_paths()
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  # This is called first! It then call a live_view function …
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

You can find an import call in phoenix source:

And the definition of said sigil is here:

use DroneShopWeb, :verified_routes

Will do it for you

1 Like

In newly generated projects this code is wrong, because a live_view function should call it. This code would be good only if author would remove said call, but then there would be no question about it …

Verified routes are not enabled by default. Either this use or

use Phoenix.VerifiedRoutes,
  endpoint: MyAppWeb.Endpoint,
  router: MyAppWeb.Router

Is needed for them to work.

Sources:

1 Like

Not sure what do you mean … They are enabled by default. See my previous post explaining macros expansion. In newly generated phoenix app you can use routes without any extra change …

I am using the latest versions of both

Here is drone_shop/lib/drone_shop_web.ex

defmodule DroneShopWeb do
  @moduledoc """
  The entrypoint for defining your web interface, such
  as controllers, views, channels and so on.

  This can be used in your application as:

      use DroneShopWeb, :controller
      use DroneShopWeb, :view

  The definitions below will be executed for every view,
  controller, etc, so keep them short and clean, focused
  on imports, uses and aliases.

  Do NOT define functions inside the quoted expressions
  below. Instead, define any helper function in modules
  and import those modules here.
  """

  def controller do
    quote do
      use Phoenix.Controller, namespace: DroneShopWeb

      import Plug.Conn
      import DroneShopWeb.Gettext
      alias DroneShopWeb.Router.Helpers, as: Routes
    end
  end

  def view do
    quote do
      use Phoenix.View,
        root: "lib/drone_shop_web/templates",
        namespace: DroneShopWeb

      # Import convenience functions from controllers
      import Phoenix.Controller,
        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]

      # Include shared imports and aliases for views
      unquote(view_helpers())
    end
  end

  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {DroneShopWeb.LayoutView, "live.html"}

      unquote(view_helpers())
    end
  end

  def live_component do
    quote do
      use Phoenix.LiveComponent

      unquote(view_helpers())
    end
  end

  def component do
    quote do
      use Phoenix.Component

      unquote(view_helpers())
    end
  end

  def router do
    quote do
      use Phoenix.Router

      import Plug.Conn
      import Phoenix.Controller
      import Phoenix.LiveView.Router
    end
  end

  def channel do
    quote do
      use Phoenix.Channel
      import DroneShopWeb.Gettext
    end
  end

  defp view_helpers do
    quote do
      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
      import Phoenix.LiveView.Helpers

      # Import basic rendering functionality (render, render_layout, etc)
      import Phoenix.View

      import DroneShopWeb.ErrorHelpers
      import DroneShopWeb.Gettext
      alias DroneShopWeb.Router.Helpers, as: Routes
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

Do I need to specifically define the verified routes somewhere?

That doesn’t look like it was generated with the latest version. I’m assuming you did an update?

As per @SergeyMoiseev pointed out you need to add:

def verified_routes do
  quote do
    use Phoenix.VerifiedRoutes,
      endpoint: DroneShopWeb.Endpoint,
      router: DroneShopWeb.Router,
      statics: DroneShopWeb.static_paths()
  end
end

Then in the view_helpers function add:

unquote(verified_routes())
1 Like

Great, now compare it with what I show. Looks like you have generated the project in earlier version of phoenix and migrated it to newest one. You have not done an optional step, see (optional) Update your app to support Phoenix.VerifiedRoutes

2 Likes

Oh yes, you are right. I double-checked and it says version 1.6.15. Which seems strange considering that I thought I generated it with mix phx.new Anyway, I’ve added those things and still get some errors. I’ll post what it looks like below

Ok, so I followed those steps and now get a new set of errors, not sure what to do from here:

Compiling 8 files (.ex)
error: module Phoenix.VerifiedRoutes is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
  lib/drone_shop_web/live/drone_live.ex:2: DroneShopWeb.DroneLive (module)

error: module Phoenix.VerifiedRoutes is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
  lib/drone_shop_web/views/error_view.ex:2: DroneShopWeb.ErrorView (module)

error: module Phoenix.VerifiedRoutes is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
  lib/drone_shop_web/views/layout_view.ex:2: DroneShopWeb.LayoutView (module)

error: module Phoenix.VerifiedRoutes is not loaded and could not be found. This may be happening because the module you are trying to load directly or indirectly depends on the current module
  lib/drone_shop_web/views/page_view.ex:2: DroneShopWeb.PageView (module)


== Compilation error in file lib/drone_shop_web/live/drone_live.ex ==
** (CompileError) lib/drone_shop_web/live/drone_live.ex: cannot compile module DroneShopWeb.DroneLive (errors have been logged)
    (elixir 1.15.5) expanding macro: Kernel.use/2
    lib/drone_shop_web/live/drone_live.ex:2: DroneShopWeb.DroneLive (module)
    expanding macro: DroneShopWeb.__using__/1
    lib/drone_shop_web/live/drone_live.ex:2: DroneShopWeb.DroneLive (module)
    (elixir 1.15.5) expanding macro: Kernel.use/2

```

This is what the file looks like now:

  @moduledoc """
  The entrypoint for defining your web interface, such
  as controllers, views, channels and so on.

  This can be used in your application as:

      use DroneShopWeb, :controller
      use DroneShopWeb, :view

  The definitions below will be executed for every view,
  controller, etc, so keep them short and clean, focused
  on imports, uses and aliases.

  Do NOT define functions inside the quoted expressions
  below. Instead, define any helper function in modules
  and import those modules here.
  """

  def controller do
    quote do
      use Phoenix.Controller, namespace: DroneShopWeb

      import Plug.Conn
      import DroneShopWeb.Gettext
      alias DroneShopWeb.Router.Helpers, as: Routes
    end
  end

  def view do
    quote do
      use Phoenix.View,
        root: "lib/drone_shop_web/templates",
        namespace: DroneShopWeb

      # Import convenience functions from controllers
      import Phoenix.Controller,
        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]

      # Include shared imports and aliases for views
      unquote(view_helpers())
    end
  end

  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {DroneShopWeb.LayoutView, "live.html"}

      unquote(view_helpers())
      unquote(html_helpers())
    end
  end

  def live_component do
    quote do
      use Phoenix.LiveComponent

      unquote(view_helpers())
    end
  end

  def component do
    quote do
      use Phoenix.Component

      unquote(view_helpers())
    end
  end

  def router do
    quote do
      use Phoenix.Router

      import Plug.Conn
      import Phoenix.Controller
      import Phoenix.LiveView.Router
    end
  end

  def channel do
    quote do
      use Phoenix.Channel
      import DroneShopWeb.Gettext
    end
  end

  defp view_helpers do
    quote do
      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
      import Phoenix.LiveView.Helpers

      # Import basic rendering functionality (render, render_layout, etc)
      import Phoenix.View

      import DroneShopWeb.ErrorHelpers
      import DroneShopWeb.Gettext
      alias DroneShopWeb.Router.Helpers, as: Routes
      unquote(verified_routes())
    end
  end

  defp html_helpers do
    quote do
      # …

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end

  def verified_routes do
    quote do
      use Phoenix.VerifiedRoutes,
        endpoint: DroneShopWeb.Endpoint,
        router: DroneShopWeb.Router,
        statics: DroneShopWeb.static_paths()
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

That’s because you ran mix phx.new with version 16 and verified routes did not exist in this version.

If you are in a position where you can start over, you can run mix archive.install hex phx_new which will update your phx_new to use the latest version of Phoenix. Otherwise, just manually update the Phoenix version in your mix.exs and do mix deps.get. You may need to undo the changes you just did first as to avoid a compilation error. You will also probably have to update some other packages as well.

1 Like

Module Phoenix.VerifiedRoutes is not available in previous phoenix versions. Therefore I’m now not sure if you really have latest phoenix version …

Here is how it looks like for newly generated project:

$ mix phx.new example
$ mix deps.tree
example
├── ecto_sql ~> 3.10 (Hex package)
├── esbuild ~> 0.7 (Hex package)
├── finch ~> 0.13 (Hex package)
├── floki >= 0.30.0 (Hex package)
├── gettext ~> 0.20 (Hex package)
├── jason ~> 1.2 (Hex package)
├── phoenix ~> 1.7.7 (Hex package)
├── phoenix_ecto ~> 4.4 (Hex package)
├── phoenix_html ~> 3.3 (Hex package)
├── phoenix_live_dashboard ~> 0.8.0 (Hex package)
├── phoenix_live_reload ~> 1.2 (Hex package)
├── phoenix_live_view ~> 0.19.0 (Hex package)
├── plug_cowboy ~> 2.5 (Hex package)
├── postgrex >= 0.0.0 (Hex package)
├── swoosh ~> 1.3 (Hex package)
├── tailwind ~> 0.2.0 (Hex package)
├── telemetry_metrics ~> 0.6 (Hex package)
└── telemetry_poller ~> 1.0 (Hex package)

Could you please compare this with the dependencies you have?

Alright, the need to update phx_new is definitely helpful information! I think I will do that and make a new project and copy over what I can since I haven’t gone that far into it. I’ll report back on what happens after updating all my dependencies.

Yes, I was mistaken on the version of Phoenix I was using. I’m going to do generate a new project after updating everything and see if that fixes my issue. I’ll let you know what happens!

1 Like

Ya, it’s a bit of a hairy command and something that eluded me for a while. It’s one of those things that you run very early on while learning and then completely forget about. It’s also not super intuitive that archive.install will also update an archive. It’s also not too intuitive as to what an “archive” is :sweat_smile: