Ajax on Rails

Ajax on Rails

This is a tutorial for ajax use with Rails conventions. For illustrative purposes, we’ll build a single-page Product list 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.

Create a New Product 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
ActiveRecord::Schema.define(version: 20140620130316) do
  create_table "products", force: true do |t|
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.string   "name",        null: false
    t.string   "description", null: false
  end
end

After creating a Product model and then create some products to play with, our Product Controller should look like this:

products_controller.rb
1
2
3
4
5
class ProductsController < ApplicationController
  def index
    @products = Product.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:

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

<%= link_to 'New Product', new_product_path, remote: true %>

...

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

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

products_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ProductsController < ApplicationController
  before_action :all_product, only: [:index, :create]

  # index action has been removed

  def new
    @product = Product.new
  end

  def create
    @product  = Product.create(product_params)
  end

  private
  def all_products
    @products = Product.all
  end

  def product_params
    params.require(:product).permit(:name, :description)
  end
end

I have removed the index action because I created a before_action filter that creates the @products 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
  @products = Product.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:

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

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

...

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

Next, create new.js.erb:

new.js.erb
1
2
$('#product-form').html("<%= j (render 'form') %>");
$('#product-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 product-form and at that point render the html in the form partial.” We typically do this in new.html.erb with:

new.html.erb
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’) %>

_form.html.erb
1
2
3
4
5
<%= simple_form_for @product, 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.

Finally, we can wrap things up by rendering our new Product 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:

index.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="row">
  <div class="col-md-5 col-md-offset-1">
    <h2>Products</h2>
  </div>

  <div class="col-md-2 col-md-offset-4">
    <%= link_to new_product_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="product-form" style="display:none;"></div>
</div>

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

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

create.js.erb
1
2
$('#products').html("<%= j (render @products) %>");
$('#product-form').slideUp(350);

Update a Product on the Index Page
As in Part One, let’s start by visiting our Product Controller and setting it up for updates:

products_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ProductsController < ApplicationController
  before_action :all_products, only: [:index, :create, :update]
  before_action :set_products, only: [:edit, :update]

  ...

  def update
    @product.update_attributes(product_params)
  end

  ...

  private
  ...

  def set_products
    @product = Product.find(params[:id])
  end

  def product_params
    params.require(:product).permit(:name, :description)
  end
end

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

_product.html.erb
1
2
3
4
5
6
7
...

  <%= link_to edit_product_path(product), 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.

Delete a Product on the Index Page
Our final updates to our Product Controller involves us providing the destroy action:

product_controller.rb
1
2
3
4
5
6
7
8
9
10
class ProductsController < ApplicationController
  before_action :all_products, only: [:index, :create, :update, :destroy]
  before_action :set_products, only: [:edit, :update, :destroy]

  ...

  def destroy
    @product.destroy
  end
end

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

_product.html.erb
1
2
3
4
5
6
7
...

<%= link_to product, 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:

destroy.js.erb
1
$('#products').html("<%= j (render @products) %>");

Seriously, Rails makes ajax easy. As I mentioned above.

So far so good, That’s it!!! See ya!!! :)