Mastering Views in Ruby on Rails 7: DRY, Partials, Layouts, and Hotwire Stimulus Unleashed

Shah Nawas Khan
15 min readJun 19, 2023

The following lesson in this blog post is also available for FREE in the VS Code. Download the latest version of Code Zoomer from your VS Code Extension Marketplace.

Code Zoomer is a VS Code extension that can get you started quickly to learn a specific subject in coding with Rails. Here are some benefits of using the extension:

  • You don’t have to create the Ruby on Rails project from scratch.
  • Copy and paste the code snippet provided into the specified source file in the lessons with ease.
  • Run Terminal and Rails Console commands provided in the lessons with a click of the Run button on the lessons page.

You can certainly cover this same lesson quicker using Code Zoomer, otherwise, let's continue to the lesson below.

Overview

In Ruby on Rails, views are responsible for presenting data to users. Views are the components of the Model-View-Controller (MVC) architecture that handle the rendering of HTML templates and generate the user interface.

The web browser expects HTML when rendering a web page that you are building. No matter what format a framework is written in, in the end, it would ultimately be compiled into HTML to be sent to the web browser to render.

Views in Rails are typically written in Embedded Ruby (ERB) syntax, which allows embedding Ruby code within HTML templates. This allows for dynamic content generation and the integration of data from the Rails controller into the views. A web browser cannot render this ERB file, so Rails would help you to compile the ERB file to plain HTML to be sent to the web browser to render the page.

Views can access data through instance variables set in the corresponding controller actions. These instance variables are then used in the view templates to display dynamic content. Views can also use view helpers, which are pre-defined Ruby methods provided by Rails to simplify common view-related tasks like generating HTML tags or handling form inputs.

Rails views are organized in the app/views directory of a Rails application. Views are usually associated with specific controllers and actions, following a naming convention that matches the controller and action names to the view file names. The organization of views helps maintain a clear separation of concerns and allows for easy management and maintenance of the application’s user interface.

In this lesson, you will learn the details of working with Views in Ruby on Rails. We would revisit the lesson you have done in the previous project to create Restful User management pages.

Getting started

This lesson has a started repository that you can clone into your development machine and continue to learn these lessons. To clone the repository use the following command:

git clone  -b 104 --depth 1 https://github.com/shahnk19/my-rails-lesson.git

This command would clone the lesson repository at the correct `tag` to get you started in this particular lesson.

If you used the Code Zoomer extension in VS Code, you can clone and load the correct repository code with a click of a button for each lesson.

Next, you need to make sure this project is in a ready state before proceeding with the lesson. First, let’s check the state of the current database and its migrations using the command below:

rails db:migrate:status

This command should let you know that all pending migrations are already `up`. If any of the migrations are in a `down` status, or you get the `Schema migrations table does not exist yet.` message, run the following command:

rails db:migrate

Check the migration again with `rails db:migrate:status` command to confirm your local project’s database is in a ready state.

This lesson also has seed data to help you populate your development database with dummy data to help with testing. You can run the seeding with the following command:

rails db:seed

User records with random names are automatically generated in your database. You can see the seed script that creates these users in `db/seeds.rb`.

Partials

In the current lesson repository, you have a view page to create a new user shown below:

<h1>New User</h1>

<%= form_with model: @user do |form| %>
<div>
<%= form.label :name %><br>
<%= form.text_field :name %>
</div>

<div>
<%= form.label :email %><br>
<%= form.text_field :email %>
</div>

<div>
<%= form.label :admin %><br>
<%= form.check_box :admin %>
</div>

<div>
<%= form.submit %>
</div>
<% end %>

and another page to edit the user as shown below:

<h1>Edit User</h1>

<%= form_with model: @user do |form| %>
<div>
<%= form.label :name %><br />
<%= form.text_field :name %>
</div>

<div>
<%= form.label :email %><br />
<%= form.text_field :email %>
</div>

<div>
<%= form.label :admin %><br />
<%= form.check_box :admin %>
</div>

<div><%= form.submit %></div>
<% end %>

These two views are very similar, using the same `form` code and the only difference is the title of the page. Rails follows the DRY (Don’t Repeat Yourself) principle to maintain clean and maintain codes. Let's say you added a new field for the user in the `new` view, you have to repeat the same code again in the `edit` code which violates the DRY principle. You can solve this by using partials to share a common code section in a view.

First, let's move the repeating form's code into its own file called `_form.html.erb`.

<%= form_with model: @user do |form| %>
<div>
<%= form.label :name %><br />
<%= form.text_field :name %>
</div>

