Turbo Rails was released in December 2020, the first line of the README was: “Turbo gives you the speed of a single-page web application without having to write any JavaScript”. I was blown away by how easy it was to work with. And the best part? No more React, no more Redux, no more Formik! Instead, I could work with the tools I love and know well: Ruby on Rails and Simple Form. Boring? Yes, but I could benefit from React with a tenth of the effort. Turbo and its integration for Ruby on Rails are brand-new tools with impressive features.
All clicks on links and form submissions are now AJAX requests, which speeds up our applications thanks to Turbo Drive. We get this benefit for free as it does not require any work; we only have to import the library.
Second, it is now effortless, with just a few lines of code to build dynamic applications by slicing pages into different pieces with Turbo Frames. We develop our CRUD controllers just like before, and just by adding a few lines of code, we can replace or lazy-load independent parts of the page!
Third, it becomes trivial to add real-time features with the help of Turbo Streams. Want to add real-time notifications to your application, build a real-time multiplayer game, or a real-time bug monitoring system? The real-time part is just a few lines of code with Turbo!
Turbo speeds up Rails applications reduces the amount of Javascript we have to write, and makes it easy to work with real-time features
Understanding what Turbo Drive is
Turbo Drive is the first part of Turbo, which gets installed by default in Rails 7 applications, as we can see in our Gemfile and our JavaScript manifest file application.js:
With Turbo Drive, our Ruby on Rails applications will be fast by default because the HTML page we first visit won’t be completely refreshed. When Turbo Drive intercepts a link click or a form submission, the response to the AJAX request will only serve to replace the
of the HTML page. In most cases, the of the current HTML page won’t change, resulting in a considerable performance improvement: the requests to download the fonts, CSS, and JavaScript files will only be made once when we first access the website.
Disabling Turbo Drive
We may want to disable Turbo Drive for certain link clicks or form submissions in some cases. For example, this can be the case when working with gems that don’t support Turbo Drive yet.
To disable Turbo Drive on a link or a form, we need to add the data-turbo=”false” data attribute on it.
On the Articles#index page, let’s disable Turbo Drive on the “New article” link:
Now let’s refresh the page and click on the “Create article” link. We will notice the unpleasant white “blink” of the full HTML page refresh. If we perform this action again with the dev tools opened on the “Network” tab, we will notice that the browser makes four requests to the server:
One HTML request to load the Quotes#new HTML page
One request to load the CSS bundle
One request to load the JavaScript bundle
One request to load the favicon of the page
Now let’s add Turbo Drive back by removing the data-turbo=”false” from the “New quote” link, refresh the page, and experiment again. We won’t see the unpleasant white “blink” because the browser does not fully reload the page. Under the hood, Turbo Drive converts the click into an AJAX request and swaps the
of the page with the response’s .
What are Turbo Frames?
Turbo Frames are independent pieces of a web page that can be appended, prepended, replaced, or removed without a complete page refresh and writing a single line of JavaScript!
Let’s create our first Turbo Frame. To create Turbo Frames, we use the turbo_frame_tag helper. Let’s wrap the content on the Articles#index page in a Turbo Frame with an id of “new_form”:
This HTML tag does not exist in the HTML language. It is a custom element that was created in the Turbo JavaScript library. It intercepts form submissions and clicks on links within the frame, making those frames independent pieces of your web page!
Now let’s click on the “Create article” button and… The frame disappears from the page, and there is an error in the console: Response has no matching element. Let’s explain this strange behavior.
When clicking on a link within a Turbo Frame, Turbo expects a frame of the same ID on the target page. It will then replace the Frame’s content on the source page with the Frame’s content on the target page otherwise will give a content missing error.
To make this work we will add a turbo frame tag with the same ID on the target page as well
See it worked as expected, When clicking on a link within a Turbo Frame, if there is a frame with the same ID on the target page, Turbo will replace the content of the Turbo Frame of the source page with the content of the Turbo Frame of the target page.
When clicking on a link within a Turbo Frame, if there is no Turbo Frame with the same ID on the target page, Turbo will remove the content of the Turbo Frame from the source page and log an error.
A link can target another frame than the one it is directly nested in thanks to the data-turbo-frame data attribute.
A link can target a Turbo Frame it is not directly nested in, thanks to the data-turbo-frame data attribute. In that case, the Turbo Frame with the same id as the data-turbo-frame data attribute on the source page will be replaced by the Turbo Frame with the same id as the data-turbo-frame data attribute on the target page.
There is a special frame called _top that represents the whole page. It’s not really a Turbo Frame, but it behaves almost like one, so we will make this approximation for our mental model.
For example, if we wanted our “Create article” button to replace the whole page, we could use data-turbo-frame=”_top” like this:
Of course, every page has the “_top” frame by default. When using the “_top” keyword, the URL of the page changes to the URL of the target page, which is another difference from when using a regular Turbo Frame.
As we can notice, Turbo Frames are a significant addition to our toolbox as Ruby on Rails developers. They enable us to slice up pages in independent contexts without writing any custom JavaScript.
Editing quotes with Turbo Frames
The first thing we need to achieve is that when clicking on the “Edit” button of an article on the Articles#index page, the card containing the article will be replaced by a card containing the edition form. Replacing pieces of a web page is precisely the kind of job Turbo Frames can do for us! But what ID should we give our Turbo Frames?
On the Articles#index page, each Turbo Frame around each quote card should have a unique ID. A good convention is to use the singular name of the model followed by the id of the quote.
With our Turbo Frames appropriately named, when clicking on the “Edit” button of the article on the Articles#index page, the content of the Turbo Frame containing the form should replace the content of the Turbo Frame containing the second article.
The Turbo Stream format
Forms in Rails 7 are now submitted with the TURBO_STREAM format. Let’s destroy a quote and inspect what happens in the log of our Rails server:
As we can see, the ArticlesController will process the #destroy action with the TURBO_STREAM format. Let’s explore what we can do with this format by making our destroy action only remove the Turbo Frame containing the deleted quote while leaving the rest of the page untouched.
In the controller, let’s support both the HTML and the TURBO_STREAM formats thanks to the respond_to method:
As with any other format, let’s create the corresponding view:
When the browser receives this HTML, Turbo will know how to interpret it. It will perform the desired action on the Turbo Frame with the ID specified by the target attribute. In our case, Turbo removes the Turbo Frame corresponding to the deleted quote leaving the rest of the page untouched. That’s exactly what we wanted!
Note: As of writing this chapter, the turbo_stream helper responds to the following methods so that it can perform the following actions:
With the combination of Turbo Frames and the new TURBO_STREAM format, we will be able to perform precise operations on pieces of our web pages without having to write a single line of JavaScript, therefore preserving the state of our web pages.