Recently, like everyone else, we’ve been putting some AJAX in our apps.
Now in Rails you have 2 ways to respond to AJAX requests:
I like either way but I found out that responding with JavaScript that the client automatically executes results in some cleaner and simpler views. Plus using RJS, I can write my JavaScript in Ruby. I like this since I’ve already gotten used to writing my SQL in Ruby via migrations.
Let’s take the simple example of an AJAXified form where the form to create a new record is on the same page that lists all records, in other words on #index there’s a form that POSTs to #create.
Say in this app we’re creating users that have nothing more than a name.
class User < ActiveRecord::Base
validates_presence_of :name
end
Schema:
users (id, name)
Here’s our controller:
class UsersController < ApplicationController
def create
@user = User.new params[:user]
respond_to do |wants|
if @user.save
wants.html { redirect_to users_path }
wants.js
else
wants.html { render :action => :index }
wants.js
end
end
end
def index
@users = User.find :all
end
end
create.rjs
if @user.valid?
page.insert_html :top, 'users', render(:partial => 'user',
:object => @user)
page[:errors].replace_html ''
page[:users_form].reset
else
page[:errors].replace_html error_messages_for(:user)
end
I don’t like the fact that there’s an ‘if’ in the action and in the create.rjs template. Now in the normal non-AJAX version of the #create action I just render the #index action’s view if the save fails so there’s only 1 ‘if’. 2 ‘if’s are necessary in the AJAX version of the #create action because there’s only 1 RJS view used (technically there’s only 1 view, index.rhtml used in the non-AJAX version because its used for both success and failure).
I’ve also seen alternative implementations of #create like this:
class UsersController < ApplicationController
def create
@user = User.new(params[:user])
@saved = @user.save
respond_to do |wants|
if @saved
wants.html { redirect_to users_path }
wants.js
else
wants.html { render :action => :index }
wants.js
end
end
end
def index
@users = User.find :all
end
end
create.rjs
if @saved
page.insert_html :top, 'users', render(:partial => 'user',
:object => @user)
page[:errors].replace_html ''
page[:users_form].reset
else
page[:errors].replace_html error_messages_for(:user)
end
This results in the same amount of ‘if’s but now we have this nice ugly boolean flag instance variable telling us the success or failure of the #save call. In my endless quest to remove all conditional logic from software, what I want is another RJS view to handle the case in which the save fails, so I can get rid of the 2nd ‘if’ in my create.rjs view.
Let’s try:
class UsersController < ApplicationController
def create
@user = User.new params[:user]
respond_to do |wants|
if @user.save
wants.html { redirect_to users_path }
wants.js
else
wants.html { render :action => :index }
wants.js { render :action => :index }
end
end
end
def index
@users = User.find :all
end
end
index.rjs
page[:errors].replace_html error_messages_for(:user)
create.rjs
page.insert_html :top, 'users', render(:partial => 'user',
:object => @user)
page[:errors].replace_html ''
page[:users_form].reset
What I added to UsersController#create is in the else branch:
wants.js { render :action => :index }
However this is just going to respond back with index.rhtml and not respond with index.rjs which has the RJS to display the errors on the page.
I thought maybe I could pass a :format parameter like:
wants.js { render :action => :index, :format => :js }
But no, this didn’t work either.
After some more trial and error I found out the following works:
class UsersController < ApplicationController
def create
@user = User.new params[:user]
respond_to do |wants|
if @user.save
wants.html { redirect_to users_path }
wants.js
else
wants.html { render :action => :index }
wants.js { render :action => 'index.rjs' }
end
end
end
def index
@users = User.find :all
end
end
I changed that same 1 line in UsersController#create this time to:
wants.js { render :action => 'index.rjs' }
Now actions for handling both AJAX and non-AJAX requests contain the same amount of conditional logic and follow the same pattern.