Using Ajax and jQuery in Rails 5 Apps

You can watch this tutorial as a screencast Using Ajax and jQuery in Rails 5 Apps. Create a new Rails 5 app without spring.

rails new ckl --skip-spring

Create a task model with name and complete fields.

rails g model task name complete:boolean

Change the migration file to make complete flag default to false.

class CreateTasks < ActiveRecord::Migration[5.0]
  def change
    create_table :tasks do |t|
      t.string :name
      t.boolean :complete, default: false, null: false

      t.timestamps
    end
  end
end

Create sample data in seeds.rb:

Task.create! name: "Meet Mr. Miyagi", complete: true
Task.create! name: "Paint the fence", complete: true
Task.create! name: "Wax the car"
Task.create! name: "Sand the deck"

Create a tasks controller with index and new actions.

rails g controller tasks index new 

The tasks controller looks like this:

class TasksController < ApplicationController
  def index
    @incomplete_tasks = Task.where(complete: false)
    @complete_tasks = Task.where(complete: true)
  end

  def new
    @task = Task.new
  end

  def create
    @task = Task.create!(allowed_params)

    redirect_to tasks_url
  end

  def update
    @task = Task.find(params[:id])
    @task.update_attributes!(allowed_params)

    redirect_to tasks_url
  end

  def destroy
    @task = Task.destroy(params[:id])

    redirect_to tasks_url
  end

  private

  def allowed_params
    params.require(:task).permit(:name, :complete)
  end
end

Define the resources in routes.rb:

Rails.application.routes.draw do
  resources :tasks

  root to: 'tasks#index'
end

Migrate and populate the database.

rails db:migrate
rails db:seed

The tasks/index.html.erb looks like this:

<h1>Task Dog</h1>
<%= link_to "New Task", new_task_path, id: "new_link" %>
<h2>Incomplete Tasks</h2>
<div class="tasks" id="incomplete_tasks">
  <%= render @incomplete_tasks %>
</div>
<h2>Comlpete Tasks</h2>
<div class="tasks" id="complete_tasks">
  <%= render @complete_tasks %>
</div>

Create the task partial:

<%= form_for task do |f| %>
  <%= f.check_box :complete %>
  <%= f.submit "Update" %>
  <%= f.label :complete, task.name %>
  <%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"} %>
<% end %>

The new.html.erb looks like this:

<h1>New Task</h1>
<%= render 'form' %>
<p><%= link_to "Back to tasks", tasks_path %></p>

The form partial, _form.html.erb:

<%= form_for @task do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

Check the source code at https://github.com/bparanj/ckl for css and the layout file changes. The todo app will work without any ajax, meaning the page will be refreshed due to redirect when you delete, create or update tasks. Let's now add Ajax to this basic todo app.

Ajaxify the Task App

Create new.js.erb:

$('#new_link').hide().after('<%= j render("form") %>');

Make the 'New Task' link render a form using ajax. Add

remote: true 

to the form partial.

<%= form_for @task, remote: true do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

and the 'New Task' link:

<%= link_to "New Task", new_task_path, id: "new_link", remote: true %>

You can now create a task with the inline form. You can see that there is a redirect after the task is created.

Started POST "/tasks" for ::1 at 2016-07-07 16:22:50 -0700
Processing by TasksController#create as JS
  Parameters: {"utf8"=>"✓", "task"=>{"name"=>"Buy a necklace"}, "commit"=>"Create Task"}
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "tasks" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Buy a necklace"], ["created_at", 2016-07-07 23:22:50 UTC], ["updated_at", 2016-07-07 23:22:50 UTC]]
   (2.0ms)  commit transaction
Redirected to http://localhost:3000/tasks
Completed 200 OK in 4ms (ActiveRecord: 2.3ms)