<div>
<%= form.label :email %><br />
<%= form.text_field :email %>
</div>

<div>
<%= form.label :admin %><br />
<%= form.check_box :admin %>
</div>

<div><%= form.submit %></div>
<% end %>

Notice how the partials filename has an underscore (`_`) at the beginning of the filename. This underscore is how all partials should be named in Rails to distinguish them from regular views.

Now that we separated the repeating forms code, let's update the `new` view as below:

<h1>New User</h1>

<%= render "form" %>

The form code is replaced with a single line to tell Rails view to `render` the partial called `form` in the same `users` folder. If you move the partial to a different folder, you should also update the code. For example, let's say the form is now in the path, i.e `app/views/shared/_form.html.erb`. Then your code to render the form should be `<%= render “shared/form” %>`.

Lets also update the `edit` page to render the same partial:

<h1>Edit User</h1>

<%= render "form" %>

Now, your code is clean, and the form code is not repeated. Any future changes you would need to do for the `User` fields would need to be updated in only one place.

Let's run your code now and see that the application still renders and works as expected. Run `rails server` and navigate to the `http://localhost:3000/users` URL in your browser to see the user listing in action. Click on the `New User` and `Edit User` on the page to test that the change you have applied is working as it used to be.

Layouts

Injecting layouts with the Provide and Yield function

The layout is another feature in Rails used to build a layout for all the views so that you don’t have to repeat the code in many files. For example, let’s say you want to add a common page title at the top of every page, you would have to add the title in every page repeatedly. Similarly, if you need to add a navigation on top of the page, or a sitemap at the footer of the page, you would have to repeat the same navigation and sitemap code on every page. This code repetition violates the DRY (Don’t Repeat Yourself) principle. Using a layout would solve this problem and keep your header, navigation, or sitemap code in one layout page and reuse it on every other page.

Let's first examine our current user pages. All the pages have a common title `MyRailsLesson`. You can see this title is displayed in the browser window when you navigate to any of the `users` pages. Each of the pages also has a page title specific to the page as shown in below screenshots:

First, we are going to change each of the pages using the embedded Ruby code to set the title of the page. For example in the `index.html.erb` page, change the line `<h1>Users</h1>` to `<% provide(:title, “Users”) %>`. In this new code, we are using the Rails special function `provide` that would help us set an instance variable `title` for the page with `Users`.

<% provide(:title, "Users") %>

<p>Total Users : <%= @users.count %></p>

<ul>
<% @users.each do |user| %>
<li><%= link_to user.name, user %></li>
<% end %>
</ul>

<%= link_to "New User", new_user_path %>

Now, open your web browser and refresh the user listing page `http://localhost:3000/users`, and notice that the page title is now removed. This is because we have removed the HTML that sets the page title `<h1>Users</h1>`. We need to add this code back to our page but only on the layout page so that all other pages can have this same title.

When you created a new Rails project, the default application layout is already created for you in `app/views/layouts/application.html.erb` shown below. This layout is used for every page served by your application. The HTML content of the page is inserted by Rails into the layout where the `yield` function is called. In your current code, the `<%= yield %>` function is inside the `<body><%= yield %></body>` tag, so every page’s HTML content would be inserted into this layout page in the `body` tag and served to the web browser.

<!DOCTYPE html>
<html>
<head>
<title>MyRailsLesson</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

<body>
<%= yield %>
</body>
</html>

Also, notice in this layout file, it already has the code to set the browser window title using `<title>MyRailsLesson</title>` in the `head` section. First, we are going to change this title to display `Users | MyRailsLesson` where the `Users` part is the page title we set previously in the view with `<% provide(:title, “Users”) %>`.

You also need to add this line `<h1><%= yield(:title) %></h1>` after the `<body>` tag. This line would add the page title at the top of the page. Notice, here you need to also use the `yield` function with an additional parameter to the function to indicate which data you need to be yielded into that line. So, `yield(:title)` tells Rails to inject the value of `title` variable we set in each of the view pages into this line in the layout.

Here is the code to make these two changes:

<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | MyRailsLesson</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

<body>
<h1><%= yield(:title) %></h1>
<%= yield %>
</body>
</html>

Now, refresh the page `http://localhost:3000/users` again and see the difference. Your browser window title should have changed to `Users | MyRailsLesson` and the page title to `Users`.

Your layout page now is ready, what you need to do next is update each of the other pages to remove the hardcoded page title `<h1>…</h1>`, and replace it with `<% provide(:title, “…”) %>`.

Here are the sample codes for each of the pages:

<% provide(:title, "New User") %>

