The magic of Turbo Drive is that you don't even notice it. It's just there, in the background, making your navigation faster.
In this section, we'll make the counter work by updating the count when you press the buttons.
We're going to use the buttons (embedded inside forms) to increment and decrement the counter. Whenever the user presses the buttons, it will submit the form to the backend, which will update the habit count, and redirect to the updated habit.
As mentioned earlier, Turbo Drive will follow the redirect and update the whole body with the new response, all without reloading the browser.
Enough talking. Let's write some code.
Step 1: Create Routes to Update the Counter
Let's create two new routes to handle the requests to increment and decrement the counter.
Rails.application.routes.draw do
resources :habits, only: [:show] do
member do
post :plus
post :minus
end
end
end
If you haven't seen the above syntax with member
before, learn more about them here: Custom, Non-Resourceful Routes in Rails
The above route configuration will generate the following routes:
$ daily-habits git:(main) ✗ bin/rails routes -g habit
Prefix Verb URI Pattern Controller#Action
plus_habit POST /habits/:id/plus(.:format) habits#plus
minus_habit POST /habits/:id/minus(.:format) habits#minus
habit GET /habits/:id(.:format) habits#show
Since the prefix for the routes is plus_habit
and minus_habit
, we can generate the corresponding URLs with plus_habit_path
and minus_habit_path
, respectively.
Now that we have the routes for the button clicks, let's add the actions to update the habits.
Step 2: Add Controller Actions to Handle Requests
Let's add two actions named plus
and minus
to increment and decrement the habit count. Note that I'm using the before_action
callback to set the habit before all actions. This eliminates the duplicated code to set the habit.
class HabitsController < ApplicationController
before_action :set_habit
def show
end
def plus
@habit.update(count: @habit.count + 1)
redirect_to @habit
end
def minus
@habit.update(count: @habit.count - 1)
redirect_to @habit
end
private
def set_habit
@habit = Habit.find_by(id: params['id'])
end
end
After updating the habit, we're redirecting the user to the habit, which renders the show
action. It will fetch and render the updated habit. Makes sense?
Alright, we're getting closer to the working implementation. Let's update the view to use the URLs for the above routes.
Step 3: Update View to Make Requests
We're going to replace the plain button tags with the button_to
helper method provided by Rails. In addition to making the code expressive, it also creates a form that is submitted to the given URL. Nothing else needs to change.
<div class="text-center font-bold text-gray-700" id="habit-name">
<%= @habit.name %>
</div>
<div class="mt-3 flex justify-center items-center space-x-5">
<%= button_to '-', minus_habit_path(@habit), class: 'btn bg-red-300 inline-block shadow-lg' %>
<div class="text-4xl font-bold"><%= @habit.count %></div>
<%= button_to '+', plus_habit_path(@habit), class: 'btn bg-green-300 inline-block shadow-lg' %>
</div>
<div class="mt-3 p-2 flex justify-center space-x-1">
<% @habit.count.times do %>
<div class="inline-block border p-1 bg-green-400"></div>
<% end %>
</div>
As you can see above, whenever the user clicks the minus button, it makes an HTTP POST request to the minus_habit_path(@habit)
, which returns /habits/1/minus
URL.
Upon receiving the request on the above endpoint, the minus
controller action will decrement the habit count by one and redirect to the show_habit_path(@habit)
, which returns habits/1
.
Finally, Turbo Drive will follow the redirect request by making the fetch
request to habits/1
. Upon receiving the request, the show
controller action will fetch the updated habit from the database and render it.
So far, this is pretty much standard Rails stuff. What's cool about Turbo Drive is that at no point the browser is fully reloading, like it would in a typical web application that's not using any SPA framework or making explicit AJAX requests.
The form submissions (and link clicks) are handled by the Turbo library. It also makes the subsequent redirect, and updates the body with the resulting response.
And we are done! The counter component should be fully functioning now. Reload the browser and click the buttons a few times. The habit count should update accordingly.
Step 4 (Optional): Disable Turbo Drive
Let's try this again, this time with Turbo Drive disabled. I want to prove to you that Turbo Drive is indeed making our application work like a single-page application.
In the app/javascript/application.js
file, add the following code to disable Turbo Drive.
Turbo.session.drive = false
Now reload the browser and click on the counter buttons. Notice something different? Every time you update the counter, the browser reloads.
This is the magic of Turbo Drive, it can update your web pages without doing a full reload.
Okay, enable Turbo Drive again. The counter should still function as expected without reloading the browser. You could just deploy it to production and be on your way with the next feature, but we're going to improve it using Turbo Frames.
Improve? But what's there to improve, you ask? Isn't it already behaving like a SPA? Well, yes, it is. But still, it's doing a lot more work than needed. Let's see how.