ruk·si

🛤️ Ruby on Rails
Templating

Updated at 2015-09-24 20:37

Related to Ruby Guide, Ruby on Rails Guide and Ruby on Rails - Helpers.

The final HTML is composed of a layout, a view and optional partials. Normally the layout wraps a view and both can contain additional partials. There are also content yields and content_for statements in the templates.

Layout
  Yield 1
  Partial
  Action View
    Define Yield 1
    Partial
    Partial
    Define Yield 2
  Partial
  Yield 2

Use convention over configuration. Rails lookups views by the action name and uses the default application layout.

# app/controllers/books_controller.rb
class BooksController < ApplicationController
  def index
  end
end
# config/routes.rb
resources :books
<!-- app/views/books/index.html.erb -->
<h1>Books are coming soon!</h1>

Use conditionally render actions. It will try to look for views like regular actions.

# looks for app/views/book/special_show.html.erb if books is special
def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show"
    return # render doesn't stop the request by itself
  end
end

Default text response MIME type is text/HTML. You will need to change it or use helpers

render text: 'Submission accepted', content_type: 'text/plain'
render plain: 'ok'
render json: { key: 'value' }

Use plain or json responses for AJAX. Doesn't include any markup. Header-only responses are also common in APIs.

render plain: "ok"      # doesn't load the layout
render json: @product   # calls to_json if object has it
head :bad_request       # only header

You can also define a callback using JSONP. Name of a callback function to be invoked in the browser when it gets your response.

render json: @record, callback: 'updateRecordsDisplay'

You can respond depending on the request type.

respond_to do |format|
  format.html                         # default view lookup
  format.json { render json: @user }  # turn user to JSON and return it
end

Add error codes to HTTP errors when applicable.

render status: 404
render status: 500

Use redirect_to to redirect visitors.

redirect_to photos_url
redirect_to :back       # Redirects user where they came, needs error handling
redirect_to post_url(@post), alert: 'Watch it, mister!'           # With flash
redirect_to post_url(@post), notice: 'Saved!'                     # With flash
redirect_to post_url(@post), flash: { updated_post_id: @post.id } # With flash

Change the layout from controller. Layout declaration cascades downward in the class hierarchy.

class ProductsController < ApplicationController
  layout "inventory"
  # ...
end

class ProductsController < ApplicationController
  layout "product", except: [:index, :rss]
end

Multiple templating styles are supported but ERB is the default. You name your files e.g. index.html.erb which gets compiled to index.html.

ERB templates have multiple tags:

  • <% %> executes the ruby code in between, can be multiline.
  • <%= %> also prints the result.
  • <% -%> also removes line break and whitespace after the expression.
  • <%- %> also removes line break and whitespace before the expression.
  • <%# %> creates a comment that is not sent to the visitor.
# @ refers to the model that is doing the rendering
Hello, <%= @name %>.

Keep your templates under 100 lines of code. Mitigate code duplication by using partial templates and layouts. Partial templates are used to share common HTML between views and split your templates to multiple files. Naming shared view directory as shared is common, partials can be in the controller view directory or in shared.

# will get partial from app/views/shared/_header.html.erb.
<%= render :partial => 'shared/header' %>

<% @products.fine.find_each do |product| %>
  <%= render :partial => "product", :locals => { :product => product } %>
<% end %>

Create view objects. View objects allow you to define how a certain set of model instances is shown to the end-user. They can have partials for different users etc. which makes they a lot more flexible than helper function.

Don't call html_safe in templates. Then you are always doing something wrong e.g. including too much logic in the templates. hmtl_safe marks the the given string as safe. You should use HTML helpers to create the already escaped HTML. Likewise, helpers should always return HTML safe, by using HTML helpers or html_escape.

# in whatever_helper.rb
def magicfy(value)
   content_tag(:span, value, :class => "magic")
end

# also fine
def magicfy(value)
   "<span class=\"magic\">#{ html_escape(value) }</span>".html_safe
