Phoenix 1.7.1 live view, warning `... .get_all/1 is undefined (module ... is not available or is yet to be defined`

Hi. I’ve been struggling with this issue through attempts at three different search tutorials/examples I’ve tried to replicate. This suggests to me that there’s something small but vital that I’m missing and/or doing wrong (I’ve included a list of the resources I’ve been referring to, below. I’ve also been crawling these forums and stackexchange.)

This is the first live module I’ve tried to add, but the rest of the site is working great. I’m very happy to have left Ruby in my rearview, and am enjoying Phoenix and Elixir a lot.

This is a really long post, but I suspect the answer is probably is a short one, maybe even just single line of code, or a few. It seems like I’m really close. The post is long because I am very new to Phoenix and Elixir, so I may be providing more detail than is needed to solve this (seems better than not providing enough info.)

I’m not an actual developer or engineer, but the solution will hopefully be easy for real devs to spot…

The Error (…er, warning. I can compile, just can’t access my new live module)

Might as well start with the errors seen in the phx server console. This shows what I see when I mix phx.server, followed by what I see when I load the page (/plays) where I’m trying to render the search_bar (the controller for the page includes an ecto query, which is what you see there before the SearchBar errors):

...
==> mono_phoenix_v01
Compiling 32 files (.ex)
warning: MonoPhoenixV01Web.SearchBar.get_all/1 is undefined (module MonoPhoenixV01Web.SearchBar is not available or is yet to be defined)
  lib/mono_phoenix_v01/search_bar_live.ex:21: MonoPhoenixV01Web.SearchBarLive.Index.load_search_bar/2

Generated mono_phoenix_v01 app
[notice]     :alarm_handler: {:set, {{:disk_almost_full, '/usr/lib/wsl/drivers'}, []}}
[notice]     :alarm_handler: {:set, {{:disk_almost_full, '/mnt/c'}, []}}
[info] Running MonoPhoenixV01Web.Endpoint with cowboy 2.9.0 at 0.0.0.0:4000 (http)
[info] Access MonoPhoenixV01Web.Endpoint at http://localhost:4000
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.29.tgz
[watch] build finished, watching for changes...
[info] GET /plays/
[debug] Processing with MonoPhoenixV01Web.PlaysPageController.plays/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="plays" db=0.4ms queue=0.4ms idle=1763.4ms
SELECT p0."title" FROM "plays" AS p0 GROUP BY p0."title" []
[info] Sent 500 in 40ms
[error] #PID<0.7371.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.7369.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /plays/
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function MonoPhoenixV01Web.SearchBarLive.__live__/0 is undefined (module MonoPhoenixV01Web.SearchBarLive is not available)
        MonoPhoenixV01Web.SearchBarLive.__live__()
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:257: Phoenix.LiveView.Static.load_live!/2
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:91: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.18.17) lib/phoenix_component.ex:883: Phoenix.Component.live_render/3
        (mono_phoenix_v01 0.1.0) lib/mono_phoenix_v01_web/templates/plays_page/plays.html.heex:13: anonymous fn/2 in MonoPhoenixV01Web.PlaysPageView."plays.html"/1
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/engine.ex:137: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/1
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/engine.ex:153: Phoenix.HTML.Safe.Phoenix.LiveView.Rendered.to_iodata/3
        (phoenix 1.7.1) lib/phoenix/controller.ex:1005: anonymous fn/5 in Phoenix.Controller.template_render_to_iodata/4
        (telemetry 1.2.1) /home/steven/webdev/elixir/mono_phoenix_v01/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix 1.7.1) lib/phoenix/controller.ex:971: Phoenix.Controller.render_and_send/4
        (mono_phoenix_v01 0.1.0) lib/mono_phoenix_v01_web/controllers/plays_page_controller.ex:1: MonoPhoenixV01Web.PlaysPageController.action/2
        (mono_phoenix_v01 0.1.0) lib/mono_phoenix_v01_web/controllers/plays_page_controller.ex:1: MonoPhoenixV01Web.PlaysPageController.phoenix_controller_pipeline/2
        (phoenix 1.7.1) lib/phoenix/router.ex:425: Phoenix.Router.__call__/5
        (mono_phoenix_v01 0.1.0) lib/mono_phoenix_v01_web/endpoint.ex:1: MonoPhoenixV01Web.Endpoint.plug_builder_call/2
        (mono_phoenix_v01 0.1.0) lib/plug/debugger.ex:136: MonoPhoenixV01Web.Endpoint."call (overridable 3)"/2
        (mono_phoenix_v01 0.1.0) lib/mono_phoenix_v01_web/endpoint.ex:1: MonoPhoenixV01Web.Endpoint.call/2
        (phoenix 1.7.1) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
        (plug_cowboy 2.6.0) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
        (cowboy 2.9.0) /home/steven/webdev/elixir/mono_phoenix_v01/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) /home/steven/webdev/elixir/mono_phoenix_v01/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3

