Playing with Ruby on Rails + Hotwire creating a simple app

 Gabriel Rodríguez
Gabriel Rodríguez
December 19, 2023
Ruby on Rails
Hotwire
Playing with Ruby on Rails + Hotwire creating a simple app

In this article, I’ll show how to build a simple grocery list using Ruby on Rails 7 and Hotwire. The idea of this article is to explain as simply as possible how Hotwire could help you to create a server-rendered web with good performance without introducing the complexity of a JavaScript framework like React or Angular. 


1 - First, I want to start with a brief introduction about what is Hotwire

In the dynamic realm of web development, Hotwire challenges the reliance on extensive JavaScript, presenting an alternative approach that favors HTML over JSON for building modern web applications. This shift ensures rapid initial page loads, maintains server-based template rendering, and facilitates a streamlined, productive development experience across programming languages, all without sacrificing the speed associated with traditional single-page applications. The team behind this technology has introduced three frameworks: Turbo, Stimulus, and Strada; aimed at delivering comprehensive solutions across all platforms.

Turbo: Powerhouse of Page Dynamics

At the core of Hotwire lies Turbo, a dynamic framework that redefines web application functionality. Turbo accelerates page changes and form submissions, divides complex pages into manageable components, and streams partial updates seamlessly over WebSocket—all without writing a single line of JavaScript.

Turbo introduces key features:

Turbo Drive: Accelerates links and form submissions, eliminating the need for full page reloads.

Turbo Frames: Decomposes pages into independent contexts, allowing scoped navigation and enabling lazy loading.

Turbo Streams: Delivers page changes over WebSocket, SSE, or in response to form submissions using HTML and a set of CRUD-like actions.

Turbo Native: Empowers your monolithic application to form the core of native iOS and Android apps, ensuring seamless transitions between web and native sections—all achieved through the transmission of HTML over the wire.

Stimulus: Enhancing Interactivity

Complementing Turbo, Stimulus handles the remaining 20% of interactivity traditionally requiring JavaScript. With an HTML-centric approach to state and wiring, Stimulus makes it easy to incorporate custom code for specific cases where Turbo may need augmentation. This collaboration allows developers to strike a balance between the convenience of Turbo and the flexibility of custom code.

Strada: Bridging Web-Native Communication

Strada standardizes communication between web and native components in mobile hybrid applications through HTML bridge attributes. This standardization facilitates progressive enhancement of web interactions with native replacements, offering a structured approach to building cohesive mobile applications. Strada plays a pivotal role in ensuring seamless integration and communication between the web and native parts of an application.

2 - Starting with a Grocery List project  

The grocery list is the project that I create each time I want to learn a new framework or library. It is a simple project that involves two entities: a grocery list and items. The idea is to create a weekly grocery list, and then we can add items to this list where each list has a name and status, and items with a name and status belong to the list. With this simple project, we will be able to create, read, update, and delete data, and create the backend, frontend, and a database.

I want to start with a new rails app from scratch using rails 7.+ and I want to include Tailwind CSS to style this app. Tailwind is not needed to create an application using Hotwire but I love using Tailwind because I think it is simple and elegant. If you haven’t worked with Tailwind yet, I highly recommend it to you.  

Well, let's start coding and having fun. If you haven’t installed Rails yet I recommend you to start with getting started with Rails.


Run the Foreman script by running the  “bin/dev” command that will launch the rails server and the Tailwind CLI to watch for changes in your HTML or ERM files and generate the CSS

and then open this URL in your browser http://localhost:3000/

You should get the following screen. 

Creating resources

Now, I’m going to create the resources for shopping_list and shopping_item. Rails provides an easy way to generate a rails scaffold, generating everything for us, like routes, controllers, views, styles, etc. But in this case we don’t need all this stuff and in order to learn how to use Hotwire we are going to create only some resources manually to understand each step. So, first we are going to create the resources for the entity “list”, which has the “title” attribute running the following command and then we are going to create the entity “shopping_item” with the attributes title, bought and a foreign key to the shopping list.
 

This is going to create the model shopping_item with a foraging key to shopping_list and it is going to generate these files and folders for us:


