Search code examples
restruby-on-rails-2

Nested Resource at Root Level of Path


I'm trying to create a nested resource with a url scheme along the lines of: "http://example.com/username/...".

What I currently have is this:

ActionController::Routing::Routes.draw do |map|
  map.home '/', :controller => 'home'

  map.resource :session

  map.resources :users, :has_many => :nodes
  #map.user '/:id', :controller => 'users', :action => 'show', :has_many => :nodes

  map.resources :nodes, :belongs_to => :user
end

This results in URLs like:

http://example.local/users/username
http://example.local/users/username/nodes

How to avoid the "users" prefix is beyond me. Passing a "as: => ''" option to map.resources doesn't work and it seems named routes don't support the ":has_many" or ":belongs_to" options.

Commenting out the "map.resources :users" and uncommmenting the "map.user" line after it seems to work… until you reach a nested resource. Then it spits out the following error:

undefined method `user_nodes_path' for #<ActionView::Base:0x1052c8d18>

I know this problem has come up plenty of times before and is always met with "Why would you want to do that?" responses. Frankly, Twitter does it, Facebook does it, and I want to do it too! ;-D

As for the common criticism of how to avoid usernames from conflicting built-in paths, I've set my minimum username length to 6 characters and plan to make all built-in root-level paths segments paths 5 characters or shorter (i.e. "/opt/..." for options, "/in/..." for session log-in, etc.).


Solution

  • As noted in this question:

    Default segment name in rails resources routing

    You should be able to use this plugin:

    http://github.com/caring/default_routing

    Alternatively, you could specify them manually with something like

    map.users     '/users',     :controller => 'users', :action => 'index',
      :conditions => { :method => :get }
    map.connect   '/users',     :controller => 'users', :action => 'create',
      :conditions => { :method => :post }
    map.user      '/:id',       :controller => 'users', :action => 'show',
      :conditions => { :method => :get }
    map.edit_user '/:id/edit',  :controller => 'users', :action => 'edit',
      :conditions => { :method => :get }
    map.new_user  '/users/new', :controller => 'users', :action => 'new',
      :conditions => { :method => :get }
    map.connect   '/:id', :controller => 'users', :action => 'update',
      :conditions => { :method => :put }
    map.connect   '/:id', :controller => 'users', :action => 'destroy',
      :conditions => { :method => :delete }
    
    map.resources :nodes, :path_prefix => '/:user_id', :name_prefix => 'user_'
    # to generate user_nodes_path and user_node_path(@node) routes
    

    That or something like it should give you what you want from map.resources.

    map.resources doesn't work and it seems named routes don't support the ":has_many" or ":belongs_to" options.

    Named routes supports has_many, which is used to add another resource level to the URL path. For example

    map.resources :users, :has_many => :nodes
    

    generates all the users_path and user_nodes_path routes like /users, /users/:id, /users/:id/nodes and /users/:id/nodes/:id. It's identical to

    map.resources :users do |user|
      user.resources :nodes
    end
    

    Commenting out the "map.resources :users" and uncommmenting the "map.user" line after it seems to work… until you reach a nested resource. Then it spits out the following error:

    undefined method `user_nodes_path' for #<ActionView::Base:0x1052c8d18>
    

    That's because map.resources :users, :has_many => :nodes generates these routes. Only one named route is generated by map.user '/:id', :controller => 'users', :action => 'show' and that's user_path.