Views are the presentation layer of the MVC pattern which Rails is built upon.
They are ultimately responsible for displaying data to users and allowing them to interact with our application to produce the desired outcomes.
In Rails, by default, they are ERB template files that end up producing HTML, but they can also produce other formats when needed.
Let's learn about what views are:
An overview
Views are the “V” in the MVC pattern, which was explicitly created to facilitate the creation of user interfaces using object-oriented languages.
By default, views in Rails are ERB (embedded ruby) templates that are processed server side to generate the HTML that's returned to the client's browser.
They are located in the app/views
folder within sub-folders that correspond to controllers.
For example, views for the CompaniesController
are located in app/views/companies
.
Just like controllers views follow a naming convention: they should match the name of the controller action they correspond to: the index
action for the CompaniesController
would typically render the app/views/companies/index.html.erb
view by default.
But, views are not bound to exclusively return HTML, they can also return the data in other formats like JSON, XML, CSV, RSS, among others.
Layouts
As it name indicates, layouts are a special type of view meant to encapsulate standalone views.
They avoid duplication by declaring repeating code like the body
, head
or footer
sections of the HTML document.
Inside them, we also declare things that are common to our layout, like a navbar or the partial in charge of rendering flash messages.
A typical Rails layout looks like this:
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<head>
<title><%= content_for?(:title) ? yield(:title) : "Default Title" %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= render 'shared/navbar' %>
<div class="container">
<%= yield %>
</div>
<%= render 'shared/footer' %>
</body>
</html>
The most important part of this view is the call to the yield
method which, unlike the corresponding method commonly declared inside blocks, is responsible for inserting the content of standalone views inside the layout.
In the end, the layout code above and below the call to <%= yield %>
is rendered around every view associated to that layout.
Yet, if you inspect the default code generated by Rails you might not find how views are associated to layouts.
This happens because, by default, Rails associates every view with the application.html.erb
layout unless we specify other layouts explicitly:
class Admin::ArticlesController < Admin::ApplicationController
layout "admin"
end
The code above will look for the app/views/layouts/admin.html.erb
layout and render the article-related views for the admin panel using that.
We can define as many layouts as we want. This behavior is actually encouraged because, otherwise, we might be using unnecessary conditionals or loading assets we don't really need.
View helpers
Helpers, as they are called in Rails, are methods that are global and made available to views in general.
Some of these helpers are defined by Rails because of their usefulness for most Rails applications: methods like link_to
, image_tag
, button_tag
, form_with
or text_field_tag
are some examples of helpers defined by Rails.
However, we can add our helpers by adding a module in the helpers
folder and declaring a method in there:
module ArticlesHelper
def formatted_view_count(article)
"This article has been viewed #{article.view_count} times."
end
end
Partials
In Rails, partials are views that are not supposed to match to a controller action.
By convention, we also define them in the views
folder, and they differ from standalone views because their name start with an underscore: _sidebar.html.erb
.
They should be defined within the folder of the resource they belong to. For example, if we want to extract the table of contents from an article show.html.erb
view, we should define the _table_of_contents.html.erb
partial within app/views/articles
.
Partials are generally rendered within views:
<%# app/views/articles/show.html.erb %>
<%= render "table_of_contents", { article: @article } %>
This will render the partial for the table of contents, and it will make a local article
variable available inside the partial. We don't have to write the underscore because if we call the render
method inside views it means we're trying to render a partial.
Note that the to use the syntax above, the table_of_contents
partial has to be inside the folder for the current view.
If you prefer a more explicit approach, we can use named parameters and define the location of the partial
<%= render partial: "articles/table_of_contents", locals: { article: @article } %>
Another use for partials is to render collections by extracting the repeating part to a partial that can be reused:
<%# app/views/articles/index.html.erb %>
<% @articles.each do |article| %>
<%= render partial: "articles/article", locals: { article: @article } %>
<% end %>
Or the shorter and more implicit:
<%# app/views/articles/index.html.erb %>
<%= render @articles %>
Consider that Rails will infer the partial name from the model name, define the local article
variable and handle the iteration automatically.
How are views rendered in Rails
At first, a lot of what Rails does for us can feel like magic, but that's usually because we don't really understand how things are working behind the scenes.
How Rails renders views is not an exception to that rule, but the actual process is pretty straightforward once we get the hang of it.
- A request from a user arrives to our server, which passes it to our Rails application in order for it to process it.
- The router tries to match the request to a predefined endpoint that also corresponds to a specific controller and a given action.
- The controllers gets instantiated, and the action gets processed. This loads the view with the controller context, instance variables and other helpers, available to it.
- The view gets processed and inserted into the corresponding layout, which will generate the HTML that will actually be returned to the client.
Templating alternatives
Rails uses the ERB templating language by default, which allows us to insert Ruby inside an HTML-like format that ultimately produces HTML after evaluating the Ruby code.
Templating is a vital part of what makes Rails useful because it saves us time and avoids unnecessary duplication.
However, ERB is not the only way to render views in Rails, there are actually various alternatives that some people prefer like HAML or Slim which are more idiomatic and can feel more natural to some people.
Having said this, deviating from the Rails way is a bold choice that should be considered carefully because maintainability can become an issue, as well as onboarding new developers into our code base.
Besides ERB, there are two other templating alternatives in Rails which are jbuilder and builder which are capable of generating JSON and XML responses out of the box.
Apart from these alternatives, there are multiple serializer libraries that can help us generate responses in our desired format.
Modern alternatives
Because of the influence of front-end frameworks that use components to model parts of the view layer, solutions that implement this abstraction have come up in Ruby land.
The most notorious to this point is ViewComponent, created and used at GitHub, but other options like Phlex which takes an object-oriented approach to building views by allowing us to define the logic and templating in a single file.
Used in combination with modern CSS frameworks like Tailwind, these modern alternatives give us a way to build more maintainable front-ends without surrendering our front-end to frameworks like React, Vue or other SPA alternatives.
Alternatives like Nice Partials, built and used in the Bullet Train SaaS Template, are also a nice complement to use on top of views to produce a component centric view layer.
Besides these, newer alternatives like Inertia Rails or Superglue allow us to build frontends using an SPA framework without the need to build a separate API, but they are meant to replace Rails views with JS components, so their use is up to your liking of the language or its practices.
Rails views best practices
If we're not careful, views can quickly become a mess. Application logic, queries and potentially reusable code will creep into them unless we're vigilant about them.
So, to keep our code nice and maintainable, we should keep the following in mind:
- Simplicity: views should focus on presentation and not logic. We should extract as much logic as possible from views because interpreting it is definitely not easy.
- Minimize complexity: as long as you can, reduce the amount of complexity in your views. Reduce conditionals to the minimum and consider using partials, helpers, or view component solutions as longs as it's possible.
- Avoid database queries: avoid querying the database from views. Doing so is usually a way to worsen performance and making your application harder to maintain.
- Consider alternatives: replacing partials with view components or accessing complex view logic with presenters is typically a better alternative than having a lot of view logic in models.
- Avoid conditionals: a couple of conditional statements are alright, but having too much conditional logic inside views is often a recipe for disaster. Replace them with polymorphism or model logic.
Summary
Views are the presentation layer of the MVC pattern, which Rails implement, that is responsible for displaying data to our users and allowing interactions with our application.
By default, they are ERB (embedded Ruby) templates, but they can also represent JSON, XML or other formats.
They are located within the app/views
directory, and they follow naming conventions matching standalone controller actions.
Layouts are a special type of view that are supposed to encapsulate other views to avoid duplicating common elements like headers, footers or commonly shared assets.
To help us build better views, Rails provides globally defined helpers which can be built-in like the link_to
method or defined by ourselves to improve our ability to parse and read views.
The rendering process flows from user request through routing to controller action, which processes the view and inserts it into the layout. Beyond ERB, alternatives include HAML, Slim, and component-based approaches like ViewComponent.
As important as views are, we need to treat them as delicate part of our application to make it maintainable eventually, especially if we consider unforseen changes as a given.