route works one place, not others - ruby-on-rails

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.

Related

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

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

How to remove the prefix generated in routes.rb for the params in the path

Currently in my routes I have:
# USER RESOURCES
resources :users do
resources :repositories
patch 'change_password'
get 'account_setting'
end
Which generates this path for the account_setting action:
user_account_setting GET /users/:user_id/account_setting(.:format) users#account_setting
What I want is to have:
user_account_setting GET /users/:id/account_setting(.:format) users#account_setting
The two are essentially the same thing, but the first has a user_ prefix for the id which rails adds because it is in users resource block.
SIDE NOTE
I know I can simply remove the account_setting action from the users resource block and write:
get 'users/:id/account_setting', to: 'users#account_setting'
But I don't want to.
You can do it as follows:
resources :users do
member do
get 'account_setting'
end
end
To add a member route, add a member block into the resource block.
For documentation, you can check http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html

Unable to add custom routes in rails

I am using the mailboxer gem in my rails app to handle private messages between users. I have a separate page to view your inbox and a separate page to view your trash folder. The URL localhost/conversations succesfully loaded the inbox. I want to have a link that can load a separate page for trash, such as localhost/conversations/trashbin. However, I can't get Rails to recognize any routes I create for such a page.
In addition, going directly to that URL displays and error:
Couldn't find Conversation with id=trashbin
I understand why the error is occurring but I don't know how to fix it. The issue is with my routes file:
resources :conversations do
member do
post :reply
post :trash
post :untrash
get 'trashbin', :action => 'trashbin'
end
end
Using resources is causing the app to look for a specific conversation. This is useful throughout the rest of the application except for this one case. I simply want to collect all of the messages that were marked as trash. How can I edit this routes file to accomplish this?
Here are the links I have on the index conversations page:
<%= #conversations.count %> Inbox | <%= #trash.count %> Trash
Thanks!
EDIT:
Thanks to the answers below I have updated the routes to:
resources :conversations do
member do
post :reply
post :trash
post :untrash
end
collection do
get :trashbin
end
end
However, the URL conversations/trashbin now displays an Unknown action error:
The action 'trashbin' could not be found for ConversationsController
My ConversationsController clearly has the action defined. Why is this error appearing?
Don't use a member route - use a collection route instead:
resources :conversations do
member do
post :reply
post :trash
post :untrash
end
collection do
get :trashbin
end
end
See here for more info on this.
Put the route in a collection scope
resources :conversations do
member do
post :reply
post :trash
post :untrash
end
collection do
get :trashbin
end
end

Ruby on Rails : add a new route

I'm new with RoR so this is a newbie question:
if I have a controller users_controller.rb and I add a method foo, shouldn't it create this route?
http://www.localhost:3000/users/foo
because when I did that, I got this error:
Couldn't find User with id=foo
I of course added a view foo.html.erb
EDIT:
I added to routes.rb this code but I get the same error:
resources :users do
get "signup"
end
This doesn't work automatically in rails 3. You'll need to add
resource :users do
get "foo"
end
to your routes.rb
You'll definitely want to have a look at http://guides.rubyonrails.org/routing.html, it explains routing pretty well.
Rails is directing you to the show controller and thinks that you're providing foo as :id param to the show action.
You need to set a route that will be dispatched prior to being matched as /users/:id in users#show
You can accomplish this by modifying config/routes.rb by adding the following to replace your existing resource describing :users
resource :users do
get "foo"
end
Just to add to the other answers, in earlier versions of Rails there used to be a default route
match ':controller(/:action(/:id))(.:format)'
which gave the behaviour you describe where a request of the form controller/action would call the given method on the given controller. This line is still in routes.rb but is commented out by default. You can uncomment it to enable this behaviour but the comment above it explains why this is not recommended:
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
At the schema ':controller/:action(.:format)', you can also easily do the following
resources :users do
get "foo", on: :collection
end
or
resources :users do
collection do
get 'foo'
end
end
http://guides.rubyonrails.org/routing.html#adding-collection-routes

Why isn't my 'join' action working, it says the action 'show' can't be found

In my UserController I have:
def join
end
I have a join.html.erb in my /views/user/ folder.
My routes has a :
resources :user
When I go to:
http://localhost:3000/user/join
I get:
The action 'show' could not be found for UserController
Re: why isn't the join action found?
To answer your specific question, what's happening is that you want to have an action "join" for your User model.
Your problem is that you haven't defined a route matching the url http://localhost:3000/user/join
The line resources :user in your routes file only defines routes for the seven standard rest verbs/actions:
index, new, create, show, edit, update, destroy
See: http://apidock.com/rails/ActionController/Resources/resources
Added: to fix, you'll need to add an explicit or generic route. Routing docs
Added: Re: why am I seeing the error message re show? To be ultra-precise, the route selector "GET /usr/:id" (created by your resource call) is being used to select the SHOW action for the User resource. The :id value is being set to "join". Since you don't have a Show method defined in your controller, that's the error that you're seeing.
You're using resources, but have a non-REST action, so you need to add the join action to the route with the appropriate HTTP verb:
map.resources :users, :member => { :join => :get }
Place:
def show
end
in your UserController.
To be certain:
app/controllers/users_controller.rb
def join
end
app/views/users/join.html.erb
config/routes.rb
resources :users

Resources