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
7enderb
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
12endContent 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:
Link Helpers
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
12enderb
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: <script>alert('xss')</script> -->
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
12endViews make your data presentable to users - keep them clean and focused on display!
