DEV Community

Matouš Borák for NejŘemeslníci

Posted on • Updated on

Migrating from jquery-ujs to rails-ujs

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 original jquery-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 with rails-ujs because it needs native DOM events which only rails-ujs triggers.
  • Unlike jquery-ujs, which is a single-file library, the current rails-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
Enter fullscreen mode Exit fullscreen mode

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 custom­izations 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) {
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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 as ajaxSuccess.

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) {

Enter fullscreen mode Exit fullscreen mode

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)