Ruby on Rails - AJAX
Ruby on Rails - Modern AJAX with Hotwire/Turbo
Section titled “Ruby on Rails - Modern AJAX with Hotwire/Turbo”AJAX (Asynchronous JavaScript and XML) allows web pages to be updated asynchronously by exchanging small amounts of data with the server behind the scenes. This means parts of a web page can be updated without reloading the entire page, leading to faster and more responsive user experiences.
AJAX incorporates several technologies:
- HTML/XHTML and CSS for presentation.
- The Document Object Model (DOM) for dynamic display and interaction.
- JSON or XML for data interchange (JSON is more common now).
- The
fetchAPI orXMLHttpRequestobject for asynchronous communication. - JavaScript to bring these technologies together.
While traditional AJAX often involves writing significant JavaScript, modern Rails (7+) embraces Hotwire (primarily Turbo and Stimulus) to achieve similar results with minimal custom JavaScript.
How Rails Implements Asynchronous Operations with Turbo
Section titled “How Rails Implements Asynchronous Operations with Turbo”Turbo, part of the Hotwire suite, enhances Rails applications by making them faster and more interactive with less JavaScript. It works by intercepting link clicks and form submissions and handling them asynchronously.
A typical Turbo-enhanced interaction flow:
- Trigger: A user clicks a link or submits a form.
- Request: Turbo intercepts the action and makes an AJAX request to the server (using
fetch). - Server Processing: The Rails controller action processes the request as usual. Instead of returning a full HTML page, it can return an HTML fragment or specific Turbo Stream instructions.
- Client Response Handling:
- If a full HTML page is returned, Turbo Drive intelligently updates the current page’s
<body>and merges the<head>, mimicking a full page load but faster. - If Turbo Stream instructions are returned, Turbo updates specific parts of the page (e.g., appending, prepending, replacing, or removing elements) without a full page refresh.
This approach simplifies building dynamic interfaces by leveraging server-side rendering and conventions.
AJAX Example: Deleting an Item with Turbo Streams
Section titled “AJAX Example: Deleting an Item with Turbo Streams”Let’s illustrate with an example: asynchronously deleting an item from a list using Turbo Streams. We’ll assume you have a scaffold for a Pony model (name:string, profession:string).
Step 1: Generate a Scaffold (if not already done)
Section titled “Step 1: Generate a Scaffold (if not already done)”If you don’t have it, create a Pony resource:
$ bin/rails generate scaffold Pony name:string profession:string$ bin/rails db:migrateThis creates the model, controller, views, and routes for ponies.
Step 2: Update the View (index.html.erb)
Section titled “Step 2: Update the View (index.html.erb)”Open app/views/ponies/index.html.erb. The scaffold likely generated a button_to for destroying a pony. We need to ensure it’s compatible with Turbo and that each pony item has a unique DOM ID.
Modify the loop that displays ponies to wrap each pony in an element with a DOM ID, and update the delete button:<div id="ponies"> <% @ponies.each do |pony| %> <%= content_tag :div, id: dom_id(pony) do %> <%# Use dom_id for unique ID %> <p> **Name:** <%= pony.name %> </p>
<p> **Profession:** <%= pony.profession %> </p>
<%= link_to "Show this pony", pony %> <%# The button_to for destroy usually comes with data-turbo-confirm %> <%# Ensure method: :delete and data attributes are present %> <%= button_to "Destroy this pony", pony, method: :delete, data: { turbo_confirm: "Are you sure?" } %> <hr> <% end %> <% end %></div>
<%= link_to "New pony", new_pony_path %>The dom_id(pony) helper (e.g., outputting pony_123) is crucial for Turbo Streams to target the correct element.
Step 3: Update the Controller (ponies_controller.rb)
Section titled “Step 3: Update the Controller (ponies_controller.rb)”Modify the destroy action in app/controllers/ponies_controller.rb to respond to Turbo Stream requests:
# DELETE /ponies/1 or /ponies/1.jsondef destroy @pony = Pony.find(params[:id]) @pony.destroy
respond_to do |format| format.html { redirect_to ponies_url, notice: "Pony was successfully destroyed." } format.json { head :no_content } format.turbo_stream # Add this line to respond to Turbo Stream requests endendBy adding format.turbo_stream, Rails will look for a destroy.turbo_stream.erb file when the request format is turbo_stream.
Step 4: Create the Turbo Stream View (destroy.turbo_stream.erb)
Section titled “Step 4: Create the Turbo Stream View (destroy.turbo_stream.erb)”Create a new file app/views/ponies/destroy.turbo_stream.erb with the following content:
<%= turbo_stream.remove @pony %><%# Or equivalently: turbo_stream.remove dom_id(@pony) %>This template tells Turbo to find an HTML element with the DOM ID corresponding to the @pony object (e.g., id="pony_123") and remove it from the page.
Step 5: Test the Application
Section titled “Step 5: Test the Application”- Start your Rails server:
$ bin/rails server - Open your browser and navigate to
http://localhost:3000/ponies. - If you don’t have any ponies, click “New pony”, fill in the details (e.g., Name: “Rainbow Dash”, Profession: “Weather Patrol”), and click “Create Pony”. You’ll be redirected to the pony’s show page. Go back to the ponies list (
/ponies). - You should see your created pony in the list. Each pony entry will have its name, profession, a “Show this pony” link, and a “Destroy this pony” button.
Now, click the “Destroy this pony” button for one of the ponies.
You should see a confirmation dialog (e.g., “Are you sure?”) because of data: { turbo_confirm: "Are you sure?" }.
Click “OK”. The pony item will disappear from the list without a full page reload. The URL will remain /ponies. This is Turbo Streams in action!
This example demonstrates a simple yet powerful way to enhance user interfaces in Rails using Hotwire/Turbo, often replacing older AJAX patterns that required more custom JavaScript.