How does rails differentiate between users/:id and users/new routes? - ruby-on-rails

I am trying to understand how does rails know the difference between the two routes
GET /users/:id
and
GET /users/new
when we type
resources :users
I tried to trace and understand the resources method in rails source code but I didn't understand it completely.
def resources(*resources, &block)
options = resources.extract_options!.dup
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
with_scope_level(:resources) do
options = apply_action_options options
resource_scope(Resource.new(resources.pop, api_only?, #scope[:shallow], options)) do
yield if block_given?
concerns(options[:concerns]) if options[:concerns]
collection do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
end
new do
get :new
end if parent_resource.actions.include?(:new)
set_member_mappings_for_resource
end
end
self
end
Is the below piece of code does that?
new do
get :new
end if parent_resource.actions.include?(:new)
if yes, can you explain?
Also if I tried to write another route with the same GET users/new format it redirects to GET users/:id, so how can I write another route like GET users/whatever without considering whatever as the :id?
Below is example of routes.rb
Example 1:
get '/feedbacks/:id' => 'feedbacks#show'
get '/feedbacks/count' => 'feedbacks#count'
feedbacks/count redirects to /feedbacks/:id
Example 2:
resources :feedbacks
get '/feedbacks/count' => 'feedbacks#count'
feedbacks/count redirects to /feedbacks/:id

Rails does not differentiate, it's just searches for the first record, which is suitable for the conditions
That's why new is generated earlier than id
Example 1:
get '/feedbacks/count' => 'feedbacks#count'
get '/feedbacks/:id' => 'feedbacks#show'
Example 2:
resources :feedbacks do
member do
get '/feedbacks/count' => 'feedbacks#count'
end
end
You can read about it here

Rails routes are just basically regular expressions on steriods. They match a path expression, a HTTP method and any additional constraints.
Routes have priority in the order they are defined. When the router matches a request in the routes collection it stops searching farther which is why the routes at the top always win.
If you look at the output from the resources macro your can see that the routes are ordered to take this into consideration:
Prefix Verb URI Pattern Controller#Action
things GET /things(.:format) things#index
POST /things(.:format) things#create
new_thing GET /things/new(.:format) things#new
edit_thing GET /things/:id/edit(.:format) things#edit
thing GET /things/:id(.:format) things#show
PATCH /things/:id(.:format) things#update
PUT /things/:id(.:format) things#update
DELETE /things/:id(.:format) things#destroy
The GET /things/new route must be declared before GET /things/:id. Otherwise it would be matched by the things#show route and give an ActiveRecord:: RecordNotFound error as the controller would attempt to find Thing id = "new".
users/:id, so how can I write another route like GET users/whatever
without considering whatever as the :id?
Use the collection option:
resources :things do
get :foo, on: :collection
end
# same but with block syntax
resources :things do
collection do
get :foo
get :bar
end
end
You can also add additional member routes (prefixed with an id):
resources :trips do
patch :cancel
end
Note that Rails defaults to on: :member so you don't need to explicitly set it.
Since resources yields at the top (yield if block_given?) these routes will have the correct priority.
See:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions

Related

need help on rails nesting routes

I want to get a route like
GET /example/notifications/status, to: example/notification#status
POST /example/notifications/disable to: example/notification#disable
Here is what I did
resources :example, only => %i[index] do
collection do
resources :notifications, :only => [] do
collection do
get :status
post :disable
end
end
end
end
it get the right path but it point to notification#status not example/notification#status
is there any way I can get both right path and controller expect code like
get "notifications/status", :to => "example/notifications#status"
You can use namespace for this purpose like below,
in you config/routes.rb file
namespace :example do
collection do
get :status
post :disable
end
end
It will hit the example/notification#status action.
for more information see here
Don't use resources since you don't want (aparently) any param on your routes. Go for namespace instead:
namespace :example do
namespace :notifications do
get 'status'
post 'disable'
end
end
Gives you:
$ rails routes -g example
Prefix Verb URI Pattern Controller#Action
example_notifications_status GET /example/notifications/status(.:format) example/notifications#status
example_notifications_disable POST /example/notifications/disable(.:format) example/notifications#disable

Is there a way to create a Rails resource route called `index`?

I want to add a resource route called index to a Rails 4 application but the generated routes aren't as expected. However, if I use another name (such as show_index), they are. To demonstrate, I'll begin with a vanilla Rails app that has no routes:
$ rake routes
You don't have any routes defined!
I add the below into config/routes.rb:
resources :items
Which produces the following resourceful Rails routes:
Prefix Verb URI Pattern Controller#Action
items GET /items(.:format) items#index
POST /items(.:format) items#create
new_item GET /items/new(.:format) items#new
edit_item GET /items/:id/edit(.:format) items#edit
item GET /items/:id(.:format) items#show
PATCH /items/:id(.:format) items#update
PUT /items/:id(.:format) items#update
DELETE /items/:id(.:format) items#destroy
The application has a show action that can render a so-called index if the parameter hash contains {with: 'index'} (this index is an application-specific thing, rather than a collection of items or anything like that).
I add a custom index route to invoke the show action with the additional parameter:
resources :items do
get 'index' => 'items#show', with: 'index'
end
This produces a new route but it has item_id instead of the expected id (compare with edit_item in the list above):
item_index GET /items/:item_id/index(.:format) items#show {:with=>"index"}
The Routing documentation explains that the way to get :id is to use on: :member, so the route would need to be
get 'index' => 'items#show', with: 'index', on: :member
but that doesn't produce the expected results. It adds the expected route but it steals the item method prefix from the default show action instead of using its own index_item (again, compare with edit_item in the list above):
item GET /items/:id/index(.:format) items#show {:with=>"index"}
GET /items/:id(.:format) items#show
However, had I used something other than index, such as show_index, then it would work as expected:
get 'show_index' => 'items#show', with: 'index', on: :member
produces
show_index_item GET /items/:id/show_index(.:format) items#show {:with=>"index"}
So there is a difference in behaviour when the route is called index. I expect this is because the implied resources routes use that name, although I don't think they use it in a way that would clash. It looks to me like I should be able to add a new index route which would become index_item (similar to the exisitng edit_item and in contrast to the existing item_index).
I know I can work around the problem, as I have demonstrated, by using a different name. But index reads better than show_index.
So my question asks is it possible to specify a resource route with index that is keyed off :id ?
`
To set specific url use as key word, so try something like:
get 'index' => 'items#show', with: 'index', on: :member, as: 'item_index'
or one of course on your wish.

route works one place, not others

This is kind of difficult to communicate but I'll try without pasting all my code. I have Members who have one Mailbox which has many Receipts. In the header layout I have a nav that calls
<%= link_to "Message Center", member_mailbox_path(current_user.member_id) %>
It works on most pages like trails/# , the resource pages for various models
But on other pages, seems like custom route pages, I get this error
No route matches {:action=>"show", :controller=>"mailbox", :member_id=>16}
Running rake routes shows this:
member_mailbox GET /members/:member_id/mailbox/:id(.:format) mailbox#show
Routes are confusing to me, here are my routes for this problem (show message isn't tested yet) ...
resources :members do
resources :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
The routes for the pages that are showing the error are similar to this one
match '/my_plays', :to => "trails#my_plays"
match '/my_creations', :to => "trails#my_creations"
So not sure if my routes are right. I wonder if resources :mailbox is correct since I don't have a bunch of resources for that, it's a has_one .... THX
----EDIT--- after changing route per advice:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
You may want to define a mailbox as a singular resource in your routes. Otherwise, Rails will expect you to pass in both the user id and the mailbox id for member_mailbox_path to route to mailbox#show. I believe this is why you're getting a routing error. Since each user has one mailbox, there's no need to make this extra lookup part of the route. So instead of resources :mailbox, you can do resource :mailbox:
resources :members do
resource :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
I believe this would generate the following routes:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
Notice that the lack of path names next to GET, PUT, and DELETE doesn't mean they don't exist; they're just repeats of the POST path, but each responds to different HTTP methods.
To render mailboxes#show, you'll need to add a MailboxesController with a show route, which might do a look up for the member:
class MailboxesController < ApplicationController
def show
#member = Member.find(params[:member_id])
# other mailbox code...
end
end
And you'll also create a template at app/views/mailboxes/show.html.erb to render the mailbox show page.
Also, I would recommend against deeply nesting your routes, as in third level :receipts.

Rails routing. Singular resource

I've got Rails routing problem. I would like to use singular resource with user controller but it doesn't work as I expected. Here is the fragment of my routes.rb file:
scope :module => "frontend" do
root :to => "home#index"
resource :user, :controller => "user"
get "/sign_up" => "user#new"
get "/sign_in" => "user#sign_in"
get "/sign_out" => "user#sign_out"
post "/authenticate" => "user#authenticate"
resources :articles
resources :article_categories
end
I thought it will work when I'll use for example "/user" or "/user/new" URL but it didn't. I get a routing error:
No route matches {:controller=>"frontend/user"}
The 'rake routes' command output is:
user POST /user(.:format) frontend/user#create
new_user GET /user/new(.:format) frontend/user#new
edit_user GET /user/edit(.:format) frontend/user#edit
GET /user(.:format) frontend/user#show
PUT /user(.:format) frontend/user#update
DELETE /user(.:format) frontend/user#destroy
sign_up GET /sign_up(.:format) frontend/user#new
sign_in GET /sign_in(.:format) frontend/user#sign_in
sign_out GET /sign_out(.:format) frontend/user#sign_out
authenticate POST /authenticate(.:format) frontend/user#authenticate
What is interesting, when I add route for index action in user controller, like this:
scope :module => "frontend" do
root :to => "home#index"
resource :user, :controller => "user"
get "/user" => "user#index"
get "/sign_up" => "user#new"
get "/sign_in" => "user#sign_in"
get "/sign_out" => "user#sign_out"
post "/authenticate" => "user#authenticate"
resources :articles
resources :article_categories
end
...it works!
But index action is not defined in user controller!
'rake routes' command returns double line for GET /user
GET /user(.:format) frontend/user#show
GET /user(.:format) frontend/user#index
so I suppose that's not the solution. Other actions assigned to '/users' URL don't work.
Is it necessary to define the route for the index action like
get "/controller_name" => "controller_name#index"
What am I doing wrong?
Defining a singular resource in your routes will not generate a route to an index action by design. The singular resource implies you're always going to lookup this resource without specifying an ID and consequently a get to index for a singular resource just doesn't make logical sense. So, a GET to your url "/user" will route to a show action for that singular resource and not an index.
EDIT: Since your issue isn't obvious, I'd simplify your routes until you can at least hit the controller you'd expect and then build from there.
config/routes.rb
scope :module=>"frontend" do
resource :user
end
#ensure you don't have any other user routes listed before this that would match "/user".
app/controllers/frontend/users_controller.rb
module Frontend
class UsersController < ApplicationController
def show
raise "in frontend/show"
end
end
end
Thanks a lot for help! I found the bug.
The routing error was caused by the following line of the layout html file
<%= auto_discovery_link_tag(:rss, {:action => "index"}, {:title => "RSS"}) %>
I was looking for errors in the erb view files but I forgot about the layout.
I must remember to check the entire view layer in such situations.

Rails: URL after validation fails when creating new records via form

Lets say I am creating a new Foo using a form and a standard Rails restful controller, which looks something like this:
class FoosController < ApplicationController
...
def index
#foos = Foo.all
end
def new
#foo = Foo.new
end
def create
#foo = Foo.create(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
render 'new'
end
end
...
end
So, if I use the standard restful controller (as above), then when I'm creating the Foo I am at example.com/foos/new, and if I submit the form and it saves correctly I'm at example.com/foos showing the index action. However, if the form is not filled correctly the form is rendered again and error messages are shown. This is all plain vanilla.
However, if errors are shown, the form page will be rendered but the URL will be example.com/foos, because the CREATE action posts to that url. However, one would expect to find Foos#index at example.com/foos, not the form they just submitted now with error messages added.
This seems to be Rails standard behavior, but it doesn't make a lot of sense to me. Obviously I could redirect back to new instead of rendering new from the create action, but the problem with that is the error messages etc. would be lost along with the partially complete Foos in memory.
Is there a clean solution for this problem, a way to send people back to example.com/foos/new when there are errors in the new Foo form they submitted?
Thanks!
To answer your comment on another answer:
I'm wondering if there's a way, without rewriting the controller at all, to tell rails that you want the URL to match the rendered template, rather than the controller action that called it.
I don't think so; URLs are tied directly to routing, which is tied into a controller and action pair--the rendering layer doesn't touch it at all.
To answer your original question, here's information from another similar question I answered.
As you've found, by default when you specify resources :things, the POST path for creating a new thing is at /things. Here's the output for rake routes:
things GET /things(.:format) {:action=>"index", :controller=>"things"}
POST /things(.:format) {:action=>"create", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
It sounds like you want something more like this:
create_things POST /things/new(.:format) {:action=>"create", :controller=>"things"}
things GET /things(.:format) {:action=>"index", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
Although not recommended, you can get this result with the following route:
resources :things, :except => [ :create ] do
post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
end
You would also need to modify your forms to make them POST to the correct path.
You could hook into rails routing by adding this in an initializer:
https://gist.github.com/903411
Then just put the regular resources in your routes.rb:
resources :users
It should create the routes and behaviour you are looking for.
You can set up the routing manually, if you're that concerned about what URL is going to show. For what you want, you can have a GET to /foos/new render your form, and a POST to the same URL do the creation:
map.with_options :controller => :foos do |foo|
foo.new_foo '/foos/new', :conditions => {:method => :get}, :action => :new
foo.create_foo '/foos/new', :conditions => {:method => :post}, :action => :create
foo.foos '/foos', :conditions => {:method => :get}, :action => :index
end
This should work without requiring any changes to your controller (yay!) - all three actions from your example are taken care of. The few disclaimers:
This is based on my routing for a 2.3.8 app - some syntax (semantics?) changes are probably required to get it into Rails 3 routing style.
My attempts to mix this style of routing with map.resources have failed horribly - unless you're more familiar with this than me, or Rails 3 routing is better (both easily possible), you'll have to do this for every route to the controller.
And finally, don't forget to add /:id, (.:format), etc. to the routes that need them (none in this example, but see #2).
Hope this helps!
Edit: One last thing - you'll need to hard-code the URL in your form_for helper on /foos/new.html.erb. Just add :url => create_foo_path, so Rails doesn't try to post to /foos, which it will by default (there might be a way to change the creation URL in the model, but I don't know of it, if there is one).
You could use Rack::Flash to store the parameters you wanted in the user's session and then redirect to your form url.
def create
#foo = Foo.new(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
flash[:foo] = params[:foo]
flash[:errors] = #foo.errors
redirect_to new_foo_path #sorry - can't remember the Rails convention for this route
end
end
def new
# in your view, output the contents of flash[:foo]
#foo = Foo.new(flash[:foo])
end

Resources