db/migrate/20231122124700_create_shopping_lists.rb

app/models/shopping_list.rbtest/models/shopping_list_test.rbtest/fixtures/shopping_lists.yml

app/controllers/shopping_lists_controller.rb

app/views/shopping_liststest/controllers/shopping_lists_controller_test.rb

app/helpers/shopping_lists_helper.rb

db/migrate/20231122132704_create_shopping_items.rbapp/models/shopping_item.rb

test/models/shopping_item_test.rbtest/fixtures/shopping_items.yml

app/controllers/shopping_items_controller.rb

app/views/shopping_itemstest/controllers/shopping_items_controller_test.rb

app/helpers/shopping_items_helper.rb

Also, it is going to add the routes for the resource “shopping_lists”, now we are going to change the “routes.rb” file to change the home page to our “shopping_lists” instead of the Rails Welcome page.

Now we have to execute the migration with the following command

bin/rails db:migrate

And then we have to create our new index page, first adding the action to the controller and then the view. In this step I’m going to create the form to create a new shopping_list and the view.

# app/controllers/shopping_lists_controller.rb

# create file app/views/shopping_lists/_form.html.erb
# create file app/views/shopping_lists/_shopping_list.html.erb
# create file app/views/shopping_lists/index.html.erb

Your page should look like this

Now we are able to create our first shopping list by adding a text in the text input and clicking on the save button, but where does Hotwire work?. If you look closely the browser is not reloading the page when we add a new shopping list. The Turbo Drive framework which is part of Hotwire is making our application faster.

I want to stop here for a moment and explain to you how Turbo Drive is making our application faster.

Turbo Drive takes control of clicks on links within the same website. When you click a link or submit a form, here's what Turbo Drive does:

  • Stop the browser from following the link.
  • Updates the browser's URL using the History API.
  • Sends a request for the new page through a fetch request.
  • Displays the new page by replacing the current <body> element with the response and combining the content of the <head> element.

The JavaScript window and document objects, as well as the <html> element, stay the same between renderings.

This process also applies to HTML forms. Turbo Drive transforms form submissions into fetch requests, follows the redirect, and shows the HTML response. This means your browser doesn't need to reload, making the app seem faster.

To observe how the app behaves without Hotwire, turn off the Turbo Drive framework by adding the following line in the app/javascript/application.js file:

Turbo.session.drive = false

Now, when you attempt to add a task, you'll see that the browser performs a full reload like traditional web applications. Turn Turbo Drive back on, and refresh the page. Try adding a new task, and you'll notice that the page doesn't reload.

Introduction to Turbo Frames

Turbo Frames track links and forms within their designated section. When the server responds, only that chosen frame gets a makeover, seamlessly replacing the old content. No fuss, no muss, no JavaScript code written for the developer.

How does it work? Wrap a chunk of your page in a <turbo-frame> element, give it a unique ID, and voila! That ID becomes the magic key linking your content to the server's response. It's like having a backstage pass to update just what you need.

And guess what? You're not limited to one frame. Your page can rock multiple frames, each doing its thing in its own little world. This means more control, less chaos, and a smoother development ride. Well, they're also about simplifying the developer's dance. With Turbo Frames, your web content gets a turbo boost towards a more efficient and interactive future. 🚀

Now we are going to create our first turbo-frame. My goal is only to reload the list of shopping_list when we create a new one. We are going to do this by modifying the file “app/views/shopping_lists/index.html.erb” by adding two turbo-frames.

#app/views/shopping_lists/index.html.erb


The first turbo-frame is going to wrap the form intersecting the request, we use the attribute “target” to let turbo know where to paste the request returned from the form. Then we are going to add a second turbo-frame with an attribute ID that must be the same string used as a target in the previous turbo-frame.

Now we have to modify the response of the action “create” in the file “shopping_list_controller.rb” to return only the new list, to do this we are going to create a new file “app/views/shopping_lists/show_shopping_list.html.erb”


#app/views/shopping_lists/show_shopping_list.html.erb


 

