Why does order matter in Rails routes.rb when using resources? - ruby-on-rails

I receive an error when my route is listed as such:
resources :coupons
get 'coupons/redeem_coupon', to: 'coupons#redeem_coupon', as: 'redeem_coupon'
The error is:
ActiveRecord::RecordNotFound - Couldn't find Coupon with 'id'=redeem_coupon:
When I reverse the order to:
get 'coupons/redeem_coupon', to: 'coupons#redeem_coupon', as: 'redeem_coupon'
resources :coupons
It works fine. I understand that resources creates these routes
GET /coupons
GET /coupons/new
POST /coupons
GET /coupons/:id
GET /coupons/:id/edit
PATCH/PUT /coupons/:id
DELETE /coupons/:id
Is listing my custom route first, more specific or overriding the other route? Why does the order matter?

The error you're getting is because rails tries to match routes starting from the top down. If you're trying to add a custom route to an existing resource, the easier way is to do this. collection is if you want to use it on the group, member is if you want to add a custom route to an individual resource.
resources :coupons do
collection do
get 'redeem_coupon'
end
end

By listing your custom route first, you are overriding the other route. When rails gets a request, it simply starts from the top of your routes.rb file and goes with whichever route matches first.

Related

ActiveRecord RecordNotFound Error

I created a CRUD using the scaffold .
rails g scaffold intermediate_level/memory_game
And then I created the method Play, but when I call the method play an error is returned.
http://localhost:3000/intermediate_level/memory_game/play?id=1
Couldn't find IntermediateLevel::MemoryGame with 'id'=play
# Use callbacks to share common setup or constraints between actions.
def set_intermediate_level_memory_game
#intermediate_level_memory_game = IntermediateLevel::MemoryGame.find(params[:id])
end
def play {
#intermediate_level_memory_game = IntermediateLevel::MemoryGame.find(params[:id])
}
My routes.file
namespace :intermediate_level do
resources :memory_game
get 'memory_game/play'
end
Your custom get 'memory_game/play should come before resources :memory_game. Rails evaluates routes in the order in which they are listed in the routes.rb file, with the routes closest to the top of the file receiving the highest priority.
With your given routes information:
namespace :intermediate_level do
resources :memory_game
get 'memory_game/play'
end
You have these two routes:
GET /intermediate_level/memory_game/:id(.:format) intermediate_level/memory_game#show
and
GET /intermediate_level/memory_game/play(.:format) intermediate_level/memory_game#play
When you make this request:
http://localhost:3000/intermediate_level/memory_game/play?id=1
it is matched by both of those routes and as you defined: resources :memory_game before get 'memory_game/play' in your routes.rb file, so the first one (GET /intermediate_level/memory_game/:id) comes into action as that has higher priority, (because Routes have priority defined by the order of appearance of the routes in the config/routes.rb file) and then it tries to find the memory game with id param which in that case is play but fails to do so (as you don't have any memory game where id=play) and fails with the error message:
Couldn't find IntermediateLevel::MemoryGame with 'id'=play
One quick way to get around this issue is to reorder your routes like this:
namespace :intermediate_level do
get 'memory_game/play'
resources :memory_game
end
Then, your request url http://localhost:3000/intermediate_level/memory_game/play?id=1 will be served by GET /intermediate_level/memory_game/play(.:format) intermediate_level/memory_game#play route which is what you want.

Rails routing: uninitialized constant ClanController

I have some problems with routing custom controller actions
Routes:
resources :clans do
get 'leave' =>'clan#leave_clan'
get 'dismiss' =>'clan#dismiss_clan'
get 'kick_from_clan/:user_id' =>'clan#kick'
get 'invite/:user_id' =>'clan#invite'
get 'join' =>'clan#join'
end
namespace :admin do
resources :clans
resources :users
end
I know i have clans both in admin namespace and also without but that is how i need it, actions are completely different in them.
And I use a generated route as clan_join_path(clan).
This action results in the next error:
uninitialized constant ClanController
Directory structure:
/app
/controller
/admin
/ClansController.rb
/ClansController.rb
EDIT:
Also invite and kick routes are not generated as expected.
*no path* GET /clans/:clan_id/kick_from_clan/:user_id(.:format) clan#kick
*no path* GET /clans/:clan_id/invite/:user_id(.:format) clan#invite
Any suggestion to the edit part?
You're defining a resource called clans and I presume you have a controller also called ClansController (note the puralization). If you don't have this controller, you'll be wanting to create it.
You probably therefore need to pluralize your routes:
resources :clans do
get 'leave' =>'clans#leave_clan'
get 'dismiss' =>'clans#dismiss_clan'
get 'kick_from_clan/:user_id' =>'clans#kick'
get 'invite/:user_id' =>'clans#invite'
get 'join' =>'clans#join'
end
Also make sure your controller is named clans_controller.rb (pluralized).
You can use member do ... end to properly route based on type of action
resources :clans do
member do
get :leave
get :dismiss
#etc
end
end
This will define the routes as clans/:id/leave clans/:id/dismiss
Check if your clans_controller.rb is intentionally not plural.
resources :clans do
get 'leave' =>'clans#leave_clan'
get 'dismiss' =>'clans#dismiss_clan'
get 'kick_from_clan/:user_id' =>'clans#kick'
get 'invite/:user_id' =>'clans#invite'
get 'join' =>'clans#join'
end
app/controllers/clans_controller.rb
class ClansController < ApplicationController
end

ActiveRecord::RecordNotFound in ContactsController#create Couldn't find Company without an ID error

When i try to submit at localhost:3000/companies/1/contacts/new i get the error Couldn't find Company without an ID https://gist.github.com/overhang/f8c20d2d2c851cdee7b1 any clue?
I reckon it might be a problem with routes.rb
Remove the following lines from routes.rb
# config/routes.rb
# get "companies/index"
# get "companies/new"
# get "companies/show"
# get "companies/create"
# get "companies/edit"
Notice that RESTful controller actions like edit and show require a specific Company passed in order for the correct company to be looked up. These get routes don't allow for that. Instead, you should be utilizing the resource routes you've already created:
# config/routes.rb
resources :companies
The non-resourceful routes are impeding the execution of the resourceful ones. Removing them should fix your problem.

Best practice to match the 2nd level (nested) resource index action

(I am doing the exercise of online course from codeschool, the rails app from scratch part 1)
Here is my routes.rb
resources :destinations, only: :index
resources :trips do
resources :destinations
end
and the rake routes result has these two routes go to the same action:
Prefix Verb URI Pattern Controller#Action
destinations GET /destinations(.:format) destinations#index
trip_destinations GET /trips/:trip_id/destinations(.:format) destinations#index
I wish to match the trip_destinations to trip#show, i.e. redirect to /trips/:id, I have some solutions like hard code it in routes.rb or add redirection in destination#index. Which one is better? Is there a best practice to accomplish this task?
resources :trips do
resources :destinations do
member do
get 'index' => 'trips#show'
end
end
end
This will work for you, but it's based on faulty design because the response does not match the request. In other words, it's a bad route. A trip's destination index resource should respond with a trip's destination index, not the trip object itself.
The /trips/:id/destinations path should not be redirecting or rendering /trips/:id. There shouldn't be a link pointing to trips/:id/destinations in the first place, unless your application can respond with the appropriate resource.
It's very easy to get carried away with fancy routes and URL presentation. Be sure your routes make sense. It will save you loads of trouble in the future.

Rails route parent ID name and CanCan issue

I'm trying to get a simple route working
/agenda_items/5/feed
To do this, I have the following route setup
resources :agenda_items do
member do
get "/feed", to: "comments#feed"
end
end
In each of my controllers, I'm using CanCan to handle the authentication and it works fine, however on this one action I'm having an issue, which I'm pretty sure is down to railsnaming generation. When I runrake routes`, the route above is produced as
feed_agenda_item /agenda_items/:id/feed(.:format) agenda_items/:id#feed
As far as I can tell, CanCan is expecting the :id parameter, to actually be :agenda_item_id so as a result, my parent resource isn't being loaded.
Is there any way I can get rails to change this so that CanCan will work without me having to manually load and authorize the resource, or is there a way I can get CanCan to change what it's looking for on certain actions?
The problem is that your routes are wrong. You try to create a member action for agenda items which routes to the comments controller. If you want a feed of all the commments from a single agenda item you should do something like this:
resources :agenda_items do
resources :comments do
collection do
get :feed
end
end
end
You should now get the following when running rake routes:
feed_agenda_item_comments /agenda_items/:agenda_item_id/feed(.:format) comments#feed

Resources