My first Rails application
Posted: September 23rd, 2008 | Author: Pierre Olivier Martel | Filed under: Rails | View CommentsI’m mostly done developing my first Rails application and I thought I could talk about my first impressions in this blog post. I assume I will most likely be preaching to the converted and so many good things have been said already about the magic of Rails so I don’t plan adding much to it. I come from the Java world and I can only say the 60 hours development it took me developing this application in Rails (and I’m still a newbie) would have at least taken me twice the the time in Java. But enough with the greatness of Rails, I want to talk about the road bumps I encountered.
Some context
I did this application for a school that has a website showing schedules for over a hundred classes. Unfortunately, their server doesn’t support any dynamic technology and this is all good old HTML. In order to update the content, they had to manually cut and paste from the Excel schedule file to the HTML files each new semester. Useless to say that this is time consuming and very error prone.
So I built a Rails web application for them where you can define your courses, classes and groups. The application automatically generates the static HTML files and then upload them to server through FTP. So it’s like a CRUD with file reading and writing, downloading and uploading features.
First problem : Three levels of nested model mass assignment
This is something that gave me quite a headache. In my app, I have models called Classes, Courses and Groups. A class has many courses and a course has many groups. And furthermore, the order of the courses is important since it will change the display of the generated content.
I designed a single page to edit these nested models. It looks like this (it helps if you understand french!) :
It’s basically a CRUD on steroids. Add, delete, edit, change the order of three levels of nested models in on page! I really liked this mass editing approach, I thought it was more productive for the user so I didn’t want to change the whole layout just to fit some technological limitations.
Doing the first level was easy and straightforward. Coming to the second level, I fumbled a bit, did some googling and finally found this Railscast about complex forms. The screencast is out of date but the code displayed on the page (which was took from the book Advanced Rails Recipes) worked great for me.
The real challenge came when I was about to fit the third level of nesting. I didn’t find anything on the Internet about it. I first tried naively to change the form inputs names so that it could be interpreted by Rails correctly and transformed into a hash. For example, the group price text input would be named :
class[course][][group][][price]
Now that didn’t work at all. First, Rails doesn’t have the context of which group belongs to which course. And second, it didn’t seem as if Rails could handle both pairs of empty brackets. When editing an existing class, only the first one would get populated with appropriate ID.
The approach I took to solve the problem was to flatten the third level so that I had fields that looked like this :
class[course][][title] class[group][][price]
Before submitting the form , I dynamically changed the names of the fields belonging to the group model to add an index number between the brackets. This way I could group them and associate them with the corresponding course. I did this with jQuery :
// Correct the names so it matches Rails way of handling array values
function correctFieldNamesBeforeSend() {
$('.course').each(function(index) {
$('.groupRow input', this).each(function() {
name = $(this).attr('name');
newName = name.replace(/\[groups\]\[\]/, '[groups][' + index + ']')
$(this).attr('name', newName);
});
})
}
And finally in my class model, I took the groups from the hash to put them in the course hash to which they belong.
def new_course_attributes=(course_attributes)
course_attributes.each do |attributes|
addGroups(attributes)
courses.build(attributes)
end
end
def existing_course_attributes=(course_attributes)
courses.reject(&:new_record?).each do |course|
attributes = course_attributes[course.id.to_s]
if attributes
addGroups(attributes)
course.attributes = attributes
else
courses.delete(course)
end
end
end
# Merges the groups attributes in the course attributes
def addGroups(attributes)
attributes.merge! groups[attributes[:position]] if attributes
end
This did the trick but it was fairly complicated to implement. I wish there was a more simple way!
Second problem : Form validation
This second problem is somehow related to the first and got me cursing at my computer. Since some of my fields in both the first and second level needs validation, I had to handle the error messages. And to add to the pain of this nested validation, my application is written in French which means I cannot use the basic default validation messages. Rails is not yet very good with I18N.
The basic validation messages all display the name of the model attribute in front of the message. I was stunned to realize there was no built-in way to change this behavior. I found the custom error message plugin that offered a solution and this blog post that offered another.
I finally opted for the second more manual solution and implemented an ErrorsHelper overriding the default error_message_for method. I also put some code in there to get rid of the annoying ‘Course is invalid’ error message occurring when there is a validation error in my nested model.
Although it worked in the end, it still felt like a big a hack and a lot trouble that could have been avoided. My code also ended up being not so DRY with repeating validation code like this :
validates_presence_of :title, :message=>'Le champ Nom de l\'atelier est obligatoire.' validates_presence_of :html_file, :message=>'Le champ Nom du fichier HTML est obligatoire.' validates_presence_of :img_file, :message=>'Le champ Fichier de l\'image titre est obligatoire.'
The solution lies ahead
The good news is that the Rails core developers seem to be aware of those hassle and have plans to make this all easier in the coming release of Rails 2.2. I found this blog post by Ryan Daigle on automatic mass assignment. This should make my hack for the nested models irrelevant.
Rails 2.2 also promises a lot on the I18N side. I found this demo app that gives a taste of what is coming. That will probably help out localizing the validation messages.
Despite these two minor drawbacks that held me captive to my computer long through the night, my first Rails experience was very positive and I’m now considering completely dropping Java development in favor of Rails. I guess that will depend on what the market looks like for Rails developer at this time.
A special thanks to Mathieu who did a code review of my application on a sunny Saturday afternoon and gave me some very useful tips on Rails and Ruby. Starting out with a new technology always comes easier when you have passionate friends to help you!
Passionate web developer living in Montreal and hacking in Ruby on Rails available for contracts and freelance work.