Render Responsive Views

Layouts

So far, each of our views has had an <html> tag and a <body> tag. By using a "layout", we can extract common code that is used for several view templates to one location. We do this by creating an application.html.erb file.

<!-- app/views/layouts/application.html.erb -->

<!DOCTYPE html>
<html>
  <head>
    <title>BlogApp</title>
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
  </head>

  <body>

    <%= yield %>

  </body>
</html>

"Common code" usually includes the <html>, <head>, and <body> tags.

Notice that we also have HTML tag helpers from the chapter on unobtrusive scripting. We make sure to include those in our layout as well. These should be included so any additions or changes to CSS and Javascript will be included in our application.

You can define and use other layouts, but layouts/application.html.erb is the default, so that's what we create here. Rails will actually generate a layout similar to the one above whenever you run $ rails new to generate a new app.

yield identifies where content from our view should be inserted into the enclosing layout. Typically, you'll have a layout for most view templates, so as to cut out code duplication.

Partials

We've shown that it is possible to insert code from a view template into a layout, and then render that layout and view together to the user. We can even go a step further, it is possible to move certain HTML/ERB code that is reused in various view templates to one location. This is called a partial view template or just "partial". Let's move some redundant HTML/ERB code to a partial. The convention we want to follow is to create a partial in the same view folder as the resource it is used for. So, if this is a partial for posts, then we'll save the partial view template file under the views/posts folder. One other convention that is important to remember is that partial view templates start with an underscore. We do this to help differentiate partials from other view templates. First, let's first take a look at the two view templates we are working with, posts/edit.html.erb and posts/new.html.erb.

<!-- app/views/posts/new.html.erb -->

<div class="errors">
  <% @post.errors.each do |attribute, error| %>
    <p class="error" style="color: orange">
      <%= attribute %>: <%= error %>
    </p>
  <% end %>
</div>


<%= form_tag posts_path, method: "post" do %>
  <%= label_tag 'title' %>
  <%= text_field_tag 'post[title]', @post.title %>

  <br /> <br />

  <%= label_tag 'body' %>
  <%= text_area_tag 'post[body]', @post.body %>

  <br /> <br />

  <%= label_tag 'author' %>
  <%= text_field_tag 'post[author]', @post.author %>

  <br /> <br />

  <%= submit_tag "Create Post" %>
<% end %>
<!-- app/views/posts/edit.html.erb -->

<div class="errors">
  <% @post.errors.each do |attribute, error| %>
    <p class="error" style="color: orange">
      <%= attribute %>: <%= error %>
    </p>
  <% end %>
</div>


<%= form_tag url_for(action: 'update'), method: "patch" do %>

  <%= label_tag 'Title' %>
  <%= text_field_tag 'post[title]', @post.title %>

  <br /> <br />

  <%= label_tag 'Body' %>
  <%= text_area_tag 'post[body]', @post.body %>

  <br /> <br />

  <% label_tag 'Author' %>
  <%= text_field_tag 'post[author]', @post.author %>

  <br /> <br />

  <%= submit_tag 'Update Post' %>

<% end %>

<br />
<%= my_link_to 'Back to Post', posts_path %>

Let's look at what similarities these two views contain:

  1. They both have the same error messages
  2. They both use fields for their form that are nearly the same.

We can address each of these commonalities by extracting them to their own partial views. Let's tackle the one for error messages first.

<!-- app/views/posts/edit.html.erb -->

<%= render "errors" %>

<!-- ... -->
<!-- app/views/posts/new.html.erb -->

<%= render "errors" %>

<!-- ... -->
<!-- app/views/posts/_errors.html.erb -->

<div class="errors">
  <% @post.errors.each do |attribute, error| %>
    <p class="error" style="color: orange">
      <%= attribute %>: <%= error %>
    </p>
  <% end %>
</div>

By using the code above, we're able to move any error related code to one place. Then, we can use that code in any view template within the same directory as _errors by calling render "errors". Now, if we wanted to make our error partial usable for multiple resources, we could store it in a separate directory. Let's say we do that, and now _errors.html.erb is now in a directory called shared. If we want to still use that partial in new and edit view template for posts, then we'll have to provide the relative path to that file, starting from the views directory: <%= render "shared/errors" %>.

The above partial would then look like this:

<!-- app/views/shared/_errors.html.erb -->

<div class="errors">
  <% obj.errors.each do |attribute, error| %>
    <p class="error" style="color: orange">
      <%= attribute %>: <%= error %>
    </p>
  <% end %>
</div>

And when we call render for that errors partial, we'll have to specify what the local obj is:

