Implement Ping Tool In Rails Application Using Hotwire

ABHIGYAN MAHANTA
5 min readJul 3, 2023
ping-tutorial-rails-7

Problem

Using Rails Hotwire, developers can create dynamic applications that are server-rendered without using Javascript libraries like React or Vue.

Server-side rendered application means that the server is responsible for delivering page changes in response to events occurring on the server such as users sending messages on a chat.

When the server dynamically renders a page, then the total network response time becomes important as it directly impacts the user’s experience.

A longer network transfer time or higher latency means a less responsive application.

You might be looking for a way to determine the latency or Ping time in order to understand how quickly a webpage or an application will load for users.

You can easily achieve this by following this tutorial.

So how do we implement a Ping tool in a rails application using Hotwire?

Solution

For the purpose of this tutorial, we’ll be setting up a new Rails application.

However, you can skip this step if you’ve already set up your project.

Let’s follow this step by step:

1. Create a new rails application:

rails new pingapp

2. Setup Hotwire

Hotwire, which is the combination of Stimulus and Turbo, is included in Rails 7 applications by default.

So you don’t need to set it up in case you are using Rails 7.

You can confirm this by visiting your Gemfile and you’ll see that it includes the following lines:

gem 'stimulus-rails'
gem 'turbo-rails'

However, if you’re on Rails 6 and below, you can set it up as follows:

i) Add Stimulus

1. Add the stimulus-rails gem to your Gemfile:

gem 'stimulus-rails'

2. Run the command in the console: ./bin/bundle install

3. Create app/javascript/controllers/index.js and load your controllers like this:

import { application } from "controllers/application"

import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"

eagerLoadControllersFrom("controllers", application)

4. Create app/javascript/controllers/application.js with the following code:

import { Application } from "@hotwired/stimulus"

const application = Application.start()

application.debug = false

window.Stimulus = application

export { application }

5. Add the following line to app/javascript/application.js for importing all your controllers:

import "controllers"

6. Finally, Pin Stimulus and controllers in config/importmap.rb by adding:

pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true

pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true

pin_all_from "app/javascript/controllers", under: "controllers"

ii) Add Turbo and Redis

1. Add the turbo-rails gem to your Gemfile:

gem 'turbo-rails'

2. Run the command ./bin/bundle install

3. Next, run the command ./bin/rails turbo:install

4. Finally, run the command ./bin/rails turbo:install:redis to change the development Action Cable adapter from Async (the default one) to Redis.

We need to switch to Redis because the Async adapter does not support Turbo Stream broadcasting.

That's it! We’re done with the setup.

3. Add a Ping controller

Next, we will add a controller with an action named as ping.

Let's call it as PingController.

Use the following command to create the controller:

rails g controller ping

This controller should have the following code:

class PingController < ApplicationController
def ping
render status: :ok, body: "PONG"
end
end

Next, we’ll add a route for this action in the config/routes.rb file as follows:

get "/ping", to: "ping#ping"

Now let’s create a form in app/views/shared/_ping.html.erb to hit this route.

<div data-controller="ping">
<%= form_tag ping_path, method: :get do %>
<%= button_tag "Ping" %>
<% end %>
<span data-ping-target="latency"></span>
</div>

Then we just need a controller which will display this _ping partial.

We'll name this controller as HomeController and add a show action. For this, use the following command:

rails g controller home show

And then we will make this action as root. Go to config/routes.rb file and add this line

root to: "home#show"

To render the ping partial, add the following code in app/views/home/show.html.erb

<%= render "shared/ping" %>

Now on the homepage, we can see the Ping button.

In the next step, we will create a function to calculate the ping time.

4. Calculate ping time

For this, we need to create a stimulus controller. Let’s run the following command:

rails g stimulus ping

This will create a file: app/javascript/controllers/ping_controller.js.

Next, we'll add the following code to this file:

import { Controller } from "stimulus";

export default class extends Controller {
static targets = ["pingForm", "latency"]

pauseRequest(event) {
event.preventDefault();
setTimeout(() => this.saveRequestTime());
event.detail.resume();
}

saveRequestTime() {
this.requestTime = new Date().getTime();
}

measureLatency() {
this.saveResponseTime();
this.latency = this.responseTime - this.requestTime;
this.displayLatency();
setTimeout(() => this.ping(), 1000);
}

displayLatency() {
this.latencyTarget.textContent = this.latency + " ms";
}

saveResponseTime() {
this.responseTime = new Date().getTime();
}

ping() {
this.pingFormTarget.requestSubmit();
}

get requestTime() {
return this._requestTime;
}

set requestTime(requestTime) {
this._requestTime = requestTime;
}

get responseTime() {
return this._responseTime;
}

set responseTime(responseTime) {
this._responseTime = responseTime;
}

get latency() {
return this._latency;
}

set latency(latency) {
this._latency = latency;
}
}

Let’s take a look at what we are doing on this file.

The pauseRequest() method will pause the request using event.preventDefault(), then save the request time right after the request is sent using

setTimeout(), and then resume the request with event.detail.resume().

The measureLatency() method will save the response time, compute the difference between the response and request time, and display the result, and resubmit the form after every one second.

The reason we're using form.requestSubmit() because form.submit() doesn't dispatch submit events.

Now we just need to update our form by adding the actions to measure the network latency when the form is submitted:

5. Update the form

In app/views/shared/_ping.html.erb, make the following changes:

<div data-controller="ping">
<%= form_tag ping_path, method: :get, data: { action: "turbo:before-fetch-request->ping#pauseRequest turbo:submit-end->ping#measureLatency", "ping-target": "pingForm" } do %>
<%= button_tag "Ping" %>
<% end %>
<span data-ping-target="latency"></span>
</div>

That’s it! everything is set. Start your rails server with the following command:

rails s

Now you can visit http://localhost:3000 and click the Ping button, you will see the ping time being displayed on the page after every second as shown below.

Discussion

This is how you can measure and monitor your application’s latency/ping time.

This tool will help you understand how responsive your app is and give you insights on further increasing your app performance.

--

--