Programming phoenix Chapter 3. Controllers page 54

Hi everyone,

Problem in programming phoenix page 55

Error trace:

[info] GET /users/
[debug] Processing with RumblWeb.UserController.index/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 500 in 53ms
[error] #PID<0.435.0> running RumblWeb.Endpoint (connection #PID<0.434.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /users/
** (exit) an exception was raised:
    ** (ArgumentError) assign @user not available in eex template.

Please make sure all proper assigns have been set. If this
is a child template, ensure assigns are given explicitly by
the parent template as they are not automatically forwarded.

Available assigns: [:conn, :users, :view_module, :view_template]

        (phoenix_html) lib/phoenix_html/engine.ex:133: Phoenix.HTML.Engine.fetch_assign!/2
        (rumbl) lib/rumbl_web/templates/user/index.html.eex:2: RumblWeb.UserView."index.html"/1
        (rumbl) lib/rumbl_web/templates/layout/app.html.eex:26: RumblWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:410: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:729: Phoenix.Controller.__put_render__/5
        (phoenix) lib/phoenix/controller.ex:746: Phoenix.Controller.instrument_render_and_send/4
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.action/2
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.phoenix_controller_pipeline/2
        (phoenix) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.plug_builder_call/2
        (rumbl) lib/plug/debugger.ex:122: RumblWeb.Endpoint."call (overridable 3)"/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

I know that this errors tell me that I don’t have assigned to the template the value of user but i did using this:

Content from :user/index.html.eex

<tr>
<td><%= render("user.html", user: @user) %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, @user.id) %></td>
</tr>

Content from :user/show.html.eex

<h1>Showing User</h1>
<%= render "user.html", user: @user %>

Content from: user/user.html.eex
<strong><%= first_name(@user) %></strong> (<%= @user.id %>)

So why does phoenix tell me that i haven’t assigned user in the template?

Thanks in advance

