Ajax Validations with Rails and jQuery (Screencast)
Server side validations are a big part of the modern web and I'm consistently surprised at the number of sites that do not use this technique. Perhaps it's an issue of scalability that I'm not understanding, but for most of the applications I'm working on this technique works just fine.
The Ruby Code
# config/routes.rb map.resources :contacts, :member => {:validate => :post} # app/models/contact.rb class Contact < ActiveRecord::Base validates_presence_of :name, :email validates_length_of :name, :within => 2..15, :too_long => "Must be under 15 characters", :too_short => "Must be over 2 characters" validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i validates_uniqueness_of :email def self.validate_field(field, value) validity = Contact.new(field => value) validity.valid? if validity.errors.on field ajaxResponse = {:valid => false, field.to_sym => validity.errors[field]} else ajaxResponse = {:valid => true} end end end # app/controllers/contacts_controller.rb def validate if params[:field].blank? || params[:value].blank? render :nothing => true else @valid = Contact.validate_field(params[:field], params[:value]) render :json => @valid end end
The Views
# apps/layouts/contacts.html.erb || apps/layouts/application.html.erb <%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %> <%= javascript_include_tag 'jquery-1.4.2.min', 'application' %> <%= yield :scripts %> # apps/views/contacts/new.html.erb <% content_for :scripts do %> <%= javascript_include_tag 'new_contact' %> <% end %> <h1>New contact</h1> <% form_for(@contact) do |f| %> <%= f.error_messages %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.label :email %><br /> <%= f.text_field :email %> </p> <p> <%= f.submit 'Create' %> </p> <% end %>
The Javascript
// Application.js function validate_field(name, value, field) { var postData = { }; postData['field'] = name; postData['value'] = value; $.post('/contacts/validate', postData, function(data) { if(data.valid==false) { $(field).parent('p').addClass('fieldWithErrors'); $(field).next('span').remove(); $(field).after('<span class="inlineError">'+ data[name] +'</span>'); }else{ $(field).parent('p').removeClass('fieldWithErrors'); $(field).next('span').fadeOut(200, function() { $(this).remove(); }); } }, 'json'); } // General delay function, used to watch keyups, // Taken from: http://stackoverflow.com/questions/1909441/jquery-keyup-delay var delay = (function(){ var timer = 0; return function(callback, ms){ clearTimeout (timer); timer = setTimeout(callback, ms); }; })(); // Checks an array for a passed value Array.prototype.in_array = function(p_val) { for(var i = 0, l = this.length; i < l; i++) { if(this[i] == p_val) { return true; } } return false; } // Allows for AJAX // Taken from http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery $(document).ajaxSend(function(event, request, settings) { if (typeof(AUTH_TOKEN) == "undefined") return; // settings.data is a serialized string like "foo=bar&baz=boink" (or null) settings.data = settings.data || ""; settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN); }); // new_contact.js $(document).ready(function() { $("#contact_name").keyup(function() { delay(function() { var name = $("#contact_name").val(); validate_field('name', name, $("#contact_name") ); }, 600); }); $("#contact_email").keyup(function() { delay(function() { var email = $("#contact_email").val(); validate_field('email', email, $("#contact_email") ); }, 600); }); });
-
Dino
6:41 PM on Friday, September 24th, 2010
Great tutorial works great with rails 2.3.8 but in rails 3 it gives me an error
ActionController::RoutingError (No route matches "/contacts/validate"):
Rendered /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.7ms)any ideas?
-
Emerson Lackey
5:33 AM on Saturday, October 2nd, 2010
@Dino,
In the process of upgrading a few of my applications to Rails3 (including this blog). I'll hopefully have some time to take notes along the way and will try to consider a Rails 3 approach to this screencast/article.
-
ionas
1:30 AM on Friday, October 15th, 2010
1. Why post and not put, one can send as many validations as he/she wishes, it won't change the data on the server until you hit submit? You could even use GET, would that not work?
2. Doing it for each field is okay if you only have "some" fields, but if you have a ton of fields, it would probably be better to validate all of them (and somehow add an exception). JQuery for doing that would be great.
3. Are you still using exactly this? I am really looking forward to a Rails3 version of this!
-
ionas
1:31 AM on Friday, October 15th, 2010
@ Dino
Looks like you didn't setup the the Rails3 routing correctly.
-
ionas
1:32 AM on Friday, October 15th, 2010
Also: You could instantly trigger the validation on losing fields user focus (tab, clicking in the next field) and up the time from 600 to 800ms for instance. Less server stress even.
-
Eli B
10:44 PM on Thursday, December 16th, 2010
Hey Emerson-
Thanks a lot for the code here; works like a charm, except for a few things.
-If you are using this with Devise 1.0.8, you need to do put the controller code in the ApplicationController or elsewhere as a :before_filter, since Devise for Rails 2 doesn't support custom actions.
-I'm not sure this works if you're trying to validate a date, because of the way dates are selected (in multiple select fields, each with different ids).
-I also found that my method of validating birthdates added a ~30 sec delay to the request, so I added ':unless => "birthdate.nil?"' to skip it.
I'm using this method: http://snippets.dzone.com/posts/show/837
I think this happens because when it's nil, it checks for every case in that list? (e.g. it's *the* worst case for a crappily-written linear time algo). Anyone know a better way to do this? (purely for the sake of it, will never happen with birthdate.nil? switch) -
Eli B
10:45 PM on Thursday, December 16th, 2010
Lol, I was just accused of being a spambot by your site, wonderful :)
-
Eli B
10:48 PM on Thursday, December 16th, 2010
(I am not a spambot...resubmitting)
Hey Emerson-
Thanks a lot for the code here; works like a charm, except for a few things.
-If you are using this with Devise 1.0.8, you need to do put the controller code in the ApplicationController or elsewhere as a :before_filter, since Devise for Rails 2 doesn't support custom actions.
-I'm not sure this works if you're trying to validate a date, because of the way dates are selected (in multiple select fields, each with different ids).
-I also found that my method of validating birthdates added a ~30 sec delay to the request, so I added ':unless => "birthdate.nil?"' to skip it.
I'm using this method: http://snippets.dzone.com/posts/show/837
I think this happens because when it's nil, it checks for every case in that list? (e.g. it's *the* worst case for a crappily-written linear time algo). Anyone know a better way to do this? (purely for the sake of it, will never happen with birthdate.nil? switch) -
Eli B
11:02 PM on Thursday, December 16th, 2010
Hey Emerson-
Thanks a lot for the code here; works like a charm, except for a few things.
-If you are using this with Devise 1.0.8, you need to do put the controller code in the ApplicationController or elsewhere as a before filter, since Devise for Rails 2 doesn't support custom actions.
-I'm not sure this works if you're trying to validate a date, because of the way dates are selected (in multiple select fields, each with different ids).
-I also found that my method of validating birthdates added a ~30 sec delay to the request, so I added ':unless => "birthdate.nil?"' to skip it.
http://snippets.dzone.com/posts/show/837
I think this happens because when it's nil, it checks for every case in that list? (e.g. it's the worst case for a crappily-written linear time algo). Anyone know a better way to do this? (purely for the sake of it, will never happen with the above switch) -
Eli B
11:53 PM on Thursday, December 16th, 2010
Hold the phone, so is there are a way to do this with information from multiple fields? This matters if you want to make sure the password confirmation field matches the password field.
-
Dorian
11:20 AM on Friday, February 4th, 2011
Thanks for this screencast, it's exactly what i was looking for in order to understand how to implement Ajax on a form with Rails 2.3
-
mynotes
3:54 AM on Friday, August 5th, 2011
first thank u for great screencast
is this working on rails 3?
bcoz i have plan to use this on my applicationregards,
mynotes