Wednesday, November 19, 2008

Restful Nested Resources in Rails 2.0

There are several tutorials around for Rails 2.0 and nested resources (Akita, Adam Heroku). I wish I could get a hold of Adam's code files, but I keep getting 404 not found).


I struggled a lot and finally figured out how to do this right. I have been keeping a log of my own coding to share with my team, and these are my logging notes. Email me if you want the actual files, and I will be happy to share.

In my case, I have a parent 'user', that can own multiple children 'categories'.

Steps:
1> I use straight up scaffolding to generate the models, controllers, and migrations for User and Category. BTW, only later, reading Akita, did I discover the option of declaring "t.references :user" for the 'categories' migration. Interesting tid-bit that I did not know.
2> For the routes.rb, following Adam's path, I do not create a seperate category resource. I use the simple has_many declaration to denote that categories are children of user. Note that I am using the acts_as_authenticated plugin, and this had to come after all the other 'user' related declarations in the routes file.
map.resources :users, :has_many => :categories
3> In the User model, declare has_many :categories, and in Category model, declare belongs_to :user
4> Following Adam's instructions, type 'rake/routes', and let your eyes bleed over the keyboard. Try to look for the routes that give you the 'user_categories' like paths.
5> After this, Adam's examples become a little hard to follow, and I will try to spell out the next steps in gory detail. Hopefully, if will save some newbies like me a couple of hours.


6> Changes in the categories_controller.rb
6a> Start at the very bottom: Create a private method that loads the user from params:
6aa> private def load_user @usr = User.find(params[:user_id]) end
6ab> Then use this in a before_filter :load_user at the very top of the controller.
6b> For 'index' method, modify line 7: @categories = @user.categories.find(:all)
6c> No change in 'show'
6d> For new, @category = Category.new(:user_id => @user.id). Not sure this is absolutely necessary.
6e> No change in 'edit'
6f> For 'create'. @category = @user.categories.build(params[:category]). Note the new method 'build', working on the @user object.
6ff> The 'redirect' changes to format.html { redirect_to ([@user, @category]) }. Note that it is passing an array.
6g> In update, same 'redirect' change, format.html { redirect_to ([@user, @category]) }
6h> In 'destroy', redirect change, { redirect_to(user_categories_url(@user)) }

7> Changes in the 'index' view: Note how 'user' is prefixed to all 'category_path'
7a> 'Show' link: link_to 'Show', user_category_path(category.user, category)
7b> 'Edit' link: link_to 'Edit', edit_user_category_path(category.user, category)
7c> 'Delete' link: link_to 'Delete', user_category_path(category.user, category), :confirm => 'Are you sure?', :method => :delete

8> Changes in the 'new' view:
8a> form_for([@user, @category]) do f: Passing an array of the @user and @category objects
8b> Link 'back' to index: link_to 'Back', user_categories_path

9> Changes in the 'edit' view:
9a> form_for([@user, @category]) : Again, passing two objects in the form
9b> 'show' link needs 'user' in the path, as well as the two objects: link_to 'Show', user_category_path(@user, @category)
9c> 'back' link simply needs to put the 'user' prefix: link_to 'Back', user_categories_path

10> Changes in the 'show' view: Only @category object needed here. Changes are in the links
10a> 'edit' link: edit_user_category_path(@category.user, @category)
10b> 'back' link simply needs to put the 'user' prefix: link_to 'Back', user_categories_path

This is a lot of typing, and manual work. There should be a plugin for this, and sure enough, there is one (http://deaddeadgood.com/2008/10/8/scaffolding-nested-resources-in-rails). However, I am a Windows user, and have not yet discovered the joys of Git. That automatically puts you in Rails purgatory. I believe I am the only PC user who comes to our local ruby meetup. However, there is hope (http://jamie.ideasasylum.com/2008/08/installing-rails-plugins-with-git-on-windows/). Haven't tried this one out yet. I do think that it is not fair to impose this much pain on Windows users to just install plugins.