How does this function look like? Do you have any calls to render/* there?

My UserController content:

defmodule RumblWeb.UserController do
  use RumblWeb, :controller

  alias Rumbl.Accounts

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user(id)
    render(conn, "show.html", user: user)
  end
end

Also this is the UserView content:

defmodule RumblWeb.UserView do
  use RumblWeb, :view

  alias Rumbl.Accounts

  def first_name(%Accounts.User{name: name}) do
    name
    |> String.split(" ")
    |> Enum.at(0)
  end
end

:users is not @user, your index.html.eex is wrong.

1 Like

OK the book is also wrong about that one. Because the content is like this for index.html.eex

<tr>
<td><%= render "user.html", user: user %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, user.id) %></td>
</tr>

Is it just that? I’d expect a for around that… Something like this:

<table>
<%= for user <- @users do %>
  <tr><td><%= render "user.html", user: user %></td></tr>
<% end %>
</table>

It uses the user.html.eex to get rid of duplication but to me it seems that it doesn’t work

The following code for index.html.eex doesn’t work but it compiles:

<tr>
<td><%= render("user.html", users: @users) %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, @users.id) %></td>
</tr>

The following doesn’t compile:

<tr>
<td><%= render("user.html", users: users) %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, users.id) %></td>
</tr>

user.html.eex is for a single user, but index (by convention) is for many. So you need to iterate over your many users and render them one by one.

I do not have access to the book. I can only tell you what I think is going on here.

This is the excerpt of the part we are talking about:

Nesting Templates
Often there’s a need to reduce duplication in the templates themselves. For ex-
ample, both of our templates have common code that renders a user. Take the
common code and 
create a user template in lib/rumbl_web/templates/user/user.html.eex:
<strong><%= first_name(@user) %></strong> (<%= @user.id %>)
We created another template to render a user. Then, whenever we build tables
or listings of users, we can re-use this template. Now, change your show.html.eex
template to render it:
<h1>Showing User</h1>
<%= render "user.html", user: @user %>
Also, change your index.html.eex template to render it:
<tr>
<td><%= render "user.html", user: user %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, user.id) %></td>
</tr>

Yes, "user.html.eex" seems to be fine. As I said already… There is a loop missing in index.html.eex.

I’m pretty sure, the book already had a loop and wants you to replace the loops body with the snippet you show, while you replaced the full content.

That makes sense I will do that the original loop is this one:

Or here https://media.pragprog.com/titles/phoenix14/code/controllers_views_templates/listings/rumbl/lib/rumbl_web/templates/user/index.html.eex

<h1>Listing Users</h1>
<table>
<%= for user <- @users do %>
<tr>
<td><b><%= first_name(user) %></b> (<%= user.id %>)</td>
<td><%= link "View", to: Routes.user_path(@conn, :show, user.id) %></td>
</tr>
<% end %>
</table>

Not working

<table>
<%= for user <- @users do %>
<tr>
<td><%= render("user.html", users: @users) %></td>
<td><%= link "View", to: Routes.user_path(@conn, :show, @users.id) %></td>
</tr>
<% end %>
</table>

Error trace:

[info] GET /users/
[debug] Processing with RumblWeb.UserController.index/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 500 in 52ms
[error] #PID<0.459.0> running RumblWeb.Endpoint (connection #PID<0.458.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /users/
** (exit) an exception was raised:
    ** (ArgumentError) assign @user not available in eex template.

Please make sure all proper assigns have been set. If this
is a child template, ensure assigns are given explicitly by
the parent template as they are not automatically forwarded.

Available assigns: [:users]

        (phoenix_html) lib/phoenix_html/engine.ex:133: Phoenix.HTML.Engine.fetch_assign!/2
        (rumbl) lib/rumbl_web/templates/user/user.html.eex:1: RumblWeb.UserView."user.html"/1
        (rumbl) lib/rumbl_web/templates/user/index.html.eex:4: anonymous fn/3 in RumblWeb.UserView."index.html"/1
        (elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
        (rumbl) lib/rumbl_web/templates/user/index.html.eex:2: RumblWeb.UserView."index.html"/1
        (rumbl) lib/rumbl_web/templates/layout/app.html.eex:26: RumblWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:410: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:729: Phoenix.Controller.__put_render__/5
        (phoenix) lib/phoenix/controller.ex:746: Phoenix.Controller.instrument_render_and_send/4
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.action/2
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.phoenix_controller_pipeline/2
        (phoenix) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.plug_builder_call/2
        (rumbl) lib/plug/debugger.ex:122: RumblWeb.Endpoint."call (overridable 3)"/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

And we are back to singular vs plural.

1 Like

The index works now like this

<table>
  <%= for user <- @users do %>
  <tr>
    <td><%= render("user.html", user: user) %></td>
    <td><%= link "View", to: Routes.user_path(@conn, :show, user.id) %></td>
  </tr>
  <% end %>
</table>

The show doesn’t work

<h1>Showing User</h1>

<%= render "user.html", user: @user %>

Error trace:

[info] GET /users/1
[debug] Processing with RumblWeb.UserController.show/2
  Parameters: %{"id" => "1"}
  Pipelines: [:browser]
[info] Sent 500 in 26ms
[error] #PID<0.548.0> running RumblWeb.Endpoint (connection #PID<0.547.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /users/1
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in RumblWeb.UserView.first_name/1
        (rumbl) lib/rumbl_web/views/user_view.ex:6: RumblWeb.UserView.first_name(nil)
        (rumbl) lib/rumbl_web/templates/user/user.html.eex:1: RumblWeb.UserView."user.html"/1
        (rumbl) lib/rumbl_web/templates/user/show.html.eex:2: RumblWeb.UserView."show.html"/1
        (rumbl) lib/rumbl_web/templates/layout/app.html.eex:26: RumblWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:410: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:729: Phoenix.Controller.__put_render__/5
        (phoenix) lib/phoenix/controller.ex:746: Phoenix.Controller.instrument_render_and_send/4
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.action/2
        (rumbl) lib/rumbl_web/controllers/user_controller.ex:1: RumblWeb.UserController.phoenix_controller_pipeline/2
        (phoenix) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.plug_builder_call/2
        (rumbl) lib/plug/debugger.ex:122: RumblWeb.Endpoint."call (overridable 3)"/2
        (rumbl) lib/rumbl_web/endpoint.ex:1: RumblWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
        (cowboy) /home/dan/Codes/rumbl/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Why does this not match RumblWeb.UserView.first_name/1from the view?

Thanks @NobbZ

Had some problems in the context

defmodule Rumbl.Accounts do
  @moduledoc """
  The Accounts context.
  """

  alias Rumbl.Accounts.User 

  def list_users do
    [
      %User{id: "1", name: "José", username: "josevalim"},
      %User{id: "2", name: "Bruce", username: "redrapids"},
      %User{id: "3", name: "Chris", username: "chrismccord"}
    ]
  end

  def get_user(id) do
    Enum.find(list_users(), fn map -> map.id == id end)
  end

  def get_user_by(params) do
    Enum.find(list_users(), fn map ->
      Enum.all?(params, fn {key, val} -> Map.get(map, key) == val end)
    end)
  end
end

Works now