Implement Ping Tool In Rails Application Using Hotwire
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.