<%= render "form" %>
<% provide(:title, "New User") %>

<%= render "form" %>
<% provide(:title, "Edit User") %>

<%= render "form" %>

On the `show` page, the page title has a greeting text `Hello` followed by the current selected user’s name. So you have to concatenate the string “Hello” with the variable `user.name` using `Hello #{@user.name}` where anything inside the token `#{…}` in the Ruby string would be converted and concatenated with the parent string.

  <% provide(:title, "Hello #{@user.name}") %>

<p>Email : <%= @user.email %></p>
<p>Admin: <%= @user.admin ? 'true' : 'false' %></p>


<%= link_to "Edit User", edit_user_path(@user) %>,
<%= link_to "Delete User", user_path(@user), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>

Now, you can go back and test each of these pages, and see for yourself the page title has not changed, but the code is unified under the application layout. Next time you want to change the page title, you only need to make the changes in the application layout file `application.html.erb`.

Asset tags

Custom CSS Stylesheets

Asset tags are a set of helper functions that are available to generate the HTML to load Javascript, stylesheets, images, videos, and audio. Here is a list of asset tags that are available in Rails:

Building a modern web application is not enough to only serve HTML, you would also need to use Javascript and CSS to transform your webpage to look good, and to provide a user-friendly and responsive interface. CSS is how to transform the look and feel of the web page and Javascript would transform the behavior of your HTML pages.

Rails by default already enabled the Asset Pipeline when you created the Rails project. In summary, the Asset Pipeline lets your Rails application manage its assets such as Javascript and CSS into one unified JS and CSS file. It also ensures the file is properly minified and compressed for better performance when rendering in a web browser. In web applications, less number of files and smaller file sizes loaded by the browser would speed up your website.

When you are creating your application assets, you can put those assets in any of these folders:

  • app/assets is for assets that are owned by the application, such as custom images, JavaScript files, or stylesheets.
  • lib/assets is for your own libraries’ code that doesn’t really fit into the scope of the application or those libraries which are shared across applications.
  • vendor/assets is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks.

First, let’s look at how to add CSS using the Asset Pipeline. By default, this is already setup for you in the layout file `application.html.erb` as shown below:

<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | MyRailsLesson</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

<body>
<h1><%= yield(:title) %></h1>
<%= yield %>
</body>
</html>

The `stylesheet_link_tag` helper function is used to add a stylesheet `link` element to the HTML page. For example `<%= stylesheet_link_tag “application” %>` would generate the following HTML code `<link href=”/assets/application.css” rel=”stylesheet” />`. Rails Asset Pipeline would load any CSS files from any of the following folders:

  • app/assets/stylesheets
  • lib/assets/stylesheets
  • vendor/assets/stylesheets

In your current project, there is already a CSS file generated for you in `app/assets/stylesheets/application.css`. Any other CSS files you create under any of the above folders would be concatenated into a single file in `application.css` and included in the layout page.

Let's create a CSS file that is custom for your application in `app/assets/stylesheets`.

  body {
padding-top: 55px;
background-color: rgb(250, 235, 215);
}

Now, go back to any of the application pages and refresh the browser to see that the stylesheet we added is already affecting the pages by changing the entire page’s background color. You should see that just adding a CSS file into a specific folder is enough to apply it to your whole application.

Custom Javascript with Hotwire

Rails 7 has introduced Hotwire which enables you to add your javascript codes to work with your HTML pages rendered on the server side. This means you need to learn the basics of Hotwire Stimulus to enable you the ability add Javascript codes to your application.

In this section, you are going to learn how to add a custom Javascript code to your application using Hotwire Stimulus. We are going to add avatars to each user on the `show` page using an API call to a free online service that generates an avatar called Dicebear.

Your Rails application already has the necessary codes to add custom Javascript. Open the Javascript file called `hello_controller.js` as shown below:

  import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
connect() {
this.element.textContent = "Hello World!"
}
}

This file is already included to show you how to insert Javascript into your application. The function `connect` for this `Controller` takes the current element and inserts the text content of the element with `Hello World!`. The current element is the HTML element on your page that is connected to this `Controller`. Let’s connect an element to this controller now. Open the `show.html.erb` and add `<div data-controller=”hello”></div>` as shown below:

<% provide(:title, "Hello #{@user.name}") %>

<div data-controller="hello"></div>

<p>Email : <%= @user.email %></p>
<p>Admin: <%= @user.admin ? 'true' : 'false' %></p>

<%= link_to "Edit User", edit_user_path(@user) %>,
<%= link_to "Delete User", user_path(@user), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>