Started GET "/tasks" for ::1 at 2016-07-07 16:22:50 -0700
Processing by TasksController#index as HTML
  Rendering tasks/index.html.erb within layouts/application
  Task Load (0.3ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", false]]
  Rendered collection of tasks/_task.html.erb [6 times] (5.2ms)
  Task Load (0.2ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", true]]
  Rendered collection of tasks/_task.html.erb [2 times] (1.2ms)
  Rendered tasks/index.html.erb within layouts/application (12.0ms)
Completed 200 OK in 36ms (Views: 33.6ms | ActiveRecord: 0.5ms)

Change the create action to handle the ajax request:

def create
  @task = Task.create!(allowed_params)

  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

Create create.js.erb:

$('#new_task').remove();
$('#new_link').show();
$('#incomplete_tasks').append('<%= j render(@task) %>')

You can now create a new task. The new task goes to the bottom of incomplete tasks list. There is no redirect when a new task is created.

Started POST "/tasks" for ::1 at 2016-07-07 16:30:09 -0700
Processing by TasksController#create as JS
  Parameters: {"utf8"=>"✓", "task"=>{"name"=>"Buy a puppy"}, "commit"=>"Create Task"}
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "tasks" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Buy a puppy"], ["created_at", 2016-07-07 23:30:09 UTC], ["updated_at", 2016-07-07 23:30:09 UTC]]
   (0.4ms)  commit transaction
  Rendering tasks/create.js.erb
  Rendered tasks/_task.html.erb (1.2ms)
  Rendered tasks/create.js.erb (2.9ms)
Completed 200 OK in 10ms (Views: 6.1ms | ActiveRecord: 0.7ms)

The remove link redirects as you can see in the log file:

Started DELETE "/tasks/13" for ::1 at 2016-07-07 18:13:44 -0700
Processing by TasksController#destroy as HTML
  Parameters: {"authenticity_token"=>"M60nBritB5vowdT03ZeulVOcC0NkNAfBdsKswwj5/HtqS0cdYVk6KLktRGZmNiuCvK2ITkZ/fJ5lR/BfKp1Isg==", "id"=>"13"}
  Task Load (0.2ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ?  [["id", 13], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (0.3ms)  DELETE FROM "tasks" WHERE "tasks"."id" = ?  [["id", 13]]
   (0.5ms)  commit transaction
Redirected to http://localhost:3000/tasks
Completed 302 Found in 3ms (ActiveRecord: 1.0ms)

Started GET "/tasks" for ::1 at 2016-07-07 18:13:44 -0700
Processing by TasksController#index as HTML
  Rendering tasks/index.html.erb within layouts/application
  Task Load (0.1ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", false]]
  Rendered collection of tasks/_task.html.erb [1 times] (1.0ms)
  Task Load (0.1ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ?  [["complete", true]]
  Rendered collection of tasks/_task.html.erb [2 times] (0.9ms)
  Rendered tasks/index.html.erb within layouts/application (4.9ms)
Completed 200 OK in 26ms (Views: 24.5ms | ActiveRecord: 0.2ms)

Add the remote flag to the remove link in task partial.

<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>

Change the destroy action to handle the ajax call.

def destroy
  @task = Task.destroy(params[:id])

  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

Create destroy.js.erb:

$('#edit_task_<%= @task.id %>').remove();

Reload the tasks index page. Remove the task by clicking (remove). You will now see redirect in the log file:

Started DELETE "/tasks/1" for ::1 at 2016-07-07 18:26:33 -0700
Processing by TasksController#destroy as JS
  Parameters: {"id"=>"1"}
  Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (1.6ms)  DELETE FROM "tasks" WHERE "tasks"."id" = ?  [["id", 1]]
   (0.4ms)  commit transaction
  Rendering tasks/destroy.js.erb
  Rendered tasks/destroy.js.erb (0.4ms)
Completed 200 OK in 10ms (Views: 3.8ms | ActiveRecord: 2.2ms)

Let's mark a task as complete without update button. We will automatically submit the form when the checkbox is clicked. In tasks.js:

$(function() {
    $('.edit_task input[type=checkbox]').click(function() {
        alert('clicked');
    });
});

Reload the tasks index page and click on the check box. Change the javascript to submit the form when the checkbox is clicked and remove all the update buttons.

$(function() {
    $('.edit_task input[type=submit]').remove();
    $('.edit_task input[type=checkbox]').click(function() {
        $(this).parent('form').submit();
    });
});

Reload the page. Check any incomplete tasks and you will see it moved to the Complete tasks section. You can also check the complete tasks and it will go into the incomplete tasks section.

Create a new task and you will see the update button show up again. To fix this, change tasks.js:

jQuery.fn.submitOnCheck = function() {
    this.find('input[type=submit]').remove();
    this.find('input[type=checkbox]').click(function() {
        $(this).parent('form').submit();
    });
    return this;
}

$(function() {
    $('.edit_task').submitOnCheck();
});

In task partial, add:

remote: true:

option to the link_to method:

<%= form_for task, remote: true do |f| %>
  <%= f.check_box :complete %>
  <%= f.submit "Update" %>
  <%= f.label :complete, task.name %>
  <%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
<% end %>

Change the create.js.erb:

$('#new_task').remove();
$('#new_link').show();
$('#incomplete_tasks').append('<%= j render(@task) %>');
$('#edit_task_<%= @task.id %>').submitOnCheck();

Now handle the ajax call in the update action:

def update
  @task = Task.find(params[:id])
  @task.update_attributes!(allowed_params)

  respond_to do |f|
    f.html { redirect_to tasks_url }
    f.js
  end
end

Create update.js.erb to move the task to appropriate section:

<% if @task.complete? %>
  $('#edit_task_<%= @task.id %>').appendTo('#complete_tasks');
<% else %>
  $('#edit_task_<%= @task.id %>').appendTo('#incomplete_tasks');
<% end %>  

You can download the source code for this article from ckl.

Summary

In this article, we used Ajax and jQuery to create a better user experience for our simple todo app. Read the jQuery docs to learn how the jQuery methods work. You can also experiment with the sample code shown in jQuery docs.


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Using Ajax and jQuery in Rails 5 Apps


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.