Skip
Arish's avatar

19. Views Basics


Views in Rails

Views are the V in MVC. They're responsible for generating the HTML that gets sent to the user's browser. Rails uses ERB (Embedded Ruby) by default for templating.

ERB Syntax

ERB allows you to embed Ruby code in HTML:

erb
1<!-- Output tag - displays result -->
2<%= expression %>
3
4<!-- Execution tag - runs code without output -->
5<% code %>
6
7<!-- Comment tag -->
8<%# This is a comment %>

Examples

erb
1<!-- Display a variable -->
2<h1><%= @article.title %></h1>
3
4<!-- Conditional logic -->
5<% if @user.admin? %>
6  <span class="badge">Admin</span>
7<% end %>
8
9<!-- Loops -->
10<ul>
11  <% @articles.each do |article| %>
12    <li><%= article.title %></li>
13  <% end %>
14</ul>

Instance Variables

Controllers pass data to views through instance variables:

ruby
1# app/controllers/articles_controller.rb
2class ArticlesController < ApplicationController
3  def show
4    @article = Article.find(params[:id])
5    @comments = @article.comments.recent
6  end
7end
erb
1<!-- app/views/articles/show.html.erb -->
2<article>
3  <h1><%= @article.title %></h1>
4  <div class="content">
5    <%= @article.body %>
6  </div>
7  
8  <section class="comments">
9    <h2><%= @comments.count %> Comments</h2>
10    <% @comments.each do |comment| %>
11      <div class="comment">
12        <strong><%= comment.author %></strong>
13        <p><%= comment.body %></p>
14      </div>
15    <% end %>
16  </section>
17</article>

Layouts

Layouts wrap your views with common HTML structure:

erb
1<!-- app/views/layouts/application.html.erb -->
2<!DOCTYPE html>
3<html>
4<head>
5  <title><%= content_for(:title) || 'My App' %></title>
6  <%= csrf_meta_tags %>
7  <%= csp_meta_tag %>
8  <%= stylesheet_link_tag 'application' %>
9  <%= javascript_include_tag 'application', defer: true %>
10</head>
11<body>
12  <header>
13    <%= render 'shared/navigation' %>
14  </header>
15  
16  <main>
17    <%= yield %>
18  </main>
19  
20  <footer>
21    <%= render 'shared/footer' %>
22  </footer>
23</body>
24</html>

Custom Layouts

ruby
1# Use a different layout for the entire controller
2class AdminController < ApplicationController
3  layout 'admin'
4end
5
6# Use different layout for specific action
7class ArticlesController < ApplicationController
8  def print
9    @article = Article.find(params[:id])
10    render layout: 'print'
11  end
12end

Content For

Pass content from views to layouts:

erb
1<!-- app/views/articles/show.html.erb -->
2<% content_for :title do %>
3  <%= @article.title %> - My Blog
4<% end %>
5
6<% content_for :sidebar do %>
7  <div class="related-articles">
8    <%= render @article.related %>
9  </div>
10<% end %>
11
12<article>
13  <h1><%= @article.title %></h1>
14  <%= @article.body %>
15</article>
erb
1<!-- app/views/layouts/application.html.erb -->
2<head>
3  <title><%= yield :title %></title>
4</head>
5<body>
6  <main><%= yield %></main>
7  <aside><%= yield :sidebar %></aside>
8</body>

Partials

Partials are reusable view fragments. Their filenames start with an underscore:

erb
1<!-- app/views/articles/_article.html.erb -->
2<article class="article-card">
3  <h2><%= link_to article.title, article %></h2>
4  <p><%= truncate(article.body, length: 150) %></p>
5  <footer>
6    By <%= article.author.name %>7    <%= time_ago_in_words(article.created_at) %> ago
8  </footer>
9</article>

Rendering Partials

erb
1<!-- Render a single partial -->
2<%= render 'article', article: @article %>
3<%= render partial: 'article', locals: { article: @article } %>
4
5<!-- Render a collection -->
6<%= render @articles %>
7<!-- Same as: -->
8<%= render partial: 'article', collection: @articles %>
9
10<!-- With custom variable name -->
11<%= render partial: 'article', collection: @articles, as: :post %>
12
13<!-- With spacer -->
14<%= render partial: 'article', collection: @articles, spacer_template: 'separator' %>
15
16<!-- From another controller -->
17<%= render 'shared/navigation' %>
18<%= render 'comments/form', comment: @comment %>

