Routing broken after upgrade to 3.0

Hi:

I’m in the midst of upgrading a Rails 2.3 app to 3.0. The app boots and many pages display fine. However, tests fail and the app fails in development for nested routes such as http://localhost:3000/users/hikari17/projects.

The error is “uninitialized constant ProjectsController”. This looks like the router is trying to point the app to the ProjectsController (in /controllers) rather than Users::ProjectsController (in /controllers/users).

/controllers/projects_controller.rb doesn’t exist. Only the nested version /controllers/users/projects_controller/rb exists. Why would the http://localhost:3000/users/hikari17/projects url be routed to the non-nested controller?

The problem must lie my routes.rb file, right? I converted it from the rails2 version following the guidelines in #203 Routing in Rails 3 - RailsCasts and in the Rails upgrade handbook, but I seem to have messed something up. Here’s the relevant portion of the routes.rb file:

ROUTES_PROTOCOL = (ENV["RAILS_ENV"] =~ /(development|test)/ ? "http" : "https")

Genlighten::Application.routes.draw do

  # users   resources :users do
    member do
      get :welcome
      get :feedback
      get :dashboard
      get :not_found
    end
    collection do
      get :autocomplete_for_user_login
      post :is_login_available
      post :is_email_available
    end

    [many lines omitted]

  # projects -- from client point of view
    resources :projects do
      collection do
        get :pending
        get :in_process
        get :completed
        get :archived
      end
    end
 end

Thanks very much!

–Dean Richardson

I think that’s your problem. Looks like you accidentally commented out part of your routes, specifically the part that nests the next section under users.

Yet another reason to not add comments to your code. :wink:

Ah, would that it were that simple. I’m afraid the error you caught was in the way I pasted the code into the forum textarea. It’s correct in the routes.rb file [see below].

I’ve found a temporary fix, however. I’m now using the :controller => option on my resources lines, and explicitly calling out the nested directory structure as I used to do in the Rails 2.3 version of routes.rb:

resources :projects, :controller => "users/projects" do

instead of just

resources :projects

This seems to fix the problem I was seeing.

Here’s the full listing, with some of the entries updated to specify the full path to the controller:

 # users
  resources :users do
    member do
      get :welcome
      get :feedback
      get :dashboard
      get :not_found
    end
    collection do
      get :autocomplete_for_user_login
      post :is_login_available
      post :is_email_available
    end
    resource :account, :constraints => {:protocol => ROUTES_PROTOCOL} #used for activating a user's account after sign up
    resource :profile
    resource :email_preferences
    resources :favorites
    resource :billing_address # or should it be billing addresses?
    resources :project_requests, :controller => "users/project_requests" do
      member do
        get :convert
      end
    end
    resources :direct_requests do
      member do
        get :convert
      end
    end
    resources :public_requests, :controller => "users/public_requests" do
      member do
        get :convert
      end
      collection do
        get :closed
      end
    end

  # projects -- from client point of view
    resources :pre_quotes, :controller => "users/pre_quotes"
    resources :projects, :controller => "users/projects" do
      collection do
        get :pending
        get :in_process
        get :completed
        get :archived
      end
    end

I dug through the routing docs a bit. I think what you want is a namespace of user.

Check this out: Rails Routing from the Outside In — Ruby on Rails Guides

Helpful?

Ben:

This is a helpful idea. Here’s why I didn’t pursue it at first. My Rails 2 routes.rb file had just one namespaced group of routes: the one for admin:

  map.namespace :admin do |a|
    a.resources :default
    a.resources :users, :collection => { :search => :get }, :member => {:orders => :get, :balances => :get, :enable => :put} do |u|
      u.resource :profile, :controller => 'users/profiles'
      u.resources :projects, :controller => 'users/projects'
      u.resources :quotes, :controller => 'users/quotes'
      u.resources :reports, :controller => 'users/reports'
    end

But my users and providers routing groups (each with many nested controllers) looked like this instead:

  map.resources :users, :has_many => [:feed_items], 
                        :member => { :help => :get, :welcome => :get, :feedback => :get, :edit_profile => :get, :edit_preferences => :get,
                                     :dashboard => :get, :old_dashboard => :get, :not_found => :get },
                        :collection => { :autocomplete_for_user_login => :get, :is_login_available => :post, :is_email_available => :post } do |users|

    # used for activating a user's account after sign up
    users.resource :account, :requirements => {:protocol => ROUTES_PROTOCOL}

