First, an example
class PostsController < ApplicationController
@posts = Post.all
@post = Post.find params[:id].to_i
respond_to do |format|
#.. Create a post
respond_to do |format|
<%= @posts.each do |post| %>
<%= render_link_to_post(post) %>
<% end %>
var $post = $("<%= j render_link_to_post(@post) %>")
var $post = $("<%= j render("overview", post: @post) %>")
<%= @post.title %>
<%= @post.body %>
link_to post.title, post_path(post), remote: true
In this example, when someone clicks on the link, it will call /posts/:id. From there, if the request is AJAX'ed, it will render show.js.erb and eval the code in the template. If not, it will call show.html.erb. There is also a template named created.js.erb that will be called when you create a resource through an AJAX form. The form was left out to keep the example as light as possible.
URL helps you locate your bug
So you have a bug when you click a link. Using this technique, you can start looking at the URL to know the origin.
posts/:id is associated to the JS template located at app/views/posts/show.js.erb, for example.
Exactly like what you would expect when dealing with HTML views.
Web pages are alive
Look at the example above. When you create a new post using an AJAX form, it will prepend the list with a link to your post. Look at the method used: It's the same as the one when you call /posts. It's not a fluke. You want to avoid code duplication because this way, ajax uses the same component a normal request would except that it injects the result in the current page.
This means that after you create the new post, if you refresh the page, it will show the same list of posts, because it uses the same mechanism.
Reusing the same view logic
This is an extension of the previous point. Since you generate your HTML on the server side, you will find a lot of code duplications in your views. It's going to be easy for you to DRY your code into helpers and decorators.
Since everything is generated from the same place, HTML modification is done in one place only. In the example, if I would like to replace the link in the list by a small description of each post, I could change the implementation details of render_link_to_post (Obviously a bad function name, bare with me here) to accomodate the changes.
The ajax behaviour and the normal URL request would then be updated to the new version.
- Loading closure as the response of a user's action on a page.
Nice thing about this is that you have more control over the content you are serving. If you feel an action becomes sluggish because there's a lot of code being parsed, you can transfert the load by abstracting some code off to sprockets.
Load what you need, when you need it
Look at the example above. create.js.erb is the one that fades the element in. I could have wrapped a nice function and all, but what's the point if people usually don't create new post.
This gives you a nice balance to play with. When something is used a lot (i.e more than once per user per page), load it with your other library using Sprocket like you would normally do. Everything else should be loaded along the HTML it acts upon.
Repeatable actions (debugging purposes)
What I do sometimes is that I remove the closure to allow me to access the variable that are in used. This way, I can narrow down a bug by manipulating variables inside the console.
Important! CSRF protection
Vlado Cingel commented about some security concerns. When I wrote this, I knew CSRF token were passed with jquery_ujs so I expected rails to actually validates it. It turns out itdoesn't when it's a GET request, and it's how it's supposed to be.
Adding CSRF protection to your callback is quite easy (assuming you use Warden and/or Devise). Also, Rails 3 and 4 uses different implementation details handling CSRF but I believe this code below should work nonetheless. Consider that this code on Rails 4 will override the use of protection methods.
class ApplicationController < ActionController::Base
prepend_before_filter :verify_authenticity_token, :if => :js?
!protect_against_forgery? || request.head? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
You need to remember that this is how I do it, your mileage may vary. I hope you enjoyed this post and that it gave you the motivation to try it out yourself.
If you do try it out, give me a shout on twitter to tell me how it went!
Get more ideas like this to your inbox