Steve McClellan
Steve McClellan

Reputation: 146

Rails not making Ajax request when file field is present in form

Problem summary

According to the docs, when using form_with, Rails makes an Ajax request by default. I need an Ajax request, but I've found a situation where a standard browser request is being made instead. I'd love to know what I'm doing wrong and how to fix it.

Desired behavior: An Ajax request is made on form submission, so that I can return JavaScript that will change only part of the displayed page.

Current behavior: A standard browser request is being made that 404s because the route is XHR-only. (If I make the route accessible to all requests, the entire page is replaced, which is still highly undesirable.)

The code

Here's the code that's generating the form:

<%= form_with(url: restore_path,
              method: 'post',
              multipart: true,
              id: 'restore-form') do %>

  <label for="file">Backup file:</label>
  <%= file_field_tag 'backup_file', required: true %>

  <%= submit_tag('Restore',
                 id: 'restore-submit-button',
                 class: 'btn btn-primary',
                 data: { disable_with: 'Restoring...' }) %>

<% end %>

This is not producing an Ajax request, however. restore_path is an XHR-only route...

  constraints(->(req) { req.xhr? }) do
    # other routes snipped for brevity's sake
    post 'restore' => 'backups#restore'
  end

...and submitting the form is leading to a 404 because the request is a standard browser request.

A weirdness I discovered when trying to debug

If I change the field from a file_field_tag to a text_field_tag, the request submits as Ajax. When I changed

<%= file_field_tag 'file', required: true %>

to

<%= text_field_tag 'test_field', nil, required: true %>

and left everything else the same, an Ajax request was made as expected. The only change in the generated HTML (aside from the exact value of the authenticity token) is that

<input type="file" name="file" id="file" required="required">

changes to

<input type="text" name="test_field" id="test_field" required="required">

...exactly as one would expect. But the form submission behavior changes entirely.

The repo

The project is open source; the aformentioned code is at https://github.com/steve-mcclellan/j-scorer/blob/master/app/views/stats/_options.html.erb

One other thing I tried

I created a tiny new Rails app from scratch to see if I could reproduce the problem, but I could not. An Ajax request is made even when there's a file upload present.

HALP!

I'm running out of ideas. What am I doing wrong? How do I make my restore form make an Ajax request when a file field is present?

Upvotes: 2

Views: 764

Answers (1)

Steve McClellan
Steve McClellan

Reputation: 146

Got it. The docs are assuming that I'm using the rails-ujs adapter for unobtrusive JavaScript (which has been built in since Rails 5.1). My app was created in the Rails 4 era, and uses the old jquery-ujs adapter.

Browsers don't natively send Ajax requests that include file uploads. rails-ujs has a built-in workaround for this, but jquery-ujs doesn't.

There are two ways to proceed from here.

The long-term solution is to switch to rails-ujs, which is currently maintained as part of Rails. (As of this writing, jquery-ujs hasn't been updated in over two and a half years.)

Unfortunately, the two adapters have some interface differences, so some changes will likely need to be made throughout the application's JavaScript. See this article for specifics.

The quick fix? Well, there's a gem for that. (The docs indicate that it's for Rails 3, but it works like a charm on my application, which is running Rails 6.0.3.3 as of this writing.)

Addition to Gemfile:

gem 'remotipart', '~> 1.4.4'

Addition to app/assets/javascripts/application.js (after the existing jquery_ujs line):

//= require jquery.remotipart

And it works. The request is being made via Ajax. It's hitting my XHR-only route without issue and rendering a JavaScript view that's updating only part of the page.

Victory!

Upvotes: 4

Related Questions