Friday, October 5, 2012

Rails Namespace, Nested Resource and form_for

Couple days ago, I ran into a problem when I try to use namespace, nested resources and form_for in Rails 3 and ruby 1.9.3. I try to setup some thing similar to this, a namespace :admin, with nested resources :menus, and :menu_items. Here is the routes.rb:
namespace :admin do
  resources :menus do
    resources :menu_items
  end
end
I think it's probably common used case. Unfortunately, I got this error when I try to create a new MenuItem in menu_items/_form.html.erb
undefind method 'admin_menu_items_path' for #<#<Class ...
...
 1: <%= form_for(@admin_menu_item)do |f| %>
      ...
17: end
I realize that the standard scaffolding won't work for me, so I start googling. Right away, I get a lot of hits. It's seem to be a common issue. However, the solutions create quite a confusion for me. I try different solutions, some looking good but don't work! .. Here is what I find working.

 First, in admin/menu_items_controller.rb, method 'new':
def new 
  @menu = Admin::Menu.find(params[:menu_id])
  @menu_item = @menu.menu_items.build
  ...
end
It load up the @menu, and build @menu_item, nothing surprise me. I also get rid of than "admin" prefix. It's simpler that way.



Next, I move form_for statement, from the partial _form.html.erb to new.html.erb. Here the form_for statement in new.html.erb looks like:
... 
<%= form_for [@menu, @menu_item], :url => admin_menu_menu_items_path do |f| %>
  <%= render :partial => 'form', :locals => { :f => f } %>
<%= end %>
...
I pass [@menu, @menu_item] as the first argument of form_for. That make sense to me because of the nested resources. However, this is not enough to handle namespace. To do so, I need to specify :url, which usually infer from the first argument of form_for. Unfortunately auto generate routing doesn't work here, probably because of the namespace.  By far, specifying :url the cleanest solution I have seen.

I have to move form_for because the partial _form.html.erb is also used by edit.html.erb. Since this solution require to specify :url, the line form_for can not be in the _form.html.erb any more.

One other thing, to pass variable f as local variable to partial _form.html.erb, you need the keyword :partial there. I still don't understand this line doesn't work!
<%= render 'form', :locals => { :f => f } %>
Why won't render 'form' take :locals options the same way as render :partial => 'form' does? It's a mystery to me.

Of course, you still need to workout a few route naming around forms. Basically adding the menu resource name in the route naming, for example for the link_to 'back', instead of admin_menu_items_path, it's become admin_menu_menu_items_path(@menu).

A few problems here and there, but I would say, Rails 3 have come a long way handling namespace and nested resources. I remember in Rails 2, to use namespace is quite fun!.