So being literal-minded, I translated the admin group to a namespaced set of routes in the Rails 3 routes.rb because it had been one in the earlier routes.rb. And I didn’t change the users and providers ones because they hadn’t been before.

When I tried namespacing users as user:

namespace :user do
  resource :account

etc.

I noticed that rake routes told me I now had properly nested controllers, except the controller was shown as bring within subdirectory user instead of users. This broke all my routes. I could of course rename the controllers/users folder to controllers/user, but that would also mean renaming the view folders, etc. And while doing this fixed some broken controller_name_path references, it broke others.

So I went back to resources :users do and inserting the name of the controller manually like this:

resources :projects, :controller => “users/projects” do
collection do
get :pending
get :in_process
get :completed
get :archived
end
end

So the app works again, and the tests pass (most of them anyway, there are still a few stray routes that aren’t working) but I’m left wondering: what’s really the right way to do this?

If I’m going to have nested routes corresponding to my users/ and providers/ associations (e.g., /users/projects, /users/payments/, etc.) how should I set up my controller directory tree and routes so they support each other and I avoid name collisions? How do you guys tend to do it? Or do you try to avoid nested routes altogether?

Thanks,

Dean

I think what you wanted was namespace :users (note the ‘s’). I believe that would set up properly nested controllers with the subdirectories.

Give that a shot?

We generally avoid explicitly specifying controller names. It usually means you’re fighting Rails’ defaults, which is almost always more painful that just going with its conventions.

Ben:

This looked tantalizingly like it was going to work (it creates the desired controller paths in the right-hand column of rake routes) but unfortunately it expects URLs that don’t have a user_id parameter:

       users_projects GET    /users/projects(.:format)                                               {:controller=>"users/projects", :action=>"index"}
                                  POST   /users/projects(.:format)                                               {:controller=>"users/projects", :action=>"create"}
                new_users_project GET    /users/projects/new(.:format)                                           {:controller=>"users/projects", :action=>"new"}
               edit_users_project GET    /users/projects/:id/edit(.:format)                                      {:controller=>"users/projects", :action=>"edit"}
                    users_project GET    /users/projects/:id(.:format)                                           {:controller=>"users/projects", :action=>"show"}
                                  PUT    /users/projects/:id(.:format)                                           {:controller=>"users/projects", :action=>"update"}
                                  DELETE /users/projects/:id(.:format)                                           {:controller=>"users/projects", :action=>"destroy"}

In other words, the “users” in the namespace doesn’t really have a controller/association tied to it.

But if I go back to resources :users do instead of namespace :users do, I get routes that look for user_ids, but point to controllers that aren’t in subdirectories:

   user_projects GET    /users/:user_id/projects(.:format)                                      {:controller=>"projects", :action=>"index"}
                                  POST   /users/:user_id/projects(.:format)                                      {:controller=>"projects", :action=>"create"}
                 new_user_project GET    /users/:user_id/projects/new(.:format)                                  {:controller=>"projects", :action=>"new"}
                edit_user_project GET    /users/:user_id/projects/:id/edit(.:format)                             {:controller=>"projects", :action=>"edit"}
                     user_project GET    /users/:user_id/projects/:id(.:format)                                  {:controller=>"projects", :action=>"show"}
                                  PUT    /users/:user_id/projects/:id(.:format)                                  {:controller=>"projects", :action=>"update"}
                                  DELETE /users/:user_id/projects/:id(.:format)                                  {:controller=>"projects", :action=>"destroy"}

Is there a way for to get both, without explicitly spelling out the controller location using :controller => “users/projects”?

–Dean

Found this SO thread that suggests the use of :module => to force the correct controller subfolder scheme:

Any thoughts on this approach compared to :controller => ?

resources :organizations, path: 'org' do
  resources :events, :module => "organization"
    member do
      get 'confirm'
    end
  end
end

–Dean

If you’re going to have to specify something, I’d use :controller, since it seems a bit more explicit to me. That said, it’s not a huge difference either way.

At this point, I’d call whatever works good enough and move on. Granted, I bill by the hour, but it’s likely there are bigger fish to fry at this point. :smile: