kqb3xq
Last Updated: November 08, 2017
·
150.4K
· sebastialonso

Rails 4: How to partials & AJAX, dead easy

Consider the basic following scenario...

A view with two columns:

  • one with links of Category model instances

  • the other empty but eager to show all the Item instances belonging to each Category.

And you want to show all of that without reloading the page. I had never played much with partials in Rails, but they are really really convenient.

So I have my index method

#items_controller.rb
def index
    @items = Item.all
    @categories = Category.all
end

For the sake of brevity, I will simplify the views. This way you can also understand the idea and apply it to your own views.

The index view contains two render calls

<!-- items/index.html.erb -->
<div class="grid">
  <%= render 'sidebar_menu' %>

  <%= render partial: 'item_grid', locals: { items: @items} %>
</div>

The category links in the sidebar_menu partial are something like the following:

<%= link_to cat.name, fetch_items_path(:cat_id => cat.id), :remote => true %>

fetch_items_path is the route that leads to our custom javascript method, which will be described next.

#config/routes.rb
...
get "/fetch_items" => 'items#from_category', as: 'fetch_items'

For more info on how to build custom routes, check Rails amazing documentation.

The :remote => true is the most important part here, it allows the whole Ajax business in the first place.

The item_grid partial looks like:

<div>
  <div id="items_grid" >
    <%= render partial: 'items_list', locals: {items: items}  %>
  </div>
</div>

The subpartial items_list just renders a list of div boxes to show our Item instances.

<% items.each do |item| %>
  <div class="item_box">
    ...
  </div>
<% end %>

Now we need the method that will do the AJAX magic. For simplicity you could have something like this:

#items_controller.rb
def from_category
    @selected = Item.where(:category_id => params[:cat_id])
    respond_to do |format|
        format.js
    end
end

Notice the type of format, there's no html view because we don't need it. We're going through JS.
Therefore, we need to create a javascript file, which will repopulate the div in the second column.

//views/items/from_category.js.erb
    $("#items_grid").html("<%= escape_javascript(render partial: 'items_list', locals: { items: @selected } ) %>"); 

Let's look at this line with care.
I'm rendering the same partial I was rendering in items#index, and the local variable for the partial is now the array of Item instances that match a given category. The difference is that I'm doing this through AJAX, so there's no need to reload the entire page.

27 Responses
Add your response

15861

Hi, thanks for the article.
I don't understand the connection between 'fetchitemspath' and the 'fromcategory' action in your items controller.
In other words can you explain "fetch
items_path is the route that leads to our custom javascript method". I do not understand how that works.
Thank you!

over 1 year ago ·
15867

In Rails, you control the routes in the routes.rb file. I will add it to the pro tip.

over 1 year ago ·
16150

Thank you for writing this, it was super helpful. Just one question, what is the purpose of the :remote => true statement at the end of the link tag?

over 1 year ago ·
16157

As it's pointed out just beneath the routes file, the remote: true hash tell Rails that the anchor element must respond in Javascript format (triggerin the Ajax logic), instead of the usual HTML.

Note that the controller method has to respond in this format also, otherwise is of no use.

over 1 year ago ·
16815

This is the best tutorial / documentation I've seen for this. Making the content minimal and concentrating on the mechanism was a good idea. Well done!

over 1 year ago ·
16817

Thanks nruth! I wrote this down because I couldn't find anything like it.
Hopefully, people won't have to do the same long research I had to do!

over 1 year ago ·
17212

How would you do this if the target element wasn't a unique element with an id, but instead if there's multiple of them with the same class name, and you want to target just that one.

Somehow you have to get the clicked element. How do you do this when you use render js? It's possible if you do a full ajax.on click event, and call $(this) within that function, but otherwise, how do you do it using render js? Cause you may want to do something on the back end in the controller or model before executing the front end part.

over 1 year ago ·
17220

This is where you have to get creative with your javascript. Instead of using the remote: trueoption, I'd use an ajax call, withdataType: 'script'` so I can capture the Javascript behaviour returned by the controller, and/or complementing it with the logic in the success function.
With the ajax call you have greater freedom for sending parameters with the call (maybe a data-something field with the id?). If you somehow decide to send an identifier, you'd have to capture it in the controller and then use it in the response to successfully identified the correct element.

over 1 year ago ·
17652

i created a search page the problem is the search page has a partial which is not rendered in the start but when parameters filled and submitted then a table is rendered showing the result i am sending the parameters through AJAX but when the response comes back i am sending the response to the same controller for rendering adding an extra parameter so that it satisfies the if-else condition i placed in that controller so that the code which hit the back-end do not run ,in else block it renders the whole page search page again with form and table .i don't want to refresh the whole page,i want to render search table partial without refreshing. i tried to use respond_to |format| format.js and js.file for rendering in that div in search page but its not working ..so i am rendering whole page now. please help

over 1 year ago ·
17684

This is nice, thank you.

over 1 year ago ·
17707

Hi manikantasai, please write your question in StackOverflow, where you can add the code in context (which make the analysis easier) and reach a higher audience.

over 1 year ago ·
18331

Thank you! I've seen a lot of tutorials, but this one is really dead simple!

over 1 year ago ·
18560

Thanks for this useful tutorial.

over 1 year ago ·
18660

Thanks! This was very helpful.

over 1 year ago ·
18788

Beatiful post . It helped me so much. Thanks a lot !!

over 1 year ago ·
21179

HI, and for the URL changing, nothing?

I have problem also with Turbolinks 3. How to fix?

History.pushstate? Like in the 246 Railscast?

over 1 year ago ·
22322

Thanks man!

over 1 year ago ·
22594

just finished implementing a dynamic form with partials, based on selecting from a set of links, this guide, first time, took me 15 minutes to follow and adapt. You've got my sincere thanks, it's amazingly clear.

over 1 year ago ·
22605

Ruby is great but I hate using rails on the frontend because of this mess. Use angular and see how easy it is

over 1 year ago ·
22652

You can use render partial: 'foo', collection: @items and then remove the manual iteration you do inside the item partial.

over 1 year ago ·
23430

This was super helpful, thanks so much!

over 1 year ago ·
25001

This is a great article. There is another method that I used with prototype helper that allows you to render the js response in the controller method without having to create a js.erb like so:
render update to |page|
page.replace_html div, partial...

Is there a possible way of doing something like this without prototype helper?

over 1 year ago ·
25151

Really nice tutorial...............Good job

over 1 year ago ·
28245

I am facing problem with is code: not really a problem but something that baffles me, something that i must have missed out, this code works fine in development mode ie. rails s but breaks when i on production environment ie, rails s -e production

http://stackoverflow.com/questions/40069866/getting-html-response-wrong-in-production-json-javascipt-response-correct

over 1 year ago ·
29033

Need to add !!!!!!
format.js { render layout: false}

over 1 year ago ·
29107

Sebastian, nicely done article. I'm encountering an "ActionController::UnknownFormat" error, however, on the respond_do |format| line. I've researched this and can't find a fix. Any ideas what might cause this?

over 1 year ago ·
29286

Thanks for this!

over 1 year ago ·