And when I try to visit http://localhost:4000/search_bar directly:

[info] GET /search_bar
[debug] Processing with MonoPhoenixV01Web.SearchBarLive.index/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 500 in 21ms
[error] #PID<0.7413.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.7411.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /search_bar
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function MonoPhoenixV01Web.SearchBarLive.__live__/0 is undefined (module MonoPhoenixV01Web.SearchBarLive is not available)
        MonoPhoenixV01Web.SearchBarLive.__live__()
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:257: Phoenix.LiveView.Static.load_live!/2
...

Theories I’ve cooked up, investigated, and am still unsure about:

  • Something I’m missing in my live view setup?
    (a plug or socket I need to edit or add somewhere? something I need to add to myapp_web.ex? something else I need to add to the live search module?)

  • Something I’ve missed in my upgrade from 1.6.15 to 1.7.1? (I do have support for verified routes installed and working, so feel free to include the ~p sigil in any suggested solutions.)

  • Something I’m misunderstanding about the naming conventions? (I’ve tried a lot of variants, trying to guess at ways at which I might be misunderstanding, may have thoroughly confused myself, lol)

  • Conflicting versions of deps?

  • Something that isn’t even on my radar yet? (my n00bism. pebkac)

  • 2 or more of the above?

Resources I’ve been working from:

In case it is relevant, the file tree for most of my app comes from this doc: Request life-cycle — Phoenix v1.6.15

I’ve since add a lib/myapp_web/live folder (details in another section, below)

