Turbocharging Ruby on Rails with ‘HTML Over the Wire’
Tutorial showing how to use the modern HTML Over the Wire — and Hotwire specifically — approach with the classic Ruby on Rails framework.
Jul 23rd, 2022 5:00am by
Lead image via Shutterstock.
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.
<code class="language-bash">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]</code>
<code class="language-bash">theNewStack> gem install rails</code>
<code class="language-bash">theNewStack> rails --version
Rails 7.0.3</code>
<code class="language-bash">theNewStack> rails new SimpleApp --skip-active-record --no-test-framework</code>
<code class="language-bash">cd SimpleApp
bin/rails server</code>
# 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:
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”:
class RegistrationsController < ApplicationController
def new
@user = User.new
end
end
And this minimal view in “app/views/registrations/new.erb.html”
<code class="language-html"><h1>Sign Up</h1></code>
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:
<code class="language-html"><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 %></code>
.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”:
<code class="language-ruby"><%= 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 %></code>
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.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.