The new list returned after submitting the form must be wrapped by a turbo-frame with the same ID used as the attribute "target" in the turbo-frame used in the form. Now we are going to modify the action “create” in the shopping list controller.


#app/controllers/shopping_lists_controller.rb


And now you can see the magic of the turbo-frame working. If you check the tab "network" in the developers tools of the browser you will see that a request for the form is made without reloading the browser and it only gets the information needed to see the new elements of the list. But the submit is not clearing the information off the form, and the “Save” button is not being disabled when the request is processed, so we are going to use some utilities provided by Hotwire to improve this form a little. 

First, we are going to block the “save” button while RoR is processing the request. Turbo provides us with attributes that we can add to our HTML elements that make this kind of thing for us easier. In this case, we are going to add the attribute “data-turbo-submits-with” to the button with the value “Creating…” and that is going to do the work for us. If you want to see how to use more data-turbo attributes, check the official documentation https://turbo.hotwired.dev/reference/attributes

To clear the form after submitting we have to write a little js code. We are going to create a form_controller.js “app/javascript/controllers/form_controller.js” with the action “clear”


#app/javascript/controllers/form_controller.js



Then we have to add this action to the form by adding to it the attributes “data-controller” with the name of the controller and the “data-action” with the event that triggers -> action to execute. IE:

<form class="mb-7" data-controller="form" data-action="turbo:submit-end->form#clear" action="/shopping_lists" accept-charset="UTF-8" method="post">


to learn more about turbo events check the official documentation https://turbo.hotwired.dev/reference/events

#app/views/shopping_lists/_form.html.erb


Now, when I click on shopping_list I want to show a form where I can add items to this list, all on the same page and without reloading the whole page.

First, we are going to allow the shopping_list to create the shopping_items by using “accepts_nested_attributes_for”


#app/models/shopping_list.rb


then we are going the enable the routes for this

#config/routes.rb

The next step is to create the action and the view to show the shopping_item form. 

First, we add the action “new” to the shopping_items controller and then we create the form.

# app/controllers/shopping_items_controller.rb

Now we are going to create the form for the shopping_items by creating the file “app/views/shopping_items/new.html.erb” I’ve added, at the bottom to the file, the list of shopping_items so I’m going to create the file “app/views/shopping_items/_shopping_item.html.erb” to show the list too.

#app/views/shopping_items/new.html.erb
#app/views/shopping_items/_shopping_item.html.erb 

Notice that the view “new” is wrapped by a turbo-frame with the id  “turbo-shopping-item-index”, so now if we want to show this new form we can do it easily by adding:

  • a turbo-frame in the index file “app/views/shopping_lists/index.html.erb” where we want to show the form
  • then adding a link to each element of the list shopping_list wrapped with a turbo-frame with the target “turbo-shopping-item-index”

#app/views/shopping_lists/index.html.erb


#app/views/shopping_lists/_shopping_list.html.erb



Now when I click on one element of the shopping_list I can see the shopping_item form, then I have to create the action “update”  in the file “app/controllers/shopping_lists_controller.rb” to allow a shopping_list to create its shopping_items

#app/controllers/shopping_lists_controller.rb




And when I create a new shopping_item the page only reloads the HTML code wrapped by the turbo-frame with id “turbo-shopping-item-index”. This is because the action “update” redirects to “new_shopping_item_path” and this page is wrapped by a turbo-frame with id “turbo-shopping-item-index”, so Hotwire knows that this part of the page must be replaced by the new HTML code returned by the form.

Introduction to Stimulus 

Now I want to introduce a new feature to this application. I want to add a checkbox to each shopping_item so I can mark the item as bought. To do this I’m going to use Stimulus js but first I’m going to create the route and the action in the controller.

#config/routes.rb

and 


#app/controllers/shopping_items_controller.rb

Then we have to add the checkbox to the list of shopping_items, Stimulus needs to know the controller name and the action to execute in the controller and if we want to subscribe to a specific event to the HTML element, to do this we have to add some attributes to HTML tag:
data-controller=”controller-name” and data-action=”controller-name#action”, if we want to subscribe to a specific event we can do something data-action=”event->controller-name#action” ie:
data-action="change->controller-name#action"


