Phoenix: no function clause matching in Keyword.has_key?/2

Created on 31 Mar 2021  路  12Comments  路  Source: phoenixframework/phoenix

Environment

  • Elixir version (elixir -v): Elixir 1.11.4 (compiled with Erlang/OTP 23)
  • Phoenix version (mix deps): phoenix 1.5.8 (Hex package) (mix)
  • Node.js version (node -v): v14.16.0
  • NPM version (npm -v): 6.14.11
  • Operating system: macOS 11.2.3

Expected behavior

When clicking

movie/show.html.eex

<%= link "Write Review", to: Routes.movie_review_path(@conn, :new, @movie) %>

invokes

review_controller.ex

  def new(conn, %{"movie_id" => movie_id}) do
    movie = Catalogs.get_movie!(movie_id)

    changeset =
      movie
      |> Ecto.build_assoc(:reviews, %Review{})

    render(conn, "new.html", movie: movie, changeset: changeset)
  end

catalogs.ex

 def get_movie!(id), do: Repo.get!(Movie, id)

renders

review/new.html.eex

<%= render "form.html", Map.put(assigns, :action, movie_review_path(@conn, :new, @movie)) %>

invokes

review/form.html.eex

<%= form_for @changeset, movie_review_path(@conn, @action, @movie), fn f -> %> # blows up here

...

<% end %>

I'm expecting that the review/new.html.eex would properly render in the browser.

Actual behavior

The actual result is as follows within the browser:

Screen Shot 2021-03-30 at 10 55 56 PM

Finally, I have reviewed the limited documentation regarding Nested Resources but the docs didn't go into any detail about the actual view and template code. I'm not sure if I'm doing something wrong here or there's a potential bug within Phoenix here.

Most helpful comment

Hi @conradwt,

your error is in these two parts, you're calling the route helper twice which is why you're seeing the error.

    # review/new.html.eex
    <%= render "form.html", Map.put(assigns, :action, movie_review_path(@conn, :new, @movie)) %>

    # review/form.html.eex
    <%= form_for @changeset, movie_review_path(@conn, @action, @movie), fn f -> %> # blows up here

In the first line Map.put(assigns, :action, movie_review_path(@conn, :new, @movie) is adding the review path to the @action attribute. (It's actually added the :action key to the assigns map, but assigns is special, in that its properties are available as attributes in the view, so assigns.action is the same as @action.)

In the second line, you're trying to get the route again, but you're passing in the @action (which already contains the full path) into the route helper, which is where the error is coming from. It's expecting :new (or :index, :edit, etc) but instead it's receiving '/movies/1/reviews'.

You can fix it, by only calling it once, like so:

    # review/new.html.eex
    <%= render "form.html", Map.put(assigns, :action, Routes.movie_review_path(@conn, :new, @movie)) %>

    # review/form.html.eex
    <%= form_for @changeset, @action, fn f -> %>

All 12 comments

Hi @conradwt
What are the values of @action and @movie when you invoke movie_review_path(@conn, @action, @movie)?

You are passing the wrong value somewhere because the route helper tries to show the error and it blows.

can you edit line 354 in ~lib/flix_web/router.ex~ deps/lib/phoenix/rounter/helpers.ex and replace
not Keyword.has_key?(routes, action) ->
with
is_atom(action) and not Keyword.has_key?(routes, action) ->

and see if the shows a nicer error

sorry, after the change you need to run mix deps.compile phoenix

@eksperimental There's only 87 lines within my router.ex. However, the code that you're referring to here is code owned by the Phoenix project.

@eksperimental There's only 87 lines within my router.ex. However, the code that you're referring to here is code owned by the Phoenix project.

My bad. it is:
deps/lib/phoenix/rounter/helpers.ex

@eksperimental This is what I'm seeing and this is much more informative than the previous error message. This should be the default.

Screen Shot 2021-03-31 at 10 16 14 PM

What are the values of @action and @movie when you invoke movie_review_path(@conn, @action, @movie)

I think you action is "movies/1/review" which is wrong. and I don't now your movie_id

@eksperimental The value of movie_id here is equal to 1 from the params.

print the value of those assigns I mentioned earlier. And see where they are being defined,
I think @action is wrong.

@eksperimental Hey, thanks for your assistance. Anyway, I'll do some more reading to figure things out regarding Phoenix's render method.

Hi @conradwt,

your error is in these two parts, you're calling the route helper twice which is why you're seeing the error.

    # review/new.html.eex
    <%= render "form.html", Map.put(assigns, :action, movie_review_path(@conn, :new, @movie)) %>

    # review/form.html.eex
    <%= form_for @changeset, movie_review_path(@conn, @action, @movie), fn f -> %> # blows up here

In the first line Map.put(assigns, :action, movie_review_path(@conn, :new, @movie) is adding the review path to the @action attribute. (It's actually added the :action key to the assigns map, but assigns is special, in that its properties are available as attributes in the view, so assigns.action is the same as @action.)

In the second line, you're trying to get the route again, but you're passing in the @action (which already contains the full path) into the route helper, which is where the error is coming from. It's expecting :new (or :index, :edit, etc) but instead it's receiving '/movies/1/reviews'.

You can fix it, by only calling it once, like so:

    # review/new.html.eex
    <%= render "form.html", Map.put(assigns, :action, Routes.movie_review_path(@conn, :new, @movie)) %>

    # review/form.html.eex
    <%= form_for @changeset, @action, fn f -> %>

@delameko I'll definitely give this a try later this evening and thanks for the explanation here.

@delameko is correct here. Please re-open this issue if there's still a problem.

Was this page helpful?
0 / 5 - 0 ratings