Now, go to the browser and click on any of the users in your `http://localhost:3000/users` page to navigate to the user’s `show` page. You should now see the text `Hello World!` appears on the page just before the user’s name.

The Hotwire Stimulus library has connected the HTML element with the `data-controller` attribute to the corresponding controller in `app/javascript/controllers`. Since we set the value of the attribute to `hello`, then it is connected with the Javascript code in the `hello_controller.js`.

The `hello` controller is just for demo and doesn’t do much of what we need. Let's add a new JS controller called `avatar_controller.js` that would fetch an avatar image using HTTP API to Dicebear. This avatar image would then be inserted into your user’s `show` page. Calling the Dicebear API using this URL `https://api.dicebear.com/6.x/bottts-neutral/svg?seed=maxpayne` would give a randomly generated avatar image for a user with the name `maxpayne`.

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static values = { userName: String };

connect() {
this.load();
}

load() {
console.log(this.userNameValue);
fetch(
`https://api.dicebear.com/6.x/bottts-neutral/svg?seed=${this.userNameValue}`
)
.then((response) => response.text())
.then((html) => (this.element.innerHTML = html));
}
}

This new Javascript would first fetch an avatar image using the `fetch` function which is a common API caller function used in Javascript. Instead of hardcoding the user’s name in the URL’s query params, i.e. `seed=maxpayne`, we are taking the user’s name value from `this.userNameValue` and concatenating with the URL. This would make sure every API call to fetch the avatar would be unique for each user. Finally, the response from the API is an SVG image which we then insert into the HTML element with the code `this.element.innerHTML = html`.

Now, we would also need to change the `show.html.erb` so that it connects the HTML element for the avatar in the page with the `avatar_controller.js` controller. Here is the code to connect the controller to the element on this page that would display the avatar fetched:

<div data-controller="avatar" data-avatar-user-name-value="<%= @user.name %>" class="avatar"></div>

Notice, we only changed the line that would insert the HTML element for the avatar. First, we change the `data-controller` to point to `avatar` so that it connects with the correct `avatar_controller.js` controller. The other change is to add another attribute called `data-avatar-user-name-value`. This attribute would represent the value of the `this.userNameValue` variable in the `avatar_controller.js` controller.

The naming of this attribute has to be specific. It begins with `data` followed by the controller name `avatar` then followed by the variable name `user-name` and ends with `value`. We can get the user’s name from the Ruby instance variable in the view called `@user.name`, so we inject that value into the HTML elements attribute.

Finally, we need some styling to make sure your avatar is displayed nicely on the page. To do this, we added the `class=”avatar”` to the HTML element. Next, add the following CSS code to the `custom.css` stylesheet you created in the previous section.

.avatar {
width: 120px;
}

You are now ready to test the new avatar for the users. Go to the browser and click on any of the users in your `http://localhost:3000/users` page to navigate to the user’s `show` page. You should now see an avatar appears on the page just before the user’s name. Try clicking on different users and see if it should give a different avatar. Each user would get a consistent avatar each time because Dicebear provides this awesome feature to seed the API. This means each time the same seed is used, you would get the same avatar response from Dicebear API. This is a great feature because we are adding the avatar to the edit page too, so when we click `Edit User` for a particular user, the avatar displayed should be the same as the `show` page.

Let's see how to add the same avatar feature to another page and how to reuse the `avatar_controller.js` on other pages. First, open the `app/views/users/edit.html.erb` file and make the changes as shown below:

<% provide(:title, "Edit User") %>

<div data-controller="avatar" data-avatar-user-name-value="<%= @user.name %>" class="avatar"></div>

<%= render "form" %>

Now in your browser, open any user’s page and take note of their avatar. Then click `Edit User` link to go to the user’s edit page you just changed. The same avatar should appear on this page too. Now, the same Javascript controller you created for the `show` page is being reused in the edit page. This is to show that you can reuse the controller you created on any page as you wish.

Conclusion

In this lesson, we covered detailed lessons on how to work with a view to building a modern web application that uses CSS and Javascript. You learned how to apply the DRY principle to make sure you are not repeating the same codes using Partials and Layouts. You also learned how to add custom CSS to change how the web page looks. Finally, you learned how to add custom Javascript using Hotwire to change the behavior of the page.

If you like this lesson, there are more lessons like this in the Code Zoomer VS Code extension where you can work in simple and easy-to-follow tutorials for a specific subject building real-life projects.

--

--

Shah Nawas Khan

I am a computer programmer, loves to learn and teach. I created Code Dryer to help developer save time from doing boring stuff. https://www.codedryer.com/