Asynchronous HTTP requests in Rails
Today is a special day. It’s the day I’ll (mostly) talk about Javascript!
I’ve been struggling with AJAX requests in Rails apps for a while. But I’ve started using them a lot recently, and pieces of the puzzle kinda fell together. Asynchronous requests can be handy when you need to update some parts of your application’s page without reloading the whole thing. I’ll show you how to do this with plain ol’ vanilla Javascript (and its fetch() method), and Rails 6 native server-side partial rendering.
2020-05-10 Update: If you’re more confortable following along a video, I’ve recorded a screencast.]
Some context
Alright. Let’s say you have a list of essays from your app’s blog. They’re sorted from newest to oldest. But you’d like your readers to have the ability to filter them by topic.
You’d start by adding some buttons at the top of your feed. Now, when readers click the button Ruby
, you’d like your feed to only display essays with the category Ruby
. All of this, without reloading the whole page.
Something like this 👇
This sounds like a job for some asynchronous HTTP requests.
The Rails bit
Let’s dive right in and add a new route:
This will be the endpoint our requests ping. Now, here’s a basic controller:
And the corresponding view:
Note that:
- Each filter has an ID.
- The DOM element embedding the list of posts has an ID.
- The list of posts is in a partial: this will make server-side rendering much easier.
Speaking of partial, here’s posts_list.html.erb
:
We’re looping over a collection of posts. For each post, we generate a clickable title and an excerpt.
And now, ladies, gentlemen, and variations thereupon 1, let’s make some Javascript!
Building asynchronous requests in Rails with fetch()
, step-by-step
Before we begin, you can either write your Javascript in your app/assets/javascripts
directory or in your app/javascript/packs
directory based on your Rails configuration (i.e. Do you have webpacker installed or not?). Both will work just fine!
The basic syntax for fetch()
Here are the outlines of our Javascript file:
Here’s what blog_filters.js
does:
- Find and store the DOM element embedding my list of posts.
- Find and store every filter-type button on our page.
- Add an event listener on each button that’ll trigger our asynchronous request.
- Store the URL where I’ll send my asynchronous request (i.e
Blog::PostsController#index
). - Fetch then handle the response from my controller.
Now, let’s work some magic between our Javascript file and our controller.
Fleshing out fetch()
fetch()
takes an URL as the first parameter. We’ve already done that. Now, we’ll add details to the second parameter (it’s called the init
object).
What’s happening here?
- I specify the HTTP request I want to do.
- I fill in the request’s headers with:
- my CRSF token (so Rails doesn’t think your request is illegitimate)
- the type of content I expect to receive (in our case, HTML)
- I specify that the request comes from our app.
This bit will ping our Blog::PostsController#index
(through the route stored in actionUrl
). Remember, we want our controller to filter our essays based on the category sent through our fetch()
method. So we need to add that category to our request:
I’ve added a parameter category
to my filterPosts
function. Then, I’m building my some Rails params
with category
and, I’m adding them to my actionUrl
. Now, I can access my category
in my Blog::PostsController
through my ActionController::Parameters
. 👌
Filtering data in our controller
I can update our controller based on the presence of the category
key in my params
:
Let’s break it down:
- If
params['category']
is absent, my controller returns a list of posts (@posts
) toindex.html.erb
which renders the partialposts_list.html.erb
. - If
params['category']
is present, my controller directly returns the partialposts_list.html.erb
with the filtered list of@posts
to my Javascript method, not to the view.
If your partial is somewhere else (like in your shared
directory, just give your controller the relative path - i.e. ../../shared/posts_list
).
Javascript kinda places itself in between my controller and my view. Why? Because we’ll only update one bit of the page by manipulating the DOM.
layout: false
tells Rails not to look for a template (since I’m feeding my Javascript method with a partial).
Handling the response from our controller
The first thing I like to do, is to check the HTTP status sent from my controller. If it’s a 200
, I’ll use the response to replace the posts list in my view.
What did I do? I applied the text()
method to my controller response
. response
is - and I quote the Mozilla documentation - a readable stream of byte data (you gotta love Javascript 🙃). text()
takes this stream and turns it into a UTF-8-encoded string.
In our case, here’s what happens:
- Our controller returns a partial with a loop in it.
- Our ERB file is interpreted, and Javascript accesses the HTML as a readable stream.
text()
kicks in and turns the stream into a string (which is a stringified version of our HTML).- I assign
response.text()
tocontent
, and I replace thepostsList
section of my DOM with the stringify partial.
If the response
status is an error (like a 500
), I can show the user an error. Whatever’s tickling your fancy.
Phew! We just changed the posts section of my page without reloading the page. No fancy framework. No HTML written inside the Javascript code. Just some well-integrated server-side rendering and vanilla Javascript. 👌
Screencast
That’s is for today folks! I hope you enjoyed it as much as I did.
If anything seems odd, ping me on Twitter or create an issue on GitHub.
Cheers,
Rémi - @remi@ruby.social
-
👋 Doctor Who fans. ↩