Popping the hood (versions, deps, contents of related files):

  • My local, and my staging site on heroku, are now on Phoenix 1.7.1

  • My production site ( https://www.shakespeare-monologues.org ) is still on 1.6.15. (It’ll get 1.7.1 when I’ve successfully added search.)

  • Erlang/OTP 24

  • Elixir 1.14.1 (compiled with Erlang/OTP 24)

See the contents of mix.ex, below, for the deps and versions.

The live dashboard installed by the 1.6.15 generator is still working correctly under v1.7.1 on my local, and all my other stuff is working, but this is the first live module I’ve tried to add.

Files

(please let me know if there are other relevant files I should post)

The first files below 3 were created by me while following the post at Handling search form nicely with Phoenix LiveView - Michal (arathunku) ,

  • lib/mono_phoenix_v01/search_bar_live.ex:
defmodule MonoPhoenixV01Web.SearchBarLive.Index do
  use MonoPhoenixV01Web, :live_view
  alias MonoPhoenixV01Web.SearchBar

  ## socket assigns
  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  @impl true
  def handle_params(params, _url, socket) do
    query = params |> Map.get("query")

    {:noreply, socket |> load_search_bar(query)}
  end

  def load_search_bar(socket, query) do
    socket
    |> assign(:query, query)
    |> assign(:search_bar, SearchBar.get_all(query))
  end

  ## render assigns

  @impl true
  def render(assigns) do
    ~L"""
      <h3>Search results</h3>
      <%= render_search_form(assigns) %> <%# added %>
      <%= render_search_bar(assigns) %>
    """
  end

  ## render the search form
  def render_search_form(assigns) do
    ~L"""
    <%= form_for :search, "#", [phx_submit: "search", phx_change: "search", id: "searchbar"], fn f -> %>
      <%= label f, :search %>
      <%= text_input f, :query, value: @query %>
      <%= submit "Search" %>
    <% end %>
    """
  end

  @impl true
  def handle_event("search", %{"search" => %{"query" => query}}, socket) do
    {:noreply, push_patch(socket, to: Routes.search_bar_path(socket, :index, query: query))}
  end

  ## render the search results
  def render_search_bar(assigns) do
    ~L"""
    <div class="center-this">
      <table class="monologue-list">
        <tbody>
          <%= for %{row: row} <- @search_bar do %>
            <tr class="monologue_list">
              <td class="{ (index.even? ? 'even' : 'odd') }">
                <span class="monologue-playname"><%= row.play %></span>&nbsp; · <span class="monologue-actscene"><%= link to: raw(row.scene), method: :get, target: "_blank" do %><%= row.location %><% end %></span>&nbsp; ·
                <span class="monologue-actscene"><%= row.style %></span>
                <br />
                <span class="monologue-character"><%= row.character %></span>
                <br />
                <div
                  class="monologue-firstline-table"
                  data-toggle="collapse"
                  data-target={"#collapse-" <> Integer.to_string(row.monologues)}
                >
                  <%= row.firstline %>
                </div>
                <div
                  class="collapse multi-collapse monologue-show"
                  id={"collapse-" <> to_string(row.monologues)}
                >
                  <br />
                  <%= raw(row.body) %>&nbsp;
                  <%= link to: raw(row.pdf), method: :get, target: "_blank", rel: "noopener" do %>
                    <img
                      src={Routes.static_path(@conn, "/images/pdf_file_icon_16x16.png")}
                      alt="Click for a double-spaced PDF of this monologue"
                      title="Click for a double-spaced PDF of this monologue"
                    />
                  <% end %>
                </div>
              </td>
            </tr>
          <% end %>
        </tbody>
      </table>
    </div>
    """
  end
end
  • lib/mono_phoenix_v01_web/live/search_bar_live.html.heex:
<.live_title prefix="Monologues from ">
  <%= assigns[:page_title] || "#{hd(@rows).play} · Shakespeare's Monologues" %>
</.live_title>
<div>
  </div>
  <div class="accent-font">
    <h3>Monologues matching your search:</h3>
    <span font-size: 10px;>
      Click on the 1st line, under the character's name, to see the full monologue. &nbsp;<a
        href="#"
        data-toggle="collapse"
        data-target=".multi-collapse"
        id="toggle-button"
      >
        <img
          src="/images/ExpandAll.png"
          id="toggle-image"
          alt="Click to toggle text of all monologues on the page.
  Reload the page to reset the toggle"
          title="Click to toggle the text of all monologues on the page.
  Reload the page to reset the toggle."
        />
      </a>
    </span>
  </div>
  <div>
    <div class="center-this">
      <table class="monologue-list">
        <tbody>
          <%= for row <- @rows do %>
            <tr class="monologue_list">
              <td class="{ (index.even? ? 'even' : 'odd') }">
                <span class="monologue-playname"><%= row.play %></span>&nbsp; · <span class="monologue-actscene"><%= link to: raw(row.scene), method: :get, target: "_blank" do %><%= row.location %><% end %></span>&nbsp; ·
                <span class="monologue-actscene"><%= row.style %></span>
                <br />
                <span class="monologue-character"><%= row.character %></span>
                <br />
                <div
                  class="monologue-firstline-table"
                  data-toggle="collapse"
                  data-target={"#collapse-" <> Integer.to_string(row.monologues)}
                >
                  <%= row.firstline %>
                </div>
                <div
                  class="collapse multi-collapse monologue-show"
                  id={"collapse-" <> to_string(row.monologues)}
                >
                  <br />
                  <%= raw(row.body) %>&nbsp;
                  <%= link to: raw(row.pdf), method: :get, target: "_blank", rel: "noopener" do %>
                    <img
                      src={Routes.static_path(@conn, "/images/pdf_file_icon_16x16.png")}
                      alt="Click for a double-spaced PDF of this monologue"
                      title="Click for a double-spaced PDF of this monologue"
                    />
                  <% end %>
                </div>
              </td>
            </tr>
          <% end %>
        </tbody>
      </table>
    </div>
  </div>
</div>
<script>
  const toggleButton = document.getElementById('toggle-button');
  const toggleImage = document.getElementById('toggle-image');

  toggleButton.addEventListener('click', () => {
    toggleImage.classList.toggle('collapsed');
  });
</script>

<style>
  #toggle-image.collapsed {
    content: url('/images/CollapseAll.png');
  }

  #toggle-image {
    content: url('/images/ExpandAll.png');
  }
