My first Rails application

Posted: September 23rd, 2008 | Author: Pierre Olivier Martel | Filed under: Rails | View Comments

I’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!


  • Mathieu Martin

    1st convert under my belt :-) I'm really glad to know you like Rails!

    Now let's DRY up these repetitive messages. I'm pretty sure you already know about the "…#{}…" way of constructing string. Here's another one:

    str = "The %s is annoying"

    now you can evaluate the string with different parameters:

    str % 'repetition'
    #=> "The repetition is annoying"

    If you look up the Kernel::sprintf doc, you'll see it supports most of the regular string formats you'd want (integers, floats and the like).

  • Mathieu Martin

    1st convert under my belt :-) I'm really glad to know you like Rails!Now let's DRY up these repetitive messages. I'm pretty sure you already know about the "…#{}…" way of constructing string. Here's another one:str = "The s is annoying"now you can evaluate the string with different parameters:str 'repetition' #=> "The repetition is annoying"If you look up the Kernel::sprintf doc, you'll see it supports most of the regular string formats you'd want (integers, floats and the like).

  • Pierre Olivier Martel

    @Mat: Just tried your advice and it worked as expected. That’s already a lot more clean! Thanks!

  • Pierre Olivier Martel

    @Mat: Just tried your advice and it worked as expected. That’s already a lot more clean! Thanks!

  • Pistos

    It would be worthwhile to at least glance at some of the other Ruby frameworks. Several have cropped up over the past year or two, including Merb and Ramaze (my favourite).

  • Pistos

    It would be worthwhile to at least glance at some of the other Ruby frameworks. Several have cropped up over the past year or two, including Merb and Ramaze (my favourite).

  • Calvin

    Nice work on the app! I just started with Rails as well and am working on the exact same problem of having two levels of nesting in a single form. Have you looked into the attribute_fu plugin?

    I’ve gotten everything other than some of the dhtml that generates additional form components to work using this plugin, but am looking into other solutions.

  • Calvin

    Nice work on the app! I just started with Rails as well and am working on the exact same problem of having two levels of nesting in a single form. Have you looked into the attribute_fu plugin? I’ve gotten everything other than some of the dhtml that generates additional form components to work using this plugin, but am looking into other solutions.

  • Anonymous

    I’ve always been wanting to give rails a go, but haven’t had the chance as of yet(fulltime job + startup during the evenings and weekends) and I’m curious to know about your rails experience.

    You said that it would take you twice as long in java — but where exactly would this twice as long effort be spent? Is it the actual webpage / vuew development, or the server / CRUD operations + services or both?

    For java webapps, I’ve found it to be very fast to crank out the services / CRUD code using hibernate or ibatis and it’s the page / view development that is the time killer. I’m just wondering if this is your view too and if this is what rails solves.

    Many thanks

  • Anonymous

    I’ve always been wanting to give rails a go, but haven’t had the chance as of yet(fulltime job + startup during the evenings and weekends) and I’m curious to know about your rails experience.You said that it would take you twice as long in java — but where exactly would this twice as long effort be spent? Is it the actual webpage / vuew development, or the server / CRUD operations + services or both?For java webapps, I’ve found it to be very fast to crank out the services / CRUD code using hibernate or ibatis and it’s the page / view development that is the time killer. I’m just wondering if this is your view too and if this is what rails solves. Many thanks

  • Anonymous

    You “specialize” in Rails development and have a single Rails app that took you 60 hours to develop?

    This word, “specialize”, I do not think it means what you think it means.

    @anonymous: Personally I don’t find that Rails saves much on the view side, and Erb/RHTML feels like a throwback to 5-10 years ago. But there are other Ruby frameworks, as was mentioned.

    IMO the biggest Rails timesavers have more to do with Ruby and its greater ability to abstractify code.

  • Anonymous

    You “specialize” in Rails development and have a single Rails app that took you 60 hours to develop?This word, “specialize”, I do not think it means what you think it means.@anonymous: Personally I don’t find that Rails saves much on the view side, and Erb/RHTML feels like a throwback to 5-10 years ago. But there are other Ruby frameworks, as was mentioned.IMO the biggest Rails timesavers have more to do with Ruby and its greater ability to abstractify code.

  • Nick

    To clean up your erb/html you should check out haml, it cleans up your markup quite a bit.

    http://haml.hamptoncatlin.com/

  • Nick

    To clean up your erb/html you should check out haml, it cleans up your markup quite a bit. http://haml.hamptoncatlin.com/

  • Pierre Olivier Martel

    Anonymous 1: I haven’t been very specific about what makes me go faster in Rails compared to Java. Here are some quick thoughts :

    - Ruby is a lot less verbose than Java and being dynamic, you write a lot less code. Also, many APIs such as File and FTP are a lot easier to use than their Java counterpart.

    - Rails uses convention over configuration and therefore you have a lot less configuration to write starting a new application. Not a single XML file to configure in my applicaiton.

    - ActiveRecord (which works a bit like Hibernate but with again a lot less configuration) makes it very easy to map your model with very little code.

    A lot more could be said and I’m certainly oversimplifying things but that’s the general feeling I had building my application.

    @Anonymous2 : Here’s a definition of specialize according to Wordnet : “become more focus on an area of activity or field of study”. But I agree that it doesn’t make me a specialist of Ruby and Rails at all! Although I whish I might become one someday!

  • Pierre Olivier Martel

    Anonymous 1: I haven’t been very specific about what makes me go faster in Rails compared to Java. Here are some quick thoughts :- Ruby is a lot less verbose than Java and being dynamic, you write a lot less code. Also, many APIs such as File and FTP are a lot easier to use than their Java counterpart.- Rails uses convention over configuration and therefore you have a lot less configuration to write starting a new application. Not a single XML file to configure in my applicaiton.- ActiveRecord (which works a bit like Hibernate but with again a lot less configuration) makes it very easy to map your model with very little code.A lot more could be said and I’m certainly oversimplifying things but that’s the general feeling I had building my application.@Anonymous2 : Here’s a definition of specialize according to Wordnet : “become more focus on an area of activity or field of study”. But I agree that it doesn’t make me a specialist of Ruby and Rails at all! Although I whish I might become one someday!

  • James Golick

    The commit that was meant to bring nested assignment to rails 2.2 has been reverted. The patch was missing support for a wide variety of situations.

    Frankly, nested assignment is a difficult problem. attribute_fu works, but it’s not the cleanest solution. Generating the necessary HTML is ugly too.

    The problem with your solution, in my opinion, is that you’ve stuffed framework code in to your model. Handling nested assignment belongs in AR.

    Also, putting the param parsing in to javascript makes it hard to test, JS framework specific, and practically impossible to debug. Rails’ params parsing is pluggable, and it should be possible to hide the ugliness in there – I just haven’t had a chance to figure out how.

    I have heard reports of people getting 3 level nesting working with attribute_fu. But, I’ve also heard of lots of trouble getting it all together. Maybe you can provide a patch. :)

  • James Golick

    The commit that was meant to bring nested assignment to rails 2.2 has been reverted. The patch was missing support for a wide variety of situations.Frankly, nested assignment is a difficult problem. attribute_fu works, but it’s not the cleanest solution. Generating the necessary HTML is ugly too.The problem with your solution, in my opinion, is that you’ve stuffed framework code in to your model. Handling nested assignment belongs in AR. Also, putting the param parsing in to javascript makes it hard to test, JS framework specific, and practically impossible to debug. Rails’ params parsing is pluggable, and it should be possible to hide the ugliness in there – I just haven’t had a chance to figure out how. I have heard reports of people getting 3 level nesting working with attribute_fu. But, I’ve also heard of lots of trouble getting it all together. Maybe you can provide a patch. :)

  • Pierre Olivier Martel

    Thanks for the critic James. I think you nailed it right about the down sides of my implementation and your feedback will help me improve it. Mathieu and a few others told me about attribute_fu and I’m very curious to see how it could work with my nested model problem. I wish I could come up with something cleaner that could be reusable. I’ll give it a try this week and blog about it if it works!

  • Pierre Olivier Martel

    Thanks for the critic James. I think you nailed it right about the down sides of my implementation and your feedback will help me improve it. Mathieu and a few others told me about attribute_fu and I’m very curious to see how it could work with my nested model problem. I wish I could come up with something cleaner that could be reusable. I’ll give it a try this week and blog about it if it works!

blog comments powered by Disqus