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.
Related
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
I'm new to Rails and I have been following a tutorial.
I have been fiddling around with routes.rb and now in total confusion about when it looks for show method and when for index method if not explicitly mentioned?
Routes are simply like regexes on steroids. They have priority in the order they are defined and match the request method, URI and any other constraints that you have added.
get '/posts', to: 'posts#index'
get '/posts/:id', to: 'posts#show'
The key difference to the routes above is that the regular expression which the path of request uri must match is different. '/posts/:id' includes a named segment which would match:
GET /posts/11
GET /posts/gobligook
But not:
GET /posts
GET /posts/1/foo
GET /posts/foo/bar
The full conventional set of CRUD verbs are:
get '/posts', to: 'posts#index' # all posts
get '/posts/new', to: 'posts#new' # form to create a post
post '/posts', to: 'posts#create' # create a post from a form submission
get '/posts/:id', to: 'posts#show' # show a single post
get '/posts/:id/edit', to: 'posts#edit' # form to edit a post
put '/posts/:id', to: 'posts#update' # for legacy compatibility
patch '/posts/:id', to: 'posts#update' # update a post from a form submission
delete '/posts/:id', to: 'posts#destroy' # delete a post
In Rails flavor REST the action is implicitly derived from the path and method.
Only the new and edit methods which are used to render forms actually explicitly add the action in the path - which is because they act on a collection or a member of a collection.
Note that the '/posts/new' route must be declared before get '/posts/:id' or the show route would match the request first (routes have priority in the order they are defined). This does not apply to get '/posts/:id/edit' since the pattern is different.
Of course typing out all those routes is really tedious so rails provides the resources macro that does this for you:
resources :posts
Both index and show are GET method, but the difference is index is collection type and show is member type. Which means index does not expect any parameter in the url, but show expects id param in the url.
EX:
Index: /posts
Show: /posts/:id
Rails chose convention over configuration. That's why the actions are not explicitly named.
Here is the complete list of the expected actions for a given controller: http://edgeguides.rubyonrails.org/routing.html#crud-verbs-and-actions
I just only want to make a test and trying to understand the routes on rails. This is what I have on my controller:
class ItemsController < ApplicationController
def index
#data = params[:number]
end
end
and in index.html.erb
The number: <%= params[:number] %>
Well, if I made curl http://localhost:3000/?number=100 I can see on the view:
The number: 100
So, here everything is correct. But, I want to do the same, but with POST verb. So when I made curl -d "number=100" http://localhost:3000 I get the following error:
No route matches [POST] "/"
I have made:
def create
render plain: params[:number].inspect
end
to see the parameters, but as I said before, only works with GET verb.
So my question: How I can see the data sent by POST to the controller with curl and see the result on my view index.html.erb?
Note: On routes.rb I have:
Rails.application.routes.draw do
#get 'items/index'
resources :items
root 'items#index'
end
Notes: Based on the answer received, I have 2 more questions:
Why the verb GET works on http://localhost:3000/?number=100 and the same with http://localhost:3000/items/?number=100? Why the same does not happens with POST?
How can I remove the message No route matches [POST] "/" if the user points directly to http://localhost:3000 with POST verb?
You are posting to the root_url. Instead, POST your request to the items_url:
curl -d "number=100" http://localhost:3000/items
update:
Why the verb GET works on http://localhost:3000/?number=100 and the
same with http://localhost:3000/items/?number=100? Why the same does
not happens with POST?
The GET request to /?number=100 works because you have specified root 'items#index' in your routes file. This specifically creates a GET route that is mapped to the index action of the items controller.
How can I remove the message No route matches [POST] "/" if the user
points directly to http://localhost:3000 with POST verb?
You can create a single POST route using the post keyword:
# routes.rb
root 'items#index'
post '/', to: 'items#create'
which would generate the routes:
root GET / items#index
POST / items#create
(from your project directory run the command rails routes)
Or you can use the resource method to create all the CRUD paths:
resources :items, path: '/'
... which would create the following routes:
items GET / items#index
POST / items#create
new_item GET /new(.:format) items#new
edit_item GET /:id/edit(.:format) items#edit
item GET /:id(.:format) items#show
PATCH /:id(.:format) items#update
PUT /:id(.:format) items#update
DELETE /:id(.:format) items#destroy
Keep in mind that this may cause routing collisions if you try to add other resources to your app. If you need to add other resources, add them before these routes in the routes.rb file. Rails evaluates the routes file from top to bottom, so these resources would only load if no other paths match.
For more information see http://edgeguides.rubyonrails.org/routing.html
The following link works in my app:
<%= link_to "invitation", :controller => :invitations, :action => :index %>
To follow restful conventions i changed the link to:
<%= link_to "invitation", index_invitation_path %>
The error that i get is:
undefined local variable or method `index_invitation_path'
Rake routes yields:
invitations GET /invitations(.:format) {:controller=>"invitations", :action=>"index"}
The page name is index.html.erb. The model is invitation.rb. The controller is invitation_controller.rb. Routes has resources :invitations. What am i missing?
Thanks!
Assuming you have the routing correct:
resources :invitations
Then the correct helper for the index action (with the url /invitations.html) is
invitations_path
You can see more information by running rake routes. It will display text like the following:
lists GET /lists(.:format)
{:action=>"index", :controller=>"lists"}
POST /lists(.:format)
{:action=>"create", :controller=>"lists"}
new_list GET /lists/new(.:format)
{:action=>"new", :controller=>"lists"}
edit_list GET /lists/:id/edit(.:format)
{:action=>"edit", :controller=>"lists"}
list GET /lists/:id(.:format)
{:action=>"show", :controller=>"lists"}
PUT /lists/:id(.:format)
{:action=>"update", :controller=>"lists"}
DELETE /lists/:id(.:format)
{:action=>"destroy", :controller=>"lists"}
root /(.:format)
{:controller=>"lists", :action=>"index"}
The above was from a route of my own (for a model called List). The route helper method is shown immediately before the HTTP method. You have to remember to append the _path to each helper method. For example the helper methods I could use are:
list_path(list)
edit_list_path(list)
new_list_path
lists_path
You'll need a route in your routes.rb file that defines a mapping to the invitations controller and the index action.
Typically this is created with a resources call
resources :invitations
Which creates several default routes, which you can see by running rake routes.
For single resources, you can also define it using a match call
match "invitations/:id" => "invitations#index", :as => index_invitation
The rails site has a great resource on routing that provides all the details: Routing from the Outside In
Update: Based on your updated question, your route includes an invitaions (notice the trailing 's') route - nothing with index or invitation. The index_ prefix is generated by the resources call when it creates the default routes for :invitations.
It looks like you've defined a custom get mapping for an invitation. While this may technically work, if you're aim is to support restful routes, use the resources method. And have a read of the Routing guide from rails it's very easy to follow and quite detailed.
type rake routes in your console and look at listing of available routes. Seems to be there is no such route index_invitation_path? maybe it named differently
I think you need "invitations_controller.rb" to contain InvitationsController. Plural.
What is the difference between collection routes and member routes in Rails?
For example,
resources :photos do
member do
get :preview
end
end
versus
resources :photos do
collection do
get :search
end
end
I don't understand.
A member route will require an ID, because it acts on a member. A collection route doesn't because it acts on a collection of objects. Preview is an example of a member route, because it acts on (and displays) a single object. Search is an example of a collection route, because it acts on (and displays) a collection of objects.
URL Helper Description
----------------------------------------------------------------------------------------------------------------------------------
member /photos/1/preview preview_photo_path(photo) Acts on a specific resource so required id (preview specific photo)
collection /photos/search search_photos_path Acts on collection of resources(display all photos)
Theo's answer is correct. For documentation's sake, I'd like to also note that the two will generate different path helpers.
member {get 'preview'} will generate:
preview_photo_path(#photo) # /photos/1/preview
collection {get 'search'} will generate:
search_photos_path # /photos/search
Note plurality!
1) :collection - Add named routes for other actions that operate on the collection. Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete, an array of any of the previous, or :any if the method does not matter. These routes map to a URL like /users/customers_list, with a route of customers_list_users_url.
map.resources :users, :collection => { :customers_list=> :get }
2) :member - Same as :collection, but for actions that operate on a
specific member.
map.resources :users, :member => { :inactive=> :post }
it treated as /users/1;inactive=> [:action => 'inactive', :id => 1]
Short Answer:
Both the member and collection blocks allow you to define additional routes for your resources than the seven standard routes that Rails will generate for you.
A member block creates new routes on a single member of the resource.
A collection block creates new routes for a collection of that resource.
Long Answer
Rails provides the member and collection blocks so you can define custom routes for both the resource collection and the individual resource.
Here's how you'd typically define routes for the article resource.
resources :articles
This creates the following routes.
➜ bin/rails routes -g article
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
But let's say you are writing your articles in markdown, and need to see a preview of the article as you write it.
You could create a PreviewController and display the article's preview using its show action, but it's convenient to add a preview action on the ArticlesController itself.
Custom Member Routes
Here's how you define the preview route on the ArticlesController using the member block.
resources :articles do
member do
get 'preview'
end
end
It adds a new route that directs the request to ArticlesController#preview action. The remaining routes remain unchanged. It also passes the article id in params[:id] and creates the preview_article_path and preview_article_url helpers.
➜ bin/rails routes -g article
Prefix Verb URI Pattern Controller#Action
preview_article GET /articles/:id/preview(.:format) articles#preview
... remaining routes
If you have a single member route, use the short-hand version by passing the :on option to the route, eliminating the block.
resources :articles do
get 'preview', on: :member
end
You can go one step further and leave out the :on option.
resources :articles do
get 'preview'
end
It generates the following route.
➜ bin/rails routes -g preview
Prefix Verb URI Pattern Controller#Action
article_preview GET /articles/:article_id/preview(.:format) articles#preview
There are two important differences here:
The article's id is available as params[:article_id] instead of params[:id].
The route helpers changes from preview_article_path to article_preview_path and preview_article_url to article_preview_url.
Custom Collection Routes
To add a new route for the collection of a resource, use the collection block.
resources :articles do
collection do
get 'search'
end
end
This adds the following new route. It will also add a search_articles_path and search_articles_url helper.
search_articles GET /articles/search(.:format) articles#search
If you don't need multiple collection routes, just pass :on option to the route.
resources :articles do
get 'search', on: :collection
end
This will add the same route as above.
Conclusion
Rails allows you to break out of its convention of using seven resourceful routes using the member and collection blocks. Both allow you to define additional routes for your resources than the standard seven routes.
A member block acts on a single member of the resource, whereas a collection operates on a collection of that resource.
Source: Define New Routes Using the Member and Collection Blocks