How to use and test an AJAX request in Rails
What's AJAX?
AJAX (Asynchronous Javascript and XML) is a kind of technique that can send HTTP requests and exchange data with a server by Javascript. It's an important and practical technique to enhance the user experience, especially when we want to achieve the effect that updating the partial content and wouldn't like to reload the whole page.
How to send an AJAX request in Rails
Rails has built-in Unobtrusive JavaScript that has required in the application.js
file.
//app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
Thanks to Unobtrusive JavaScript! We can just put something like data-*
on the HTML DOM and some simple dynamic things can be implemented. (Read more about the list of data attributes)
The most common one is data-confirm
. After adding data-confirm
like the example below, when a user clicks the Delete
, a pop-up dialog with 'Are you sure to delete this event' texts will show up.
<%= link_to(
'Delete',
event_path(event),
method: :delete,
data: { confirm: 'Are you sure to delete this event?' }
)
%>
We can implement an AJAX request by the data attributes that Unobtrusive JavaScript has provided, too. Just like the example above, we just need to add data-remote
on the target HTML DOM. After that, Rails will help us use AJAX to send the HTTP request.
For instance,
<%= link_to 'Show the article', article_path(article), remote: true %>
Rails helper will generate HTML tags like:
<a href="/artciles/1" data-remote="true">an article</a>
How to test an AJAX request with RSpec
For a general GET request, the test subject might look like this:
RSpec.describe ArticlesController, type: :controller do
describe 'GET #show' do
subject { get :show, params: params }
let(:params) { { id: article.id } }
it { is_expected.to have_http_status(:ok) }
it { is_expected.to render_template(:show) }
end
end
To make an AJAX request to the show action using GET,
we have to add xhr: true
to the subject (Rails version 5.0+):
RSpec.describe ArticlesController, type: :controller do
describe 'GET #show' do
subject { get :show, params: params, xhr: true }
#...
end
end
According to Rails ActionController::TestCase,
# rails/actionpack/lib/action_controller/test_case.rb:569
if xhr
@request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
@request.fetch_header("HTTP_ACCEPT") do |k|
@request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
end
end
I'm curious if it is an AJAX request now, so I try to check it out by calling request.headers["X-Requested-With"]
and request.xhr?
.
# rails console
def show
=> binding.pry
#...
end
$ request.headers["X-Requested-With"]
#=> "XMLHttpRequest"
$ request.xhr?
#=> 0
# means the value of headers["X-Requested-With"] is totally match "XMLHttpRequest" string
The answer is yes! It's an AJAX request now.
The process of AJAX interacting with the server is by an XMLHttpRequest
object. After setting xhr: true
means the “X-Requested-With” header has contained “XMLHttpRequest” and tells the client to send the request by AJAX.
Reference:
