Routing error when using nested resources - ruby-on-rails

I have a Post and Comment models. Comment belongs to a Post, and it is nested under Post in routes. Comments are posted from Posts#show. My routes look like this:
resources :posts do
resources :comments, only: [:create, :edit, :update, :destroy]
end
If a user submits a comment that fails validation, the URL will look like this:
app.com/posts/:id/comments
If, for any reason, the user decides to hit enter in the address bar, there will be a routing error:
Routing Error
No route matches [GET] "/posts/21/comments"
Try running rake routes for more information on available routes.
This strikes me as some what odd. I understand why the error is happening, but it doesn't seem like it's a good idea for usability. Is there a way to prevent this from happening?
This becomes a bigger issue when doing friendly redirects. When a friendly redirect happens, Rails will redirect to that same URL using a GET request, again causing a routing error.

I think that the best way to avoid it is to create a route for this case and redirect to wherever make sense for your application. Something like the following:
match "/posts/:id/comments" => redirect {|params| "/posts/#{params[:id]}" }
Instead of that routing error, the user would be redirected to the post page.

If your routes are
resources :posts do
resources :comments, only: [:create, :edit, :update, :destroy]
end
then the URL for edit would be
app.com/posts/:post_id/comments/:id/edit
where :id is the comment. If validation fails, you should be redirecting back to this URL.
def update
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
redirect_to(edit_post_path(#post))
else
redirect_to(edit_post_comment_path(#post, #comment), :notice => "update failed")
end
end
Better, since you are already at the correct edit URL,
...
else
flash[:error] = "Error - could not update comment"
render :action => "edit"
end

Not the best but the other solution may be to add comments through nested attributes of post.

Related

Use same controller with associations in top-level and nested paths in Rails

I would like my REST API to have several routes, such as:
GET /posts/
GET /posts/1
POST /posts
GET /users/
GET /users/1
GET /users/1/posts
POST /users/1/posts
Is it possible to reuse the same controller for those nested routes under the users collection?
It looks like you want nested routes. Try this is your config/routes.rb
resources :posts
resources :users do
resources :posts
end
This has more info. You could also use match or post and get verb methods individually. There are also many options for nested routes.
https://guides.rubyonrails.org/routing.html.
ALTERNATIVELY
in config/routes.rb:
get 'users/:id/posts', to: 'users#posts'
and in controllers/users_controller.rb
before_action :set_user, only: [:users_posts, :show, :edit, :update, :destroy]
...
def posts
#posts = #user.posts
end
With the second option you can KISS by keeping POST/PATCH/UPDATE/DESTROY at their native home like /posts and /posts/42. Just treat :user_id as a form variable in that case, with whatever extra validation you might need, perhaps referencing a session var.
LASTLY
You can actually put this in your config/routes.rb. But now you're probably writing new forms because :user_id is a route parameter. I'd file that under extra complexity. Maybe it fits your situation though.
post 'users/:id/posts', to: 'users#posts_create'

Rails 4 user routing

<%= link_to "Whatever", current_user %>
is linking to /user.id
My routes are setup like this
resource :user, except: [:index, :destroy]
So it should be linking to /user, right?
When I visit /user it says "Couldn't find User without an ID".
My user show action looks like this
def show
#user = User.find(params[:id])
end
The reason you are getting /user.id is because you have defined your routes as
resource :users, except: [:index, :destroy]
Note the singular resource which will create all the routes without any :id. As you don't have an accepting param within the route, the current_user that you are passing is matched to the format i.e., like .html, .js, etc. which in your case becomes .id
I would recommend using resources (note the plural)
resources :users, except: [:index, :destroy]
This will resolve the error Couldn't find User without an ID as you would be passing an params id within your route.
NOTE:
As per Rails convention, controller name should be plural. For UsersController, resources should be resources :users
I have had this happen many times. My fix is to use the path helpers.
<% link_to "Whatever", user_path current_user %>
This will drop the .id and make it /user/id

Rails Cancan load_resource with nested routes

I am having a problem that I thought would have been answered somewhere but I cannot find it So I have my route like this
resources :users, only: [:show, :edit, :update] do
get :resend_invitation
end
So of course the :resend_invitation route looks like this /users/:user_id/resend_invitation
It seems like Cancan only loads resources with the :id parameter. I cannot find in the docs how to specify to include the :user_id parameter as well. I just want to automatically load my resources for my nested routes as well.
If anyone has any insight I would be very grateful.
Thanks
EDIT FOR CLARITY:
My end goal is that I want #user to be populated in my nested routes
def resend_invitation
# #user = User.find(params[:user_id] done by cancan
#user.something
end
hav tim, check this in cancan's github:
https://github.com/ryanb/cancan/issues/178#issuecomment-486203
also this:
https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions
there is your answer

How do I re-write this route to be shorter?

I have resources :tags in my routes.rb.
So when I go to /tags/ios, it shows me the correct Tag#Show view.
What I would like to happen is when the user goes to /tags/iosit shows it as /ios and likewise I want that generated path to always be /ios (and not have the leading /tags).
Here is an example of how I am rendering that link, within an each block:
<%= link_to "#{tag.name}", url_for(tag) %>
In your routes.rb
resources :tags
match '/:tag_name' => 'tags#show'
Then in your tags_controller#show action you have access to the tag name via:
params[:tag_name]
Be sure to put that at the end of your routes file, as it will catch everything. Also, since it will catch everything you should render a 404 if it's not a valid tag name:
def show
unless #tag = Tag.where(name: params[:tag_name]).first
raise ActionController::RoutingError.new('Not Found')
end
end
To redirect /tags/ruby to /ruby:
match "/tags/:tag_name" => redirect("/%{tag_name}")
http://guides.rubyonrails.org/routing.html
well the problem here is namespace
if you define a route like
get "/(:slug)" => "Tags#show"
that will basically match anything where its a valid tag or not. The way I would do it would be
in your routes file:
begin
Tag.all.each do |t|
begin
get "#{t.slug}" => "Tags#show"
rescue
end
end
rescue
end
Then in your tags controller you can
def show
slug = request.env['PATH_INFO']
#tag = Tag.find_by_slug(slug)
end
My answer comes from another answer I wrote that you may be interested How to create app-wide slug routing for Rails app?
So even though I got lots of nice suggestions from these guys, I found what I think is the best solution for me.
All I am trying to do is basically make /tags/ruby be rendered as /ruby both in the actual URL on the links of all the tags, and in the URL bar.
I don't want to do anything that will add load to my app.
This is the solution that works for me:
resources :tags, path: "", except: [:index, :new, :create]
Using path:, you can specify what you want the path to display as. i.e. what do you want to appear before your resource. So if you wanted your URLs to look like myhotness/tags/ruby, then you would simply do path: "myhotness".
I didn't want anything in my path, so I just left it blank.
For what it's worth, you can even add a constraint to that route, like so:
resources :tags, path: "", except: [:index, :new, :create], constraints: { :id => /.*/ }

Rails Controllers => params[]

Can someone please help me understand params in nested attributes a little better?
I am using Apotomo. But for the example. We could just assume its in the ApplicationController
I have a simple controller show action.
if params[:id].present?
#menu = Menu.find(params[:id])
else
#menu = Menu.first
end
Which checks to see if a menu id is specified in the URL. If not, it shows the first menu.
This works well as long as I'm only on the /menus/ URL.
But I have nested attributes. So once we visit URL /menus/17/categories/
It finds params[:id] as that of the category, not the menu.
Once I'm nested, I can call :menu_id, and it works fine. But no longer works on the parent object.
How do I look for params[:id] of the menu object regardless of where I am in the URL?
And am I missing something completely?
Here is my routs config as well:
resources :menus, :only => [:show, :home] do
resources :categories, :only => [:index, :show]
end
Thanks for your patience.
I would check how routing is defined. Maybe there is a reason why this link is translated this way.

Resources