Reputation: 146
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.)
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.
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 project is open source; the aformentioned code is at https://github.com/steve-mcclellan/j-scorer/blob/master/app/views/stats/_options.html.erb
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.
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
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