How to share code between Javascript and Rails
Rails’ validations is great because it allows you to quickly implement the valid states of your models and at the same time have a ready-made way of displaying the errors to your users. For example, below is a screenshot of a registration form written with just a few lines of code:

However, this approach requires the server to perform the validation even for simple things like checking the length of a password. To improve response time (and thus the user experience), we use Javascript to pass some workload to the browser. For example, the popular jQuery framework has a validation plugin similar to that of Rails.

Here are the Rails and jQuery code snippets for our form validation:
validates_presence_of :login validates_length_of :login, :within => 3..40
$('#new_user').validate({
rules: {
'user[login]': {
required: true,
maxlength: 40,
minlength: 3
}
},
});
Notice the length condition of the login is repeated in Rails and jQuery, which violates the Don’t Repeat Yourself principle. To remedy this, one approach is to publish the constants from your Rails code as Javascript code. To do this, we need to refactor our model a bit and use a Rails helper method.
# User model
MIN_LENGTH = 3
MAX_LENGTH = 40
validates_presence_of :login
validates_length_of :login, :within => MIN_LENGTH..MAX_LENGTH
# in your helper
def js_user_format
format = {
"MAX_LENGTH" => User::MAX_LENGTH,
"MIN_LENGTH" => User::MIN_LENGTH
}
javascript_tag "var User=#{format.to_json}"
end
Then, in your form template:
<%= js_user_format %>
This will produce the following Javascript code in your page:
<script type="text/javascript">
//<![CDATA[
var User={"MIN_LENGTH": 3, "MAX_LENGTH": 40}
//]]>
</script>
You can now improve your jQuery code:
$('#new_user').validate({
rules: {
'user[login]': {
required: true,
maxlength: User.MAX_LENGTH,
minlength: User.MIN_LENGTH
}
},
});
This is just a simple example and I bet you can extend this one. For example, if you use regular expressions with validates_format_of, you can use the same regex to validate the format of the input in jQuery.
Update: One reader had a problem including Javascript in templates using HAML. You can include Javascript in HAML templates but there should be no indentation. Otherwise, HAML would complain of “illegal nesting”. Of course, no indentation would make it harder to read your Javascript. To preserve the whitespace, you can use multiline string. I also suggest you put it in a helper to isolate your Javascript code. If you can get away with it in your helper, the better.
# in helper
def js_foo
javascript_tag <<-eos
function onAfter() {
var current = $(this).attr('id');
if (current!="") {
$('.'+current).removeClass("active", 'timeout');
}
};
eos
end
# in haml template
= js_foo
Related posts:
- Rails 3 upgrade part 4: Prototype helpers and Javascript Rails 3 is embracing the unobtrusive Javascript (or UJS) mantra which is good because it is the right way; at the same time, it is bad because many applications will...
- Rails 3 upgrade part 3: Code fixes, views, and forms This is part 3 of my Rails 2 to Rails 3 upgrade experience. Part 1 is about the initial code upgrade and getting the application to boot while part 2...
- How to create Google-friendly URLs in Rails If you are building content-management systems or any applications with pages that you want to appear in a Google search, it is very important that you employ search engine optimization...
- Rails 3 upgrade part 1: Booting the application It’s time for another Rails upgrade! We all have our share of bad experiences and frustrations every time we upgrade a piece of software. Even for technical people who live...
- Rails 3 upgrade part 2: Routes In the previous post, I outlined the steps I took to upgrade and boot a Rails 3 application. This time, I share my experience upgrading the routes file. By the...
Thanks Greg..
I moved most of javascript on erb to helpers based on your code a few days ago.
Here’s a better way of handling javascript and haml (for those using it).
On head tag of layout, add:
=yield :head
then on helper:
def mail_gallery_js
content_for(:head) {
javascript_tag <<-eos
$(document).ready(function($) {
all_js_code_here
});
eos
}
Katz
29 Jul 09 at 7:54 am
I prefer using just a helper so I could the js code anywhere in the page. For example, I put the jQuery validation after a form.
Greg Moreno
29 Jul 09 at 10:29 am