end

Be very careful when using raw in templates. raw is used to return objects as unescaped HTML but marking them safe.

Each controller has associated directory in the app/views. This directory has a view for each controller action that renders something to the visitor. View name should be the same as the action name. Partial files that are not to rendered alone are named starting with underscore _.

app/controllers/articles_controller.rb
# has
app/views/articles
app/views/articles/index.html.erb
app/views/articles/edit.html.erb
app/views/articles/show.html.erb
app/views/articles/new.html.erb
app/views/articles/_form.html.erb

Learn to utilize and create helpers. Rails has a wide range of view helpers, more in helper notes.

Keep views simple. Never make complex formatting in the views, export the formatting to a method in the view helper.

# renders a container tag that relates to your ActiveRecord object
<%= content_tag_for(:tr, @article, class: "frontpage") do %>
  <td><%= @article.title %></td>
<% end %>

<tr id="article_1234" class="article frontpage">
  <td>Hello World!</td>
</tr>

Never call the model layer directly from a view. Use view helpers, but check standard helper libraries first.

<%= content_tag_for(:tr, @articles) do |article| %>
  <td><%= article.title %></td>
<% end %>

<tr id="article_1234" class="article">
  <td>Hello World!</td>
</tr>
<%= div_for(@article, class: "frontpage") do %>
  <td><%= @article.title %></td>
<% end %>

Use content yielding. Especially useful for <title> and metatags.

<div>
  <h1> This is the wrapper!</h1>
  <%= yield :my_content %>
</div>

<% content_for :my_content do %>
  This is the content.
<% end %>
<% if content_for?(:title) %>
  <title><%= yield(:title) %> | Website </title>
<% else %>
  <title>Website</title>
<% end %>

<% if content_for?(:my_content) %>
  <%= yield(:my_content) %>
<% else %>
  <% navigation_content = yield(:navigation) %>
  <% if navigation_content.present? %>
    <%# Doodooodooo %>
  <% end %>
<% end %>

Use partial templates.

<%= render "shared/menu" %>
<%= render partial: "link_area", layout: "graybar" %>

Define body to allow custom styling per page. Helps e.g. highlightin current navigation item.

<body <%= content_for(:body_attr) %>
    class="<%= controller_name %> <%= action_name %>"
    data-controller="<%= controller_name %>"
    data-action="<%= action_name %>">

Returned Parameters

All form values are found in params.

<input id="person_name" name="person[name]" type="text" value="Henry">
# {'person' => {'name' => 'Henry'}}
params[:person][:name]
<input name="person[phone_number][]" type="text">
<input name="person[phone_number][]" type="text">
<input name="person[phone_number][]" type="text">
params[:person][:phone_number] # an array
<input name="addresses[][line1]" type="text">
<input name="addresses[][line2]" type="text">
<input name="addresses[][city]" type="text">
params[:addresses] # contains an array of hashes

Security

<%# form helpers generate CSRF tokens automatically though %>
<%= csrf_meta_tags %>

Streaming

You can stream views. This renders the layout first, returns it to the user and sends content_for and provide by streaming. Does not work with JSON.

class ProfileController
  def dashboard
    # Allow lazy execution of the queries
    @posts = Post.all
    @pages = Page.all
    @articles = Article.all
    render stream: true
  end
end
  • All streaming actions are executed in a separate thread so they must be threadsafe.
  • Webserver must support streaming.
  • All headers must be specified before anything is sent to the browser.
  • Streaming socket must be closed in the end.

Streaming template example:

<html>
  <head><title><%= yield :title %></title></head>
  <body><%= yield %></body>
</html>

<%= content_for :title, "Main" %>
Hello
<%= content_for :title, " page" %>
<%# => title becomes Main page %>

<%= provide :title, "Main" %>
Hello
<%= content_for :title, " page" %>
<%# => title becomes Main as provide means not to wait for more data %>

Sources