Using Hotwire and Rails to build a live commenting system24 Jun 2021
Today we’re exploring Hotwire, a new-old way of building web applications by sending HTML instead of JSON over the wire.
We’ll learn how Hotwire works with Ruby on Rails by building a simple application that allows users to leave comments on a project and see those comments in real-time, without requiring full page turns.
To accomplish this, we will start with a new Rails 6.1 application, install Hotwire in the application, and then walk through the basics of adding Hotwire to our views and controllers.
I’m writing this assuming that you are comfortable with the basics of Ruby on Rails development and that you’ve never used Hotwire before.
You can find the complete source code for this tutorial on Github.
Let’s dive in.
Set up our project
To get started we will create a new Rails application and scaffold up a Project resource to build our commenting system against. While you can work from an existing application of your own, you will find this guide easier to follow if you start from scratch along with me.
First, run these console commands to create and setup our Rails app:
In your browser, head to http://localhost:3000/projects and create a project. We’ll use this project as we build the commenting system.
Once that’s done, open the application’s code in your favorite editor and add
has_many :comments to
Next, display comments on the project show page by updating
app/views/projects/show.html.erb with the following:
And add a few comments in the rail console:
If you’ve followed along successfully so far, when you visit http://localhost:3000/projects/1 you should see something that looks like this:
Now we’re ready to install Hotwire and start learning.
To install Hotwire, first add the hotwire-rails gem to your Gemfile by running this commend in your console or by manually updating your Gemfile and running
bundle install in your terminal.
Once the gem is added, run the installer from your terminal:
After the installer runs, restart your rails server or you may encounter some undefined method errors later in this guide.
Now that we have Hotwire installed, let’s start with getting comments to render in real time.
Adding a comments stream
Hotwire as we’re using it relies on frameworks designed and maintained by the Basecamp team. One of those frameworks is Turbo. Turbo is a set of “complimentary techniques for speeding up page changes and form submissions”.
A Turbo Stream delivers page changes to the browser over Websocket. By adding the Hotwire Rails project to our application, we gain access to a set of helper methods that make working with Turbo Streams simple.
Let’s see how this works by updating our project show page as follows.
Let’s walk through the changes here one-by-one.
First, we add a turbostream that listens for broadcasts on the comments channel for our Project. This stream is how we subscribe to comment updates for our project. Note that because we’re working with a nested resource here (comments belong to a project), our stream subscribes to comments for just the current project.
We also add an id to the parent div of our comments list. This id is used to identify where to add broadcasted comments to the DOM. If this id is not present or does not match the id in the broadcast, no DOM updates will occur when comment changes are broadcast.
Finally, we’re using a _comment partial to render each comment. That partial doesn’t exist yet, let’s add it now:
And then fill in the comment partial with the below content.
Note that here, we’re ensuring each comment has a unique id in the DOM, which ensures that new comments are properly inserted into the DOM.
Our last step towards getting our stream setup and comments prepending in real time is to add a callback to the comment model.
When this callback runs, the newly created comment will be broadcast on the project_comments stream that our project#show page is now subscribed to.
Here we are using the
broadcast_prepend_to method provided by turbo-rails. There are a variety of methods that can be used to add, remove, and replace DOM content through callbacks triggered by model changes. The best place to find these methods is by reading the turbo-rails code.
Note that the
target value passed to
broadcast_prepend_to must exactly match the id set in the DOM earlier.
The dom_id method used to set the broadcast channel target is not normally available in models so we include the relevant ActionView helper class in the model. This simplifies our method of broadcasting to the appropriate channel. H/T to Chris Oliver at GoRails for this helpful trick.
With these changes in place, let’s test out our stream and see how it works.
Make sure you’ve got your project open in a web browser, and then open your Rails console and create a comment. If you’ve followed along successfully, immediately after you create the comment in the console, you should see the comment added to the project page open in your browser, like this:
Adding comments with a form
While our subscription is working great, users aren’t going to add content through the Rails console. Let’s finish up this tutorial by adding a form to add comments in the UI without requiring a page turn.
First, let’s create a partial to render our form:
And add the below to the form:
This is pretty close to a standard Rails form partial, with the added turbo_frame_tag that wraps the entire <form> element.
Now we’ll need a controller to handle submissions from this form.
Fill in the controller with the below content:
Our comments controller create method is a fairly standard Rails controller with one twist: the create method responds to turbo_stream requests. When a turbo_stream request is sent to this endpoint, the controller responds with a turbo_stream response to replace the comment_form DOM element with the comments/form partial, both on a successful request and a failed request. Note that, as before, the “comment_form” id in our turbo_frame needs to match the target passed to the turbo_stream.replace method.
The only difference between the failure and success responses is using the existing @comment on failed responses so that errors can be rendered. On success, we set the comment local variable for the form partial to a new comment to clear out the body field on the form.
To test out the error path, you can update your Comment model to validate the presence of the body field.
Next, before we can create comments, we need to add the appropriate route to the routes.rb file:
Finally, let’s display the comment form on the project show page:
Refresh your project page after you update the view.
If everything is set up correctly, you should see new comments added without a page turn. If you submit with the comment body left blank, you should see error messages added to the page.
Combining Hotwire and Rails, with a sprinkling of Stimulus for client-side interactivity is a powerful way to build performant, scalable, developer-and-user friendly web applications.
Helpful Hotwire resources:
- Hotwire intro video
- Turbo handbook
- Stimulus handbook
- GoRails Hotwire introduction
- turbo-rails source
Thanks for reading! As always, get in touch if you have any questions or if I can be of any help.