purpose
provide DRY solution for rendering (or redirecting) from controller on invalid request, such as failed validation on create/update
requirements
- jquery js framework
- notifications helper from my earlier post about custom ajax helper:
http://tech.maysora.com/2010/11/rails-custom-ajax-helper-for-jquery.html
codes
application_controller.rb
# render invalid request to show error message depend on request type
# example:
# render_invalid "Update failed."
# ajax: show error notification with message "Update failed."
# html: simply render text "Update failed."
# render_invalid "Save failed.", :render => {:action => "new"}
# ajax: show error notification with message "Save failed."
# html: render action new with "Save failed." flash message
# render_invalid "Save failed.", :render => {:action => "new"}, :flash => false
# same as above but without flash for html
# render_invalid "You can't do that.", :redirect_to => {:action => "index"}, :flash => false, :status => :unauthorized
# same as above but redirecting to index instead of rendering new for html
# and using unauthorized(401) status instead of not_acceptable(406)
# render_invalid "Update failed.", :object => @user, :fields_wrapping => true
# ajax: show error notification with addition of object's error messages and wrap error fields with div class fieldWithErrors
# html: same as ajax minus fields wrapping
def render_invalid message="Error.", options={:flash => true}
options[:status] ||= :not_acceptable
message += "#{options[:object].errors.full_messages.join('
')}
" if options[:object].present?
respond_to do |format|
format.js do
render(:update, :status => options[:status]) do |page|
page << notification_error(message)
if options[:object].present? && options[:fields_wrapping]
page.rjs_clean_error_fields request
page.rjs_apply_error_fields options[:object], request
end
end
end
format.html do
if options[:render].present?
flash.now[:error] = message if options[:flash]
render options[:render].try(:merge, {:status => options[:status]}) || {:text => message, :status => options[:status]}
elsif options[:redirect_to].present?
flash[:error] = message if options[:flash]
redirect_to options[:redirect_to]
else
render :text => message, :status => options[:status]
end
end
end
end
application_helper.rb (only if you use ajax error fields wrapping)
def rjs_apply_error_fields object, request
klass_name = object.class.name.underscore
wrapper = ""
object.errors.each do |attr, msg|
attr = attr.split(".")
page << "var $fields = $(), $container = $('\##{klass_name}_#{object.id || "new"}');"
page << "if(!$container.length) $container = $('form[action=\\'#{request.request_uri}\\']');"
page << "if(!$container.length) $container = $(document);"
if attr.length > 1
object.send(attr.first).each_with_index do |x,i|
page << "$fields = $fields.add('[id^=#{klass_name}_#{attr.first}_attributes][id$=#{attr.last}]:eq(#{i}),label[for^=#{klass_name}_#{attr.first}_attributes][for$=#{attr.last}]:eq(#{i})', $container)" if x.errors.on(attr.last)
end
else
page << "$fields = $('label[for=#{klass_name}_#{attr}],\##{klass_name}_#{attr}', $container);"
end
page << "if(!$fields.parent('.fieldWithErrors').length)"
page << "$fields.wrap('#{wrapper}')"
end
end
def rjs_clean_error_fields request
page << "$('.fieldWithErrors.rjs_#{request.request_method}_#{request.request_uri.gsub(/_=\d+($|&)/, "").parameterize} *').unwrap();"
end
usage
users_controller.rb (using non ajax form)
#...
def create
@user = User.new(params[:user])
unless @user.save
render_invalid "Failed to register.", :object => @user, :render => {:action => "new"}
else
flash[:info] = "Registration success, please check your email to activate."
redirect_to login_path
end
end
#...
users_controller.rb (using ajax form)
#...
def update
@user = current_user
unless @user.update_attributes(params[:user])
render_invalid "Profile update failed.", :object => @user, :fields_wrapping => true
end
end
#...
and update.js.rjs (for success behavior)
page << notification_info "Profile updated."
page.rjs_clean_error_fields request # used for cleaning error fields wrapping
#... other necessary action ...
additional tips
depending on your site style, it might be a good idea to change the div wrapper in rjs_apply_error_fields helper to span.
you can also change rails standard error field wrapper for non-ajax form, see
this post