Question

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.).

Was it helpful?

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.

OTHER TIPS

See my answer in this related question

Basically, you can pass :path => '' as an option to negate the identifying segment (Tested in Rails 3 only) Just be aware that this may clash with other routing patterns, so be careful where and how you place it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top