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.