Turbocharging Ruby on Rails with ‘HTML Over the Wire’

Ruby was my first love, my first language was C, and now I go steady with C#. This is a typical romantic journey for most developers; falling for a shiny new methodology, only to flirt with a statically typed language later. But there’s one thing I have been consistent with: I don’t want to write JavaScript if I don’t have to.
Now, as a pragmatic developer, I recognize why I might need JavaScript, but I’d always prefer to be in the more comfortable confines of a bigger language. Since the birth of node.js, there have been plenty of devs who are happy to use JavaScript exclusively — for both client and server. And admittedly, I did like the control that media queries gave me with responsive web design.
I’ve always thought Ruby on Rails was a little easier to manage because it was “opinionated” — even if it was a little too keen to push you into a database. So I was intrigued by the idea of “HTML Over the Wire”; and Hotwire specifically. It seems to give you JavaScript-like abilities without actually having to write any. So I thought it might be time to fire up a Rails project and see how that went.
Installing Rails (Again)
So, let’s start with a simple Rails installation. We just want a form and response. I’ll assume that you are comfortable with what Rails is and its basic tenets, but maybe (like me) you haven’t done all this stuff for a while. So here’s what I’ll do:
- I won’t use a database;
- I’ll try using a generator, so I don’t have to remember all the files needed.
I have an rvm (Ruby enVironment Management), so let’s try to install a new ruby to match what we need for Rails. Naturally, whatever Ruby I did have wasn’t high enough:
1 2 3 4 5 6 |
theNewStack> rvm get stable ... theNewStack> rvm use ruby --install --default ... theNewStack> ruby --version ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin21] |
Then the important stuff:
1 |
theNewStack> gem install rails |
That gave me 35 gems and documentation.
1 2 |
theNewStack> rails --version Rails 7.0.3 |
Ok, so let’s ask the generator for a skeleton application, with no tests or active record.
1 |
theNewStack> rails new SimpleApp --skip-active-record --no-test-framework |
At least we have enough to throw something immediately on the screen. As I demonstrated with Sinatra, the current favored server is called Puma. So on my Mac, we can wander into the app directory and start a server with Puma:
1 2 |
cd SimpleApp bin/rails server |
And at http://localhost:3000 we see the default Rails page. I can already see some Hotwire things in the Gemfile (there is a “turbo-rails” gem), so perhaps we don’t need to do anything further for that.
We want a simple form, like a sign-up page, and then update it after submission. I used to use a Flash Message for this in the past. So in a Model/View/Controller world, we want a user model, then a form for our “new user” view, and finally a registrations controller. No, I haven’t written any of these things yet.
Fortunately, despite not using Rails recently, my old friend the “routes.rb” file still exists. A route is how we describe our internal architecture in good REST fashion. So I associate the URL GET request “/sign_up” with a new registration, and it’s POST response appropriately too.
1 2 3 4 5 6 |
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html Rails.application.routes.draw do get "sign_up", to: "registrations#new" post "sign_up", to: "registrations#create" end |
OK, so we better make a registrations_controller. Remember, the Rails world is very opinionated, so stick to the mantra and use the words and phrases as expected. We are already being difficult by not having a database!
But first, a quick User model. Note that because I ditched the database, we are using ActiveModel to persuade Rails we are good citizens. So in “app/models/user.rb” we have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class User include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming attr_accessor :objectId, :name, :email, :password @id = nil def initialize(attributes = {}) @name = attributes[:name] @email = attributes[:email] @password = attributes[:password] @objectId = attributes[:id] end def id return self.objectId end def persisted? !(self.id.nil?) end end |
And a quick RegistrationsController in “app/controllers/registrations_controller.rb”:
1 2 3 4 5 |
class RegistrationsController < ApplicationController def new @user = User.new end end |
And this minimal view in “app/views/registrations/new.erb.html”
1 |
<h1>Sign Up</h1> |
We get this blessed result in the browser:
OK, that is cool but we need a more fulsome form. After all we need something for Hotwire to do.
Now, this isn’t a post about the multitude of ways you can get a screen to look nice, but I’ve squeezed a bit of juice out of Rails and HTML, so the form looks alright without having to do anything much in CSS. For a real project, you would probably want to do the opposite.
The Rails smart magic works via “form_for”, between the ERB tags, which looks at the model (i.e. user.rb) and riffs on that. It even deduces that “Create User” would be a good default for the submit button text. This is what you are paying for when using Rails.
Here is the nicer view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<h1>Sign Up</h1> <%= form_for (@user), url: sign_up_path do |form| %> <div > <%= form.label :name %> <%= form.text_field :name, class: "form_field", id: "name_field", placeholder: "The New Stack" %> </div> <br> <div> <%= form.label :email %> <%= form.text_field :email, class: "form_field", id: "email_field", placeholder: "info@thenewstack.io" %> </div> <br> <div> <%= form.label :password %> <%= form.password_field :password, class: "form_field", id: "password_field", placeholder: "password123"%> </div> <br> <div> <%= form.submit %> </div> <% end %> |
Note the URL override to send the response to the sign_up path we have made in routes. I’ve identified the fields, as will need that shortly. The rest is just Rails and HTML.
Just to make it a little cleaner, I have added a simplistic CSS in “app/assets/stylesheets/application.css”:
1 2 3 4 5 |
.form_field { display : block; min-width: 30%; } |
This all gives us this:
Getting Hotwire Involved
OK, wonderful, but this post is about Hotwire. Normally we would have to redraw a new page here, or use a little Flash Message to somehow confirm that a user has been created. But what Hotwire in its Turbo Stream mode gives us is a chance to replace a section of code in response to an action. This is done in AJAX so no screen drawing is needed, but also it is done in a nice Railsy way. A Railsway. Take a look at “app/views/registrations/submit.turbo_stream.erb”:
1 2 3 4 5 6 7 8 9 10 11 |
<%= turbo_stream.replace("name_field") do %> <span class = "form_field" style="font-weight: bold"><%=@user.name%> <% end %> <%= turbo_stream.replace("email_field") do %> <span class = "form_field" style="font-weight: bold"><%=@user.email%> <% end %> <%= turbo_stream.replace("password_field") do %> <span class = "form_field" style="font-weight: bold">Password recorded <% end %> |
Firstly, note the nomenclature of the filename. The rest is kind of simple; in this case we replace the identified div with some simple HTML.
I added the create method to the “app/controllers/registrations_controller.rb”:
1 2 3 4 5 6 7 8 9 10 11 12 |
class RegistrationsController < ApplicationController attr_accessor :user def new @user=User.new end def create @user = User.new(params.require(:user)) render "submit" end end |
I clearly haven’t quite got the variable use right here: the @user in the create that reads the incoming parameters is the important thing though.
Here is the page after you create a user:
So, to summarise the behavior and flow:
- We relate the GET request sign_up page to the registration controller’s new method in “routes.rb”.
- Rails knows to relate the registrations new method to the correct registrations view “new.html.erb”, where the form is.
- The form is displayed, based on the “user.rb” model.
- The button on the form sends a POST request to sign_up.
- We related the POST sign_up to the controller’s create method. This directs rails to render the submit view.
- Turbo looks for the “submit.turbo_stream.erb”, and makes the replacements asked for.
- The result is no page redrawing.
While I took a bit of time to grok how Rails is currently set up, and I almost certainly haven’t used it in the official way, it is still very easy to play around and see the results. However, familiarity with HTML and CSS are still necessary — even if we can briefly ignore our JavaScript overlords.