Chances are, if you maintain a nontrivial Ruby on Rails application, you already use some Unobtrusive JavaScript, or UJS. UJS in general is a technique of not inlining your JS code into the HTML markup, but in Rails context specifically it is a small JavaScript library invented by the Rails team that lets you write almost no code in some common situations. The library supports:
- confirmation dialogs in links or forms
- non-GET methods in link actions
- auto-disabling buttons in forms to prevent double submitting
- remote forms and links - automatic AJAX calls and responses procesing
- automatic CSRF protection in AJAX requests
The UJS library has been originally written using jQuery some time around 2010 under the name jquery-ujs
. In 2016, the Rails team decided to drop the jQuery dependency and rewrite the library into a vanilla-JS one, named rails-ujs
. Soon after that the rails-ujs
library has been moved into Rails itself, starting with Rails 5.1.
Why would you want to migrate?
I can see a few reasons that might make you want to switch to rails-ujs
:
- You might want to take this migration as the starting point for removing the jQuery dependency from your project altogether. Not that I have anything against jQuery, but it seems obvious that one of its main purposes — unifying browsers behavior under a single API — is way less relevant than it was years ago, as the modern browsers APIs tend to converge and standardize today.
- You upgraded your Rails application to Rails 5.1 or newer and just want to use UJS that is officially supported as a part of the Rails framework. While the
rails-ujs
code seems to be actively developed and maintained, the originaljquery-ujs
repository is slowly falling behind. -
You want to use Stimulus for responding to your remote forms or other UJS actions. If you try that, you will notice that Stimulus does not work together with
jquery-ujs
but it does withrails-ujs
because it needs native DOM events which onlyrails-ujs
triggers. - Unlike
jquery-ujs
, which is a single-file library, the currentrails-ujs
is written in a more modular style so it might be easier for you to read the sources or contribute, especially if you’re familiar with CoffeeScript (my guess is that UJS will eventually be rewritten to ES6, though).
For our team at NejRemeslnici, all four points were relevant, but especially the second one made us touch the code after the recent upgrade of our app from Rails 4.2 to 6.0. We want to be closer to active development of the dependencies we use! Convinced, too? Read on!
Simple things first
If you’re still packing your JS code via the asset pipeline, just swap the libraries in the corresponding asset file (otherwise see the README for configuration in webpacker):
// app/assets/javascripts/application.js
-//= require jquery_ujs
+//= require rails-ujs
Note that you should still require the library after
jquery
, so that the automatic CSRF protection still works even in manual AJAX calls made using the$.ajax()
jQuery method.
Next, search through your code for $.rails.<something>
and replace all occurrences to Rails.<something>
. This is relevant if you customized the UJS library somehow such as when you used a custom confirmation dialogue or similar.
Technically speaking, you are not forced to do this replacement because
rails-ujs
recognizes that you still use jQuery and sets up the$.rails = Rails
link for you. But why would you want to use that with a vanilla JS library?
Also, it is a good time now to get rid of jQuery stuff in your UJS customizations code, if you like.
Dealing with AJAX event handlers
This is where the migration starts to be a bit tricky…
The new event parameters syntax
The syntax for additional parameters of the AJAX event handlers has changed considerably in rails-ujs
. The event itself is now implemented as a CustomEvent
and all the extra parameters (the data
, status
and xhr
) that were previously sent to the handlers as separate arguments are now bundled into the event.detail
attribute (as an array). So instead of:
$(document).on("ajax:success", function(event, data, status, xhr) {
...
});
you should write something like:
$(document).on("ajax:success", function(event) {
var data = event.detail[0];
var status = event.detail[1];
var xhr = event.detail[2];
...
});
The content of the event.detail
for each particular event type is nicely documented in the official guides.
Migrating jQuery events to UJS events
A small surprise may hit you if you mixed handling jQuery AJAX events (such as ajaxSuccess
, ajaxComplete
, etc.) with UJS AJAX calls in your code. Previously you may have used handlers for jQuery events (e.g. ajaxSuccess
) as well as UJS events (e.g. ajax:success
) quite interchangeably, since jQuery was always used under the hood and thus both events were always triggered. But this is not true any more and you must convert all jQuery events to the corresponding UJS events.
Of course this only applies to AJAX calls that originate from UJS, e.g. from specifying
remote: true
in your forms. If you use custom AJAX calls made via the jQuery$.ajax()
method, you should still handle the jQuery events, such asajaxSuccess
.
So, for instance, the ajaxSuccess
jQuery event handler could be rewritten to the UJS variant as follows:
// old form (jQuery event)
$(document).ajaxSuccess(function(event, xhr, options, data) {
...
}
// ⟶ new form (UJS event)
$(document).on("ajax:success", function(event) {
...
}
Note that the jQuery events use yet another set of extra parameters so rewrite them carefully and check the jQuery docs if unsure.
Watch out for proper AJAX data type
In jquery-ujs
, the AJAX call response body wasn’t processed in any way, it was simply passed to the appropriate event handler. rails-ujs
however, tries to do some basic response processing:
- it parses JSON responses into a JavaScript object
- it parses HTML / XML / SVG responses into a
Document
object - most importantly, it automatically executes JavaScript responses.
Especially the third option may cause unexpected behavior if you weren’t careful enough for the AJAX data type. By default, rails-ujs
sets the data type of an AJAX call to "script"
which tells it to expect a JavaScript response from the server and execute the response when received.
Now, if your server tries to send anything else than JavaScript (e.g. some HTML content), the AJAX will suddenly fail as the UJS library tries to execute the JavaScript that it expects. To fix that, either ensure that the server returns the proper data type (perhaps a Server-generated Javascript Response, SJR) or change the expected data type of the AJAX call via the data-type
attribute. The supported data types are "text"
(for plaintext responses), "html"
, "xml"
, "json"
and the default "script"
(see the sources for details).
Don’t expect to trigger()
UJS actions any more
Suppose you have a link with UJS remote handling (via remote: true
attribute) and you want to “click” the link programmatically. Previously, you could simply call $('a#my-link').trigger('click')
and this would trigger the AJAX call. In rails-ujs
, none of this works any more.
The simple explanation is that the jQuery trigger()
function can only trigger events handled again in jQuery, which they were in jquery-ujs
. Since rails-ujs
is completely jQuery-free, you must convert such code to native events dispatching instead. In our simple example, you can just call the native click()
method to trigger the AJAX, other times you might want to look at the dispatchEvent()
method.
Final notes
Overall, after going through all this in our code base, the migration felt like a fixing task too, as it forced us to fully understand and correct pieces of code that we didn’t care for enough in the past. That alone justified the migration efforts for us!
I hope the little gotchas described here will not stop you from upgrading your code to rails-ujs
. They shouldn’t, after you’ve read all this! ;-) Thanks!
Top comments (0)