Tealeaf Academy Blog

Teaching Ruby on Rails Courses

The Detailed Guide on How Ajax Works With Ruby on Rails

This is a tutorial for ajax use with Rails conventions. For illustrative purposes, we’ll build a single-page task list app. For reference and convenience, I have created 2 github repos:

Static Task List: A textbook example on creating a basic and static task list. It also serves the purpose of demonstrating an app with excess navigation that can be significantly improved with ajax for an enhanced user experience.

Rai-jax: This tutorial serves an illustrative example of the significant improvement a little ajax and some styling can provide to an otherwise dull and static app.

About Ajax

Ajax (Asynchronous JavaScript and XML) is used as a mechanism for sending and retrieving data asynchronously (in the background). While XML can certainly be used with ajax, it is not limited to this format. The JSON format, for example, is more commonly used today, especially in the Rails community. There are significant advantages in using Ajax, which include better user interactivity. Ajax allows content on a page to be updated without having to re-render the entire page, making it a “seamless” experience.

Part One: Creating a New Task on the Index Page

Before we start, let’s take a quick look at our schema so that we know what we’re working with:

1
2
3
4
5
6
7
8
9
10
11
# schema.rb

ActiveRecord::Schema.define(version: 20140620130316) do

  create_table "tasks", force: true do |t|
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.string   "description", null: false
    t.datetime "deadline"
  end
end

After creating a Task model and then creating some tasks to play with, our Task Controller should look like this:

1
2
3
4
5
6
7
8
# tasks_controller.rb

class TasksController < ApplicationController

  def index
    @tasks = Task.all
  end
end

Instead of creating new.html.erb, let’s add a button somewhere on our index.html.erb that users can use to display a hidden form:

1
2
3
4
5
6
7
# index.html.erb

...

<%= link_to 'New Task', new_task_path, remote: true %>

...

Here we pass the remote: true option to disable the default Rails mechanism that would have otherwise navigated us to /tasks/new.

Before moving on, let’s quickly revisit our Task Controller and set it up to create new tasks with ajax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# tasks_controller.rb

class TasksController < ApplicationController
  before_action :all_tasks, only: [:index, :create]
  respond_to :html, :js

  # index action has been removed

  def new
    @task = Task.new
  end

  def create
    @task  = Task.create(task_params)
  end

  private

    def all_tasks
      @tasks = Task.all
    end

    def task_params
      params.require(:task).permit(:description, :deadline)
    end
end

I have removed the index action because I created a before_action filter that creates the @tasks instance variable for us automatically. Because we no longer have any logic in our index action, it is not necessary. Rails will automatically render the correct template, even without the presence of the action.

Noteworthy here is the respond_to method invoked near the top that will allow us to render both html and javascript responses with all of our controller actions. The respond_to method provides the ability to respond to the requests with many formats(i.e. csv, xml, etc…). This can be done for one or all actions in a controller. If we wanted to provide json only in our index action, we would write something like this:

1
2
3
4
5
6
7
8
def index
  @tasks = Task.all

  respond_to do |format|
    format.html
    format.json
  end
end

Now, choose a place on the index page to hide your form by passing a style attribute with the following:

1
2
3
4
5
6
# index.html.erb

...

<div id="task-form" style="display:none;"></div>
...

which will hide our form when the page is initially visited.

Next, create new.js.erb:

1
2
3
4
# new.js.erb

$('#task-form').html("<%= j (render 'form') %>");
$('#task-form').slideDown(350);

This is just an ERB template that generates Javascript instead of the HTML we’re used to. It basically translates to: “Find the element with an id attribute of task-form and at that point render the html in the form partial.” We typically do this in new.html.erb with:

1
<%= render 'form' %>

Since render is a Rails method, JavaScript doesn’t understand it and it has to be interpreted with ERB. The ‘j’ is syntactic sugar for <%= escape_javascript (render 'form') %>

1
2
3
4
5
6
7
# _form.html.erb

<%= simple_form_for @task, remote: true do |f|   %>
  <%= f.input  :description                      %>
  <%= f.input  :deadline                         %>
  <%= f.button :submit                           %>
<% end %>

This is the ‘form’ being rendered in new.js.erb with a remote: true option being passed in. In our form partial, we also pass the remote: true option that will execute an ajax POST request.

POST Request without Ajax:

rails POST request without ajax

POST request with Ajax:

rails POST request with ajax

Finally, we can wrap things up by rendering our new task list and hiding our form. This final step includes identifying where to render our list. Using the rai-jax app as an example, let’s look at what our final index.html.erb should look like at this stage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# index.html.erb

<div class="row">
  <div class="col-md-5 col-md-offset-1">
    <h2>Tasks</h2>
  </div>

  <div class="col-md-2 col-md-offset-4">
    <%= link_to new_task_path, remote: true do %>
      <button class="btn btn-default">New</button>
    <% end %>
  </div>
</div>

<div class="row">
  <div class="col-md-6 col-md-offset-2" id="task-form" style="display:none;"></div>
</div>

<div class="row">
  <div class="col-md-7 col-md-offset-1" id="tasks"><%= render @tasks %></div>
</div>

And we update our task list and hide our form with create.js.erb:

1
2
3
4
# create.js.erb

$('#tasks').html("<%= j (render @tasks) %>");
$('#task-form').slideUp(350);

Part Two: Updating a Task on the Index Page

As in Part One, let’s start by visiting our Task Controller and setting it up for updates:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# tasks_controller.rb

class TasksController < ApplicationController
  before_action :all_tasks, only: [:index, :create, :update]
  before_action :set_tasks, only: [:edit, :update]

  ...

  def update
    @task.update_attributes(task_params)
  end

  ...

  private

    ...

    def set_tasks
      @task = Task.find(params[:id])
    end

    def task_params
      params.require(:task).permit(:description, :deadline)
    end
end

Similar to Part One, we add an edit button with a remote: true option

1
2
3
4
5
6
7
8
9
# _task.html.erb

...

  <%= link_to edit_task_path(task), remote: true do %>
    <button>Edit</button>
  <% end %>

...

And, finally, our edit.js.erb and update.js.erb are the same as our new and update templates: edit.js.erb corresponds to new.js.erb and create.js.erb corresponds to update.js.erb.

Part Three: Deleting a Task on the Index Page

Our final updates to our Task Controller involves us providing the destroy action:

1
2
3
4
5
6
7
8
9
10
11
12
# task_controller.rb

class TasksController < ApplicationController
  before_action :all_tasks, only: [:index, :create, :update, :destroy]
  before_action :set_tasks, only: [:edit, :update, :destroy]

  ...

  def destroy
    @task.destroy
  end
end

When adding our delete button, two additional steps are required:

1
2
3
4
5
6
7
8
9
# _task.html.erb

...

<%= link_to task, remote: true, method: :delete,  data: { confirm: 'Are you sure?' } do %>
  <button>Delete!</button>
<% end %>

...

First, we pass in a method: :delete option; Second, we provide a courtesy confirmation to the user making sure they don’t delete anything by accident.

The last file we’ll create is destroy.js.erb and it will contain one line:

1
2
3
# destroy.js.erb

$('#tasks').html("<%= j (render @tasks) %>");

Conclusion

Seriously, Rails makes ajax easy. As I mentioned above, there’s a repo of this up on GitHub with all the styling I omitted for brevity and clarity here. They’re small touch ups from the static list app, but go a long way in significantly improving look, feel, and user experience.

Comments