Showing a User

Now that we’ve created the code to show a list of users, we can work on showing a single user. To refresh your memory, let’s look at the route we created earlier:

 get ​"​​/users/:id"​, UserController, ​:show

That’s easy enough. On a request to /users/:id, where :id is part of the inbound URL, the router will add at least two things we’ll need to conn, including the :id that’s part of the URL, and the action name, :show. Then, the router will call the plugs in our pipeline, and then the UserController. To show a single user using this request, we need a controller action, which we add to lib/rumbl_web/controllers/user_controller.ex:

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

Now, you can see why Plug breaks out the params part of the inbound conn. We can use params to extract the individual elements our action needs. In this case, we’re matching on the "id" key to populate the id variable. We then use that to ask the Accounts context for the given user, and use that to render the result.

When you point the browser to localhost:4000/users/1, predictably, Phoenix screams at you. You’ve not yet built the template.

Add this to lib/rumbl_web/templates/user/show.html.eex:

 <h1>Showing User</h1>
 <b>​<%=​ first_name(@user) ​%>​</b> (​<%=​ @user.id ​%>​)

Point your browser to /users/1. You can see the first user, with the dynamic content piped in as we require.

Naming Conventions

When Phoenix renders templates from a controller, it infers the name of the view module, RumblWeb.UserView, from the name of the controller module, RumblWeb.UserController. The view modules infer their template locations from the view module name. In our example, our RumblWeb.UserView would look for templates in the web/templates/user/ directory. Phoenix uses the explicit names you provide throughout, whether singular or plural. That strategy avoids confusing pluralization rules and naming inconsistencies you might find in other frameworks.

You’ll see how to customize these conventions later. For now, know that you can let Phoenix save you some time by letting the good old computer do the work for you. Break the rules if you have to, but if you’re smart about it, you’ll save some tedious ceremony along the way.

Nesting Templates

Often there’s a need to reduce duplication in the templates themselves. For example, 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>

At this point, it’s worth emphasizing that a view in Phoenix is just a module, and templates are just functions. When we add a template named lib/rumbl_web/templates/user/user.html.eex, the view extracts the template from the filesystem and makes it a function in the view itself. That’s why we need the view in the first place. Let’s build on this thought inside iex -S mix:

 iex>​ user = Rumbl.Accounts.get_user(​"​​1"​)
 %Rumbl.Accounts.User{...}
 
 
 iex>​ view = RumblWeb.UserView.render(​"​​user.html"​, ​user:​ user)
 {:safe, [[[[["" | "<strong>"] | "José"] | "</strong> ("] | "1"] | ") "]}
 
 iex>​ Phoenix.HTML.safe_to_string(view)
 "<strong>José</strong> (1) "

We fetch a user from the repository and then render the template directly. Because Phoenix has the notion of HTML safety, we can see that render returns a tuple, tagged as :safe just as we saw with our link helper. Likewise, the contents are also stored in an I/O list for performance.

Each template in our application becomes a render(template_name, assigns) clause in its respective view. So, rendering a template is a combination of pattern matching on the template name and executing the function. The assigns argument is simply a holding hash for user-defined values containing values set by plugs and controller functions. Because the rendering contract is so simple, nothing is stopping developers from defining render clauses directly on the view module, skipping the whole template. For example, in your RumblWeb.ErrorView, you could respond to 404 or 500 status codes with basic error messages by simply implementing the following functions:

 def​ render(​"​​404.html"​, _assigns) ​do
 "​​Page not found"
 end
 
 def​ render(​"​​500.html"​, _assigns) ​do
 "​​Internal server error"
 end

By default, your generated error view implements the template_not_found/2 callback which renders these basic error messages for you. You can see this in action in your own RumblWeb.ErrorView, which contains:

 # By default, Phoenix returns the status message from
 # the template name. For example, "404.html" becomes
 # "Not Found".
 def​ template_not_found(template, _assigns) ​do
  Phoenix.Controller.status_message_from_template(template)
 end

The Phoenix.View module—the one used to define the views themselves—also provides functions for rendering views, including a function to render and convert the rendered template into a string in one pass:

 iex>​ user = Rumbl.Accounts.get_user(​"​​1"​)
 %Rumbl.Accounts.User{...}
 
 iex>​ Phoenix.View.render(RumblWeb.UserView, ​"​​user.html"​, ​user:​ user)
 {:safe, [[[[["" | "<strong>"] | "José"] | "</strong> ("] | "1"] | ") "]}
 
 iex>​ Phoenix.View.render_to_string(RumblWeb.UserView, ​"​​user.html"​, ​user:​ user)
 "<strong>José</strong> (1) "

Behind the scenes, Phoenix.View calls render in the given view and adds some small conveniences, like wrapping our templates in layouts whenever one is available. Let’s find out how.

Layouts

When we call render in our controller, instead of rendering the desired view directly, the controller first renders the layout view, which then renders the actual template in a predefined markup. This allows developers to provide a consistent markup across all pages without duplicating it over and over again.

Since layouts are regular views with templates, all the knowledge that you’ve gained so far applies to them. In particular, each template receives a couple of special assigns when rendering, namely @view_module and @view_template. You can see these in lib/rumbl_web/templates/layout/app.html.eex:

 <main role=​"main"​ class=​"container"​>
  <p class=​"alert alert-info"​ role=​"alert"​>
 <%=​ get_flash(@conn, ​:info​) ​%>
  </p>
  <p class=​"alert alert-danger"​ role=​"alert"​>
 <%=​ get_flash(@conn, ​:error​) ​%>
  </p>
 <%=​ render @view_module, @view_template, assigns ​%>
 </main>

It’s just pure HTML with a render call of render @view_module, @view_template, assigns, but it doesn’t need to be restricted to HTML. As in any other template, the connection is also available in layouts as @conn, giving you access to any other helper in Phoenix. When you call render in your controller, you’re actually rendering with the :layout option set by default. This allows you to render the view and template for your controller action in the layout with a plain render function call. No magic is happening here.

We can tweak the existing layout to be a little more friendly to our application. Rather than slog through a bunch of CSS and HTML here, we’ll let you work out your own design. If you choose to do so, replace the layout you find at lib/rumbl_web/templates/layout/app.html.eex with one you like better. As always, you’ll see your browser autoupdate.

We’re just about done here. By now, our growing company valuation is somewhere north of, well, the tree house you built in the third grade. Don’t worry, though; things will pick up in a hurry. You’re going to go deeper faster than you thought possible.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.191.86