Partial with Object

erb
1<!-- When you pass an object matching the partial name -->
2<%= render @article %>
3<!-- Rails looks for app/views/articles/_article.html.erb -->
4<!-- and passes local variable 'article' -->

View Helpers

Helpers are methods available in all views:

erb
1<%= link_to 'Home', root_path %>
2<%= link_to 'Article', @article %>
3<%= link_to 'Edit', edit_article_path(@article), class: 'btn' %>
4<%= link_to 'Delete', @article, method: :delete, data: { confirm: 'Sure?' } %>
5
6<!-- With block for complex content -->
7<%= link_to @article do %>
8  <h3><%= @article.title %></h3>
9  <span><%= @article.author.name %></span>
10<% end %>

Image Helpers

erb
1<%= image_tag 'logo.png' %>
2<%= image_tag 'avatar.jpg', class: 'rounded', alt: 'User avatar' %>
3<%= image_tag user.avatar.url, size: '100x100' %>

Text Helpers

erb
1<%= truncate(@article.body, length: 100) %>
2<%= truncate(@article.body, length: 100, separator: ' ') %>
3
4<%= simple_format(@article.body) %>  <!-- Converts \n to <br> and wraps in <p> -->
5
6<%= pluralize(5, 'comment') %>  <!-- "5 comments" -->
7<%= pluralize(1, 'comment') %>  <!-- "1 comment" -->
8
9<%= highlight(@article.body, 'keyword') %>  <!-- Highlights matches -->
10<%= excerpt(@article.body, 'keyword', radius: 50) %>  <!-- Excerpt around keyword -->

Number Helpers

erb
1<%= number_to_currency(1234.50) %>  <!-- $1,234.50 -->
2<%= number_to_percentage(75.5) %>   <!-- 75.500% -->
3<%= number_with_delimiter(1234567) %>  <!-- 1,234,567 -->
4<%= number_to_human(1234567) %>     <!-- 1.23 Million -->
5<%= number_to_human_size(1234567) %>  <!-- 1.18 MB -->

Date/Time Helpers

erb
1<%= time_ago_in_words(@article.created_at) %>  <!-- "3 days ago" -->
2<%= distance_of_time_in_words(Time.now, @event.starts_at) %>
3
4<%= @article.created_at.strftime("%B %d, %Y") %>  <!-- "January 15, 2024" -->
5
6<!-- With I18n -->
7<%= l(@article.created_at, format: :long) %>

Custom Helpers

Create your own helpers:

ruby
1# app/helpers/articles_helper.rb
2module ArticlesHelper
3  def reading_time(article)
4    minutes = (article.body.split.size / 200.0).ceil
5    "#{minutes} min read"
6  end
7  
8  def article_status_badge(article)
9    status = article.published? ? 'published' : 'draft'
10    content_tag :span, status.titleize, class: "badge badge-#{status}"
11  end
12end
erb
1<!-- Use in views -->
2<%= reading_time(@article) %>
3<%= article_status_badge(@article) %>

Safe HTML Output

Rails automatically escapes HTML to prevent XSS attacks:

erb
1<%= "<script>alert('xss')</script>" %>
2<!-- Output: &lt;script&gt;alert('xss')&lt;/script&gt; -->
3
4<!-- Mark as safe (be careful!) -->
5<%= "<b>Bold</b>".html_safe %>
6<%= raw "<b>Bold</b>" %>
7
8<!-- Better: use sanitize -->
9<%= sanitize @article.body, tags: %w(b i strong em a) %>

JSON Views

For API responses:

ruby
1# app/views/articles/show.json.jbuilder
2json.id @article.id
3json.title @article.title
4json.body @article.body
5json.author do
6  json.name @article.author.name
7  json.email @article.author.email
8end
9json.comments @article.comments do |comment|
10  json.id comment.id
11  json.body comment.body
12end

Views make your data presentable to users - keep them clean and focused on display!