This blog post is for Rails developers that want to use Ajax in their application using Unobtrusive JavaScript. This is divided into these parts:
Introduction
Create a new Rails app
Add a remote form
Tutorial#create action
Bind on ajax: success
Effects
Delete using Ajax
Conclusion
Note: Rails has support for using Javascript frameworks like Vue, React, and StimulusJS through Webpacker. At some point, a framework might be more suitable for your application. Use this blog post to compare Rails UJS and frameworks. In the future, I will write about these frameworks and how to use them with Rails.
Introduction
JavaScript libraries like jQuery makes it easier to make Ajax requests. Rails takes that a step further and you don’t even have to make the Ajax request at all. Previously, you need to add remote: true
to your forms. form_with
was introduced on Rails 5.1 that by default uses Ajax. You have to add local: true
if you don’t want to use Ajax.
This is a blog post on using Ajax for forms and links with jQuery. I have also blogged about jQuery and Rails in the past. Check this post for a basic introduction to jQuery and Rails.
Even though you can use jQuery to make an Ajax request, we’ll use the built-in Rails features to make the Ajax requests for us. This is done through a technique called Unobtrusive JavaScript. The HTML forms and links that Rails helpers generate contain data-remote=true
. Rails will look for elements marked as remote and make the Ajax requests when the form is submitted or the link is clicked.
Rails 5.1 removed jQuery for the UJS feature (Unobtrusive JavaScript) and uses its own rails-ujs
code. In this post, we will remove rails-ujs
and use the jquery-rails
gem. The only reason for this is I want to use some jQuery features that’s not related to UJS. In a future post, I’ll use the default rails-ujs
.
Create a new Rails app
This section is a step by step guide to creating a new Rails app. If you just want to read the code, you can skip this section and go to the next one.
Generate a new Rails 5.1 application named tutorial app. This is a simple app that displays a list of tutorials saved in the database.
rails new tutorialapp --skip-turbolinks
Starting Rails 5.1, jQuery is not included by default. Let’s add it to the Gemfile.
gem 'jquery-rails'
The jquery-rails gem contains the jQuery files. Install the gem with
cd tutorialapp
bundle install
To include the jQuery files in our application, we will require a few files on app/assets/javascripts/application.js
. We’ll also remove rails-ujs
since we’re using jquery_ujs
for the same purpose.
//= require jquery3
//= require jquery_ujs
//= require_tree .
Now, let’s generate the tutorial resource which will create the Tutorial model and controller and update the routes. The model will have a title and a url, both Strings.
rails g resource tutorial title:string url:string
On app/controllers/tutorials_controller.rb
, add the index action.
def index
@tutorials = Tutorial.all
end
On the index view app/views/tutorials/index.html.erb
, display all the tutorials.
<h1>Tutorials</h1>
<ul id=tutorials>
<% @tutorials.each do |tutorial| %>
<li><%= tutorial.title %></li>
<% end %>
</ul>
Before we start the application, let’s add a few records so our app has data to display. Add the following to db/seeds.rb
.
Tutorial.create(title: 'Encrypted Rails Secrets on Rails 5.1', url: 'https://www.engineyard.com/blog/encrypted-rails-secrets-on-rails-5.1/')
Tutorial.create(title: 'Using Docker for Rails', url: 'https://www.engineyard.com/blog/using-docker-for-rails-in-a-production-environment/')
Tutorial.create(title: 'Running a Rails App in Kubernetes', url: 'https://www.engineyard.com/blog/kubernetes-tutorial-running-a-rails-app-in-kubernetes/')
Then run the migration and seed commands.
bin/rake db:migrate
bin/rake db:seed
Start the application and go to the tutorials index page.
bin/rails server
On your browser, go to http://localhost:3000/tutorials
Add a remote form
Instead of creating a new page with a form, we’ll add the form to the existing index page. Our goal is for the page to NOT reload when you click Submit. An Ajax request will be made, the tutorial will be saved to the database, and the new tutorial will appear on the page. This will be faster and give a better user experience than reloading the page.
Let’s edit app/views/tutorials/index.html.erb
that we created earlier.
<h1>Tutorials</h1>
<ul id=tutorials>
<% @tutorials.each do |tutorial| %>
<li><%= tutorial.title %></li>
<% end %>
</ul>
<%= form_with model: Tutorial.new, data: {'js-tutorial-form' => true} do |form| %>
Title <%= form.text_field :title %><br>
URL <%= form.text_field :url %><br>
<%= form.submit %>
<% end %>
form_with
by default creates a remote form. We also added the data attribute js-tutorial-form
. The generated HTML is
<form data-js-tutorial-form='true' action='/tutorials' accept-charset='UTF-8' data-remote='true' method='post'>
data-remote
is used by Rails, specifically jquery_ujs
in our app, to make an Ajax request. data-js-tutorial-form
is a custom data attribute that we will use later.
Tutorial#create action
The create
action on the Tutorial controller saves the tutorial record to the database. After that, on non-Ajax requests, you would usually redirect to the tutorial page or the index page. But here, we’ll render a partial for the tutorial.
def create
@tutorial = Tutorial.new(params[:tutorial].permit(:title, :url))
if @tutorial.save
render partial: 'tutorial', locals: {tutorial: @tutorial}
end
end
The partial app/views/tutorials/_tutorial.html.erb
displays the title of the tutorial inside <li></li>
.
<li><%= tutorial.title %></li>
Bind on ajax:success
The tutorial partial isn’t automatically displayed on the page. When the Ajax request is successful, Rails fires the ajax:success
event. We need JavaScript code to bind to this event.
On application.js, add
$(document).ready(function() {
$('[data-js-tutorial-form]').on('ajax:success', function(event, data, status, xhr){
$('#tutorials').append(xhr.responseText);
});
});
We put our code inside $(document).ready()
to make sure that the DOM (Document Object Model) is ready.
We use $('[data-js-tutorial-form]')
to get the form then call on
with ajax:success
and a function.
This function has 4 arguments. Since we bind on the ajax:success
event, we know that the request was successful. In this case, we only use xhr.responseText
which is the part that we rendered from the create
action.
$('#tutorials')
is the ul
element that displays all our tutorials. We append the new tutorial.
Effects
The code above appends the new tutorial but it can be hard to notice. Let’s add the fadeIn
effect from jQuery.
$(document).ready(function() {
$('[data-js-tutorial-form]').on('ajax:success', function(event, data, status, xhr){
var tutorial = $(xhr.responseText).hide();
$('#tutorials').append(tutorial);
tutorial.fadeIn(2000);
});
});
We hide the new tutorial first, append it, then call fadeIn
with an argument of 2000 milliseconds.
There are other effects that you can use like a highlight but that requires jQuery UI.
Delete using Ajax
Let’s add a delete link that will delete the tutorial from the database. We’ll add the link to the partial and we’ll rewrite the index page to use the partial as well.
On app/views/tutorials/index.html.erb
, change the ul
portion to
<ul id=tutorials>
<%= render @tutorials %>
</ul
On app/views/tutorials/_tutorial.html.erb
, add the delete link. We’ll also add a data attribute to the li
element.
<li data-js-tutorial-id=<%= tutorial.id %>>
<%= tutorial.title %> <%= link_to 'Delete', tutorial, remote: true, method: :delete, data: { confirm: 'Are you sure you want to delete '#{tutorial.title}'?' } %>
</li>
On app/controllers/tutorials_controllers.rb
, add the destroy action.
def destroy
@tutorial = Tutorial.find(params[:id])
@tutorial.destroy
render json: @tutorial
end
On app/assets/javascripts/application.js
, bind to ajax:success
.
$('[data-js-tutorial-id]').on('ajax:success', function(event, data, status, xhr){
var tutorial_id = xhr.responseJSON.id;
$('[data-js-tutorial-id=' + tutorial_id + ']').hide();
});
Each tutorial has a data-js-tutorial-id
attribute so $('[data-js-tutorial-id]')
is used to bind to the ajax:success
event.
xhr.responseJSON
returns a json of the tutorial which was generated by the destroy
action.
$('[data-js-tutorial-id=' + tutorial_id + ']').hide()
hides the tutorial.
Conclusion
jquery-ujs
and Rails UJS, in general, makes it easy to use Ajax on your Rails application. The use of data attributes allows you to put all your JavaScript code on the assets directory. You can use IDs and classes but it is not clear which classes are used for styles and which ones are used by JavaScript.
Do you use Rails UJS or are you using a JavaScript framework? We like to hear from you in the comments.