</style>

  • I’m trying to render it in lib/mono_phoenix_v01_web/templates/plays_page/plays.html.heex, thusly:
  <div class="input-group accent-font">
    <%= live_render(
      @conn,
      MonoPhoenixV01Web.SearchBarLive,
      id: "searchbar"
    ) %>
  </div>

  • mix.ex (am I missing something needed for live modules to work?)
defmodule MonoPhoenixV01.MixProject do
  use Mix.Project

  def project do
    [
      app: :mono_phoenix_v01,
      version: "0.1.0",
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      # compilers: [] ++ Mix.compilers(), no longer needed in Elixir 1.14+
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {MonoPhoenixV01.Application, []},
      extra_applications: [:logger, :runtime_tools, :os_mon]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.7.1", override: true},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.6"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.0"},
      {:phoenix_view, "~> 2.0"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.18.15"},
      {:phoenix_live_dashboard, "~> 0.7.2"},
      {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
      {:swoosh, "~> 1.3"},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.22.1"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"},
      {:redirect, "~> 0.4.0"},
      {:html_assertion, "0.1.5", only: :test},
      {:floki, ">= 0.34.2", only: :test},
      {:credo, "~> 1.6", only: [:dev, :test], runtime: false}
    ]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to install project dependencies and perform other setup tasks, run:
  #
  #     $ mix setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    [
      setup: ["deps.get", "ecto.setup"],
      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
      "ecto.reset": ["ecto.drop", "ecto.setup"],
      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
      "assets.deploy": ["esbuild default --minify", "phx.digest"]
    ]
  end
end

  • router.ex: (wondering if I’m missing anything here, too, wondering the same about the endpoint file below it)
defmodule MonoPhoenixV01Web.Router do
  use MonoPhoenixV01Web, :router

  import Redirect

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)
    plug(:fetch_live_flash)
    plug(:put_root_layout, {MonoPhoenixV01Web.LayoutView, :root})
    plug(:protect_from_forgery)
    plug(:put_secure_browser_headers)
  end

  pipeline :api do
    plug(:accepts, ["json"])
  end

  scope "/", MonoPhoenixV01Web do
    pipe_through(:browser)

    get("/", StaticPageController, :home)
    get("/plays", PlaysPageController, :plays)
    get("/play/:playid", PlayPageController, :play)
    get("/mens", MensPageController, :mens)
    get("/men/:playid", MenplayPageController, :menplay)
    get("/womens", WomensPageController, :womens)
    get("/women/:playid", WomenplayPageController, :womenplay)
    get("/monologues/:monoid", MonologuesPageController, :monologues)
    get("/aboutus", StaticPageController, :aboutus)
    get("/faq", StaticPageController, :faq)
    get("/home", StaticPageController, :home)
    get("/links", StaticPageController, :links)
    get("/privacy", StaticPageController, :privacy)
    get("/maintenance", StaticPageController, :maintenance)
    get("/hello", PageController, :hello)
    get("/sandbox", PageController, :sandbox)
    live("/search_bar", SearchBarLive, :index)
  end

  ## redirects for deep links from other sites. Will not work inside a scope.
  # redirect from /men/plays/123 to /men/123
  # redirect from /men/plays/123 to /men/123
  redirect("/men", "/mens", :permanent, preserve_query_string: true)
  redirect("/women", "/womens", :permanent, preserve_query_string: true)
  redirect("/men/plays/13", "/men/13", :permanent, preserve_query_string: true)
  # omitted from this post: a long list of redirects like the above, for old legacy links on misc external referring sites
  # Other scopes may use custom stacks.
  # scope "/api", MonoPhoenixV01Web do
  #   pipe_through :api
  # end

  # Enables LiveDashboard only for development
  #
  # If you want to use the LiveDashboard in production, you should put
  # it behind authentication and allow only admins to access it.
  # If your application does not have an admins-only section yet,
  # you can use Plug.BasicAuth to set up some basic authentication
  # as long as you are also using SSL (which you should anyway).
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through(:browser)

      live_dashboard("/dashboard", metrics: MonoPhoenixV01Web.Telemetry)
    end
  end

  # Enables the Swoosh mailbox preview in development.
  #
  # Note that preview only shows emails that were sent by the same
  # node running the Phoenix server.
  if Mix.env() == :dev do
    scope "/dev" do
      pipe_through(:browser)

      forward("/mailbox", Plug.Swoosh.MailboxPreview)
    end
  end