#app/views/shopping_items/_shopping_item.html.erb


Finally, we need the Stimulus controller for the shopping_item, we can generate the file manually as we made it with the form_controller.js in the folder “app/javascript/controllers” or we can use the rails generator running the following command.

 bin/rails generate stimulus shopping_item

In the file generated “app/javascript/controllers/shopping_item_controller.js” we have to add our JavaScript code, where we want to send a Post message to this URL “`/shopping_lists/${shoppingListId}/shopping_items/${id}/toggle_bought`”. To do this we are going to use the library fetch. For further information about fetch check the official documentation https://www.w3schools.com/jsref/api_fetch.asp. 

To generate the URL we need to extract the id of the shopping_item and the id of the shopping_list, it is easy because we have added this information as attribute data in the checkbox HTML tag, so we can get it from the event object that the function “toggle_bought” receives as the first parameter, then we need the “csrfToken” to send it in the header of the request, we can get this from the tag “meta” with the name “csrf-token”, and with that, we can create the following code:

app/javascript/controllers/shopping_item_controller.js


With that, we have a brief introduction about how to use the tools Turbo Drive, Turbo Frames, and Stimulus. If you want to keep practicing I invite you to add some new features to this application like:

- adding an edit button to shopping_list, When I click on the edit button the app should show the form to edit the shopping_list in the place where we show the list item. The form has to have a save button and a cancel button, if I click on it I should show the list again. 

- adding a delete button to shopping_list

I hope that you enjoyed and learned from this short article.



#config/routes.rb 

and 

#app/controllers/shopping_items_controller.rb

Then we have to add the checkbox to the list of shopping_items, Stimulus needs to know the controller name and the action to execute in the controller and if we want to subscribe to a specific event to the HTML element, to do this we have to add some attributes to HTML tag:
data-controller=”controller-name” and data-action=”controller-name#action”, if we want to subscribe to a specific event we can do something data-action=”event->controller-name#action” ie:
data-action="change->controller-name#action"


#app/views/shopping_items/_shopping_item.html.erb


Finally, we need the Stimulus controller for the shopping_item, we can generate the file manually as we made it with the form_controller.js in the folder “app/javascript/controllers” or we can use the rails generator running the following command.

 bin/rails generate stimulus shopping_item

In the file generated “app/javascript/controllers/shopping_item_controller.js” we have to add our JavaScript code, where we want to send a Post message to this URL “`/shopping_lists/${shoppingListId}/shopping_items/${id}/toggle_bought`”. To do this we are going to use the library fetch. For further information about fetch check the official documentation https://www.w3schools.com/jsref/api_fetch.asp. 

To generate the URL we need to extract the id of the shopping_item and the id of the shopping_list, it is easy because we have added this information as attribute data in the checkbox HTML tag, so we can get it from the event object that the function “toggle_bought” receives as the first parameter, then we need the “csrfToken” to send it in the header of the request, we can get this from the tag “meta” with the name “csrf-token”, and with that, we can create the following code:

// app/javascript/controllers/shopping_item_controller.js

With that, we have a brief introduction on how to use the tools Turbo Drive, Turbo Frames, and Stimulus. If you want to keep practicing I invite you to add some new features to this application like:

- adding an edit button to shopping_list. When you click on the edit button the app should show the form to edit the shopping_list in the place where we show the list item. The form has to have a save button and a cancel button, if you click on it, it should show the list again. 

- adding a delete button to shopping_list

I hope that you enjoyed and learned from this short article, if you need code references check the git repository.

Don't miss a thing, subscribe to our monthly Newsletter!

Thanks for subscribing!
Oops! Something went wrong while submitting the form.

Adding fonts to asset pipeline

A simple step by step tutorial on how to add fonts to asset pipeline in Ruby on Rails.

March 15, 2020
Read more ->
Integration
Ruby
Ruby on Rails
Rails

Contact

Ready to get started?
Use the form or give us a call to meet our team and discuss your project and business goals.
We can’t wait to meet you!

Write to us!
info@vairix.com

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.