<!-- For Posts -->

<%= render 'shared/errors', obj: @post %>

<!-- For Comments -->

<%= render 'shared/errors', obj: @comment %>

Next, let's work on view template similarity number 2, the fields for our two forms. Note, that our two forms for edit.html.erb and new.html.erb go to different paths. For the time being we'll only extract the fields for these forms to a partial. But, later on we'll be able to extract the entire form.

<!-- app/views/posts/new.html.erb -->

<%= render "shared/errors", obj: @post %>

<%= form_tag posts_path, method: "post" do %>
  <%= render "form_fields" %>
<% end %>
<br />
<%= link_to "Back to Posts", posts_path %>
<!-- app/views/posts/edit.html.erb -->

<%= render "shared/errors", obj: @post %>

<%= form_tag url_for(action: 'update'), method: "patch" do %>
  <%= render "form_fields" %>
<% end %>
<br />
<%= my_link_to 'Back to Post', posts_path %>
<!-- app/views/posts/_form_fields.html.erb -->

<%= label_tag 'Title' %>
<%= text_field_tag 'post[title]', @post.title %>
<br /> <br />

<%= label_tag 'Body' %>
<%= text_area_tag 'post[body]', @post.body %>
<br /> <br />

<%= label_tag 'Author' %>
<%= text_field_tag 'post[author]', @post.author %>
<br /> <br />

<%= submit_tag "#{@post.new_record? ? 'Create' : 'Update'} Post" %>

So far we have seen that we can render partials by calling <%= render "path/to/partial" %>. There is actually one other way to call render, and that is by passing a hash to render.

<%= render { partial: 'shared/errors' } %>

The code above would give us the same result as calling: <%= render 'shared/errors' %>

Options for using layout method

In the controller, we may specify a different layout (instead of the default one, application.html.erb) for our views. It's also possible to set a specific layout for all actions in a controller. We do this by using the layout macro:

layout 'layout_file_name', options

As an example, what if we wanted to use a different layout for all actions in our PostsController. First, we would have to create a new layout file and store it under the views/layouts folder. Then, all that needs to be done is to specify that file in PostsController.

class PostsController < ApplicationController
  # ...
  layout 'file_name'


  # ...
end

Pretty useful. But we may want to only use this new layout file in certain actions, not all of them. We have two ways to go about that. One way would be to specify which actions to apply the layout to via our options hash. Code such as the following is perfectly valid:

layout 'file_name', only: :index

Or, if we want specify more than one action:

layout 'file_name' only: [:index, :new]

If we don't want to specify which actions apply for this layout, we can instead specify which ones we don't want by using the syntax except: instead of only:

layout 'file_name' except: [:edit, :create, :update, :destroy]

Finally, we may also specify the layout in the action itself. Recall that in these actions we have been specifying which view template to render, e.g. render :edit. render can take a layout key as part of its options hash.

render :edit, layout: 'file_name'

Flash Messages

In Rails, the flash message is a convenient way to show messages on a page, typically as confirmation or error message after user actions.

Let's add flash messages to our app for the create action of the CommentsController:

def create
  @comment = @post.build_comment(params[:comment])

  if @comment.save
    redirect_to post_path(@post.id)
  else
    render 'posts/show'
  end
end

Becomes:

def create
  @comment = @post.build_comment(params[:comment])

  if @comment.save
    flash[:success] = "You have successfully created the comment."
    redirect_to post_path(@post.id)
  else
    flash.now[:error] = "Comment couldn't be created. Please check the errors."
    render 'posts/show'
  end
end

You can consider flash as a hash that can store messages which can then be pulled out in the next HTTP request. This is why we added flash[:success] before the redirection. Flash messages are automatically deleted after the next request.

If we want the messages to be available to the current request, we can use the flash.now hash instead, like we did for the error path.

We'll also need to put the flash messages in the view. In fact we're going to put the snippet to handle flash messages in a partial and render it from the application layout.

Let's do that now.

<!-- app/views/layouts/application.html.erb -->

<!DOCTYPE html>
<html>
  <head>
    <title>BlogApp</title>
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= render 'layouts/messages' %>
    <%= yield %>

  </body>
</html>
<!-- app/views/layouts/_messages.html.erb -->

<% flash.each do |name, message| %>
  <% if message.is_a? String %>
    <p style="color: <%= name == 'success' ? 'green' : 'red' %>">
      <%= message %>
    </p>
  <% end %>
<% end %>

There, now we may include a flash message in our view templates. This is a feature that is available throughout our entire app. That call to <%= render 'layouts/messages' %> makes it all possible.