end
  • lib/mono_phoenix_v01_web/endpoint.ex:
defmodule MonoPhoenixV01Web.Endpoint do
  use Phoenix.Endpoint, otp_app: :mono_phoenix_v01

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  @session_options [
    store: :cookie,
    key: "_mono_phoenix_v01_key",
    signing_salt: "s8druUxv"
  ]

  socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]])

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phx.digest
  # when deploying your static files in production.
  plug(Plug.Static,
    at: "/",
    from: :mono_phoenix_v01,
    gzip: false,
    only: MonoPhoenixV01Web.static_paths()
  )

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
    plug(Phoenix.LiveReloader)
    plug(Phoenix.CodeReloader)
    plug(Phoenix.Ecto.CheckRepoStatus, otp_app: :mono_phoenix_v01)
  end

  plug(Phoenix.LiveDashboard.RequestLogger,
    param_key: "request_logger",
    cookie_key: "request_logger"
  )

  plug(Plug.RequestId)
  plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])

  plug(Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library()
  )

  plug(Plug.MethodOverride)
  plug(Plug.Head)
  plug(Plug.Session, @session_options)
  plug(MonoPhoenixV01Web.Router)
end

Thanks in advance for any help. I’ve been stuck on this for a couple of weeks, and am really looking forward to getting unblocked.

1 Like

You are defining your live view with the module name above, but your router is looking for MonoPhoenixV01Web.SearchBarLive.

I suggest you remove the .Index bit from your module name and see what happens…

2 Likes

The first warning warning: MonoPhoenixV01Web.SearchBar.get_all/1 is undefined (module MonoPhoenixV01Web.SearchBar is not available or is yet to be defined) says that module is not available, have you created such a module? I suspect it would crash there next when the problem mindok pointed out is fixed.

2 Likes

Thanks much for the reply and the suggestion. I just have .Index there because it was in the tutorial I was following. Removing it doesn’t seem to make a difference, just changes the wording of the error message slightly:

[info] GET /plays/
[debug] Processing with MonoPhoenixV01Web.PlaysPageController.plays/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="plays" db=0.5ms queue=0.6ms idle=1321.3ms
SELECT p0."title" FROM "plays" AS p0 GROUP BY p0."title" []
[info] Sent 500 in 43ms
[error] #PID<0.642.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.625.0>, stream id 5) terminated
Server: localhost:4000 (http)
Request: GET /plays/
** (exit) an exception was raised:
    ** (ArgumentError) cannot invoke handle_params/3 on MonoPhoenixV01Web.SearchBarLive because it is not mounted nor accessed through the router live/3 macro
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/route.ex:26: Phoenix.LiveView.Route.live_link_info!/3
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:279: Phoenix.LiveView.Static.call_mount_and_handle_params!/5

1 Like

Hey, thanks for the reply. I’d thought the module was the first file I posted under the heading “Files” in my initial post. Am I even more confused than I thought I was?

The module in that file is named MonoPhoenixV01Web.SearchBarLive.Index. This is not the same as MonoPhoenixV01Web.SearchBar.

2 Likes

That specific error message comes up in two issues on the phoenix_live_view repo, both related to combining live_render and handle_params:

2 Likes

Thanks for the reply, and thanks for pointing that out, @Nicd .

As I mentioned, I’m very new to Phoenix and Elixir, and I’m not an actual developer, so my brain lacks the formatting to be able to parse the intent of, or anything beyond the most basic meaning of your reply.

That is to say, I’m aware they’re not the same, but I thought that was purposeful. I initially matched the naming patterns in the post/tutorial I’m using: Handling search form nicely with Phoenix LiveView - Michal (arathunku) (I added Web to that alias just to see if omitting it was an error in the tutorial, and forgot to remove it before copying it to the post. I’m currently getting the same behavior either way though.)

I assumed (incorrectly) that the tutorial was creating an alias without using as (like I’d read about here: Alias, import, require and use in Elixir - guide & examples | Curiosum, and elsewhere.) But rereading that now, I see that would only work if the module name didn’t have “.Index” on the end of it. (Welcome to the mind of a clueless n00b. :blush:)

Can you point me to an example of what I need to be doing to remedy what you pointed out?

The top of that post I’m working from mentions:

“It’s a pretty standard app using Elixir, Phoenix with LiveView and PostgreSQL. Deployed on Heroku, CI on GitHub. Nothing fancy, boring tools.”

That is an exact match for my site and needs, and is why I am trying to get that particular solution working (after failing with 2 others.)

I have been wondering how the setup he describes in his tutorial connects to ecto and postgresql to get the search results, so perhaps you’re hinting at what I’m missing?

The best n00bish guess I’ve come up with, so far, is that perhaps I need to replicate one of the controllers I’m using to query my DB, and define it as MonoPhoenixV01Web.SearchBar? Or perhaps MonoPhoenixV01.SearchBar

If I’m anywhere near the ballpark with that guess, where should I put the file, and how must it be named? My guesses:

Put it with my existing (not live) controllers? e.g.
lib/mono_phoenix_v01_web/controllers/search_bar_controller.ex with the top of the file looking like this?:

defmodule MonoPhoenixV01Web.SearchBarController do
  use MonoPhoenixV01Web, :controller
  import Ecto.Query, only: [from: 2]
...

Or would it need to be in lib/mono_phoenix_v01 instead, like lib/mono_phoenix_v01/search_bar.ex with the top of the file looking more like this?:

defmodule MonoPhoenixV01Web.SearchBar do
  use MonoPhoenixV01Web, :live_view
    import Ecto.Query, only: [from: 2]
...

Or would it need to be more like
lib/mono_phoenix_v01_web/live/search_bar.ex with the top of the file looking like the most recent one above?


Post-scrum’: In case you’re wondering how I got this far, while still being this clueless:

I didn’t have the luxury of learning Phoenix and Elixir properly, then taking my time to build a new site from scratch.

I’m working on a site I first built in 1997, in good ol’ HTML 2.0. Ffwd to more than a decade later, a friend rebuilt the site in Ruby in 2010. I paid attention to updates and applied them dutifully for a few years. Then I started learning to play guitar in 2017, and Ruby’s high maintenance needs kinda fell into my rearview (oops!.) So, of course, the site went down on December 1st when Heroku stopped supporting Ruby 2.3.0.

Suddenly I had site users pinging me for answers, and I started losing about $400/mo in ad revenue (more than my rent recently went up! stressy!)

So, after three nightmarish weeks of trying to revive the site by upgrading it from Padrino and Ruby 2.3.0 to Sinatra and 3.1.3, I decided I wanted nothing more to do with Ruby, ever again, and I started looking for an alternative.

Just five weeks after I started putting together a Phoenix / Elixir dev environment on my local and started reading on hexdocs, I had translated the Ruby site to Elixir, and relaunched the production site. The speed of that seems kind of a minor miracle to me. (Search is the only function from the Ruby site that I’ve yet to restore in the new version.)

I think my fast roll from zero to 60 (from never having heard of Elixir to launching a production site) says a lot about how cool Phoenix is. I mean, even a non-engineer n00b like myself can migrate a Ruby site to Elixir and get it back online that fast, while also working 40+ hours a week at my actual job, that’s gotta be a sign of a pretty solid platform and community, right? (And I only posted one question here between 0 and relaunch. :slight_smile: ) Full-disclosure: ChatGPT and text-davinci-003 helped with some of the migration/translation/porting, but they also led me astray as much as they helped, because they both lie with great authoritative confidence. lol.

So, anyway, I’m just completely stumped by trying to add live search. I think I’ve probably confused the hell out of myself by trying to read and absorb way too much info in way too short a time. :slight_smile: Any pointers to examples and/or examples of what I’m missing would be very much appreciated!

Thanks for the reply @al2o3cr . I did encounter both of those issues during my search for the answer to what I’m doing wrong.

But since both of those posts are over 3 years old, and are about much earlier versions of Phoenix and Elixir, I assumed the info in those threads might not be applicable to Phoenix 1.7.1 and Elixir 1.14.1, and/or that I’m not familiar enough with the changes since then to be able to apply the info there to the current versions.

I also noticed that the error in those issues doesn’t match my error: I’m getting “‘module’ is undefined or not available”, but the error in both of those issues is “cannot invoke handle_params … because it is not mounted nor accessed through the router live/3 macro”

If the threads for either of those issues is applicable to my scenario, would you mind explaining to me which of the solutions in those threads applies to my scenario and how? (I’m a clueless n00b who has been learning Phoenix on the fly to migrate a dead Ruby site to Elixir and get it back online, so I’m still quite unclear on many of the concepts, and am still relying heavily on examples of code.)

Thanks!

Hi again!

Just a tip to keep in mind, it can be counterproductive to add code from tutorials without understanding the intent and reasoning behind it. In this case, the tutorial was likely adding a LiveView for a collection of resources such as this example from the docs about a LiveView for Articles that includes multipe LiveView routes with their own actions.

It looked like the first error with the unnecessary .Index was obscuring the cannot invoke handle_params error that was downstream of it.

Anyway, based off this comment from those threads, the issue might be that the handle_params callback can not be used when embedding a LiveView within a regular view with live_render which is something that you’re trying to do that the tutorial you linked to is not.

I suggest removing the handle_params callback and migrating the logic into handle_event callback.

1 Like

As said in the tutorial:

I will not go into details in this post what’s exactly inside Fooz.CodeReviews , but let’s just say it returns an array of pending code reviews from whatever, be it GitHub or GitLab or something else.

So what they’re saying is that there exists a module Fooz.CodeReviews somewhere and it’s up to you to implement it (and to know how to do that based on the description).

Back to your specific issue:

alias MonoPhoenixV01Web.SearchBar

means that “Hey Elixir, there exists a module called MonoPhoenixV01Web.SearchBar, I want to use it with the short name SearchBar in this module”. Then you indeed use it with SearchBar.get_all(query). What Elixir is telling you now is that no such module exists, because it was left as an exercise for the reader by the tutorial author.

You guessed right that you need to define the module yourself.

You can put it anywhere in lib because module names do not correspond to filenames in Elixir, except for some specific Phoenix requirements regarding controllers/views. For this module Phoenix has no requirements. Typically you create a directory structure that somewhat matches your module name.

Since this is a module that deals with your database and not your web interface, I’d recommend naming it MonoPhoenixV01.SearchBar (without the Web) and putting it in lib/mono_phoenix_v01/search_bar/search_bar.ex (though you could remove the folder from in between if you feel it’s superfluous).

This would be wrong as this module is not a controller, so it should not designate itself as a controller.

It is not a live view either, so it should not designate itself as a live view.

What you want is just a module, so start with just defmodule MonoPhoenixV01.SearchBar do (also change the alias to match that name in your live view module). You guessed right that if you want to interact with the database, then you need import Ecto.Query, only: [from: 2] after that (which makes the Ecto.Query.from/2 function available in that module).

Then it’s a matter of implementing the function def get_all(query) do, but I don’t know what it should do so I can’t help you there.

As for tutorial / example suggestions of live search, sadly I don’t really have any but I hope others do. I see you’ve already made great progress by jumping head first into the deep end of the pool and I applaud you for that. But I recommend at least going through the Elixir Getting Started guide at https://elixir-lang.org/getting-started/introduction.html, it will give you a good base on how Elixir works and that in the future will help with debugging such errors and knowing things like “ah, they want me to implement this module myself” just by seeing some code.

3 Likes

Thanks for your replies @Nicd and @codeanpeace (hello again!) I very much appreciate your observations and suggestions. I’m digging back in to apply your wisdoms to my search for search. Have a good weekend!