Rails Cancan load_resource with nested routes - ruby-on-rails

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

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'

How to use "link_to" in an Admin section correctly in Rails

So I have an app which has an admin section. The admin section has a challenges controller with an index method and a view index.
I have also a challenges controller seperate from the admin folder. This controller has the whole CRUD.
Every challenge belongs_to a subject. The controller subjects in the admin section has an index method and view. The controller subjects not in the admin section has the whole CRUD.
Now, in the view of subjects (NOT the admin section), I can do something like:
<%= link_to "New Challenge".html_safe, new_subject_challenge_path(#subject) %>
I would like to do the same in the admin-section, but I can't really figure out how to do it. Copying the code throws me an error:
No route matches {:action=>"new", :controller=>"challenges", :subject_id=>nil} missing required keys: [:subject_id]
But I was hoping I could do this without additional routes....
It seems like it should be easy but I don't really know how to handle this. Any help would be very much appreciated... I hope I explained myself well enough.
The admin routes are used with a namespace:
namespace :admin do
resources :paths, only: [:index, :new, :create, :update, :edit]
resources :users, only: [:index, :new, :create, :show, :edit, :update]
end
resources :challenges, except: [:index, :destroy] do
resources :solutions, only: [:create]
end
resources :subjects
The link you are creating points to a route that requires a subject id. In the subjects view it works because Rails can find the subject_id in the #subjectthat you are passing to the path helper.
When you copy and try to re-use the same link in your admin view, I expect that #subject is not assigned and so it cannot find the required subject_id. Provide your admin section view with the subject and it should work!
Also if you want to get a clearer idea of routing, the Rails docs are pretty great.

Rails link_to polymorphic parent, which can have a nested route

I have the Comment model, which is polymorphic associated to commentable models like Project, User, Update etc. And I have a page where a user can see every User's comment. I want a link near each comment with an address of an object this comment is associated with.
I could write something like that:
link_to 'show on page', Object.const_get(c.commentable_type).find(c.commentable_id)
But this will work only for not nested routes (like User). Here's how my routes look like:
resources :users do
resources :projects, only: [:show, :edit, :update, :destroy]
end
So when I need a link to a Project page, I will get an error, because I need a link like user_project_path.
How can I make Rails to generate a proper link? Somehow I have to find out if this object's route is nested or not and find a parent route for nested ones
You could use a bit of polymophic routing magic.
module CommentsHelper
def path_to_commentable(commentable)
resources = [commentable]
resources.unshift(commentable.parent) if commentable.respond_to?(:parent)
polymorphic_path(resources)
end
def link_to_commentable(commentable)
link_to(
"Show # {commentable.class.model_name.human}",
path_to_commentable(commentable)
)
end
end
class Project < ActiveRecord::Base
# ...
def parent
user
end
end
link_to_commentable(c.commentable)
But it feels dirty. Your model should not be aware of routing concerns.
But a better way to solve this may be to de-nest the routes.
Unless a resource is purely nested and does not make sense outside its parent context it is often better to employ a minimum of nesting and consider that resources may have different representations.
/users/:id/projects may show the projects belonging to a user. While /projects would display all the projects in the app.
Since each project has a unique identifier on its own we can route the individual routes without nesting:
GET /projects/:id - projects#show
PATCH /projects/:id - projects#update
DELETE /projects/:id - projects#destroy
This lets us use polymorphic routing without any knowledge of the "parent" resource and ofter leads to better API design.
Consider this example:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :projects
resources :users do
# will route to User::ProjectsController#index
resources :projects, module: 'user', only: [:index]
end
end
class ProjectsController < ApplicationController
def index
#projects = Project.all
end
# show, edit, etc
end
class User::ProjectsController < ApplicationController
def index
#user = User.joins(:projects).find(params[:user_id])
#projects = #user.comments
end
end
This would let us link to any project from a comment by:
link_to 'show on page', c.commentable
And any users projects by:
link_to "#{#user.name}'s projects", polymorphic_path(#user, :projects)

Why does this route setup result in these path variables?

In my rails 4 application, I have a piece in routes.rb that looks like this:
namespace :settings do
resources :profile, only: [:index] do
put :update_user, on: :collection
end
end
The controller is located in app/controllers/settings/profile_controller.rb and looks like this:
class Settings::ProfileController < ApplicationController
def index
end
def update_user
end
end
This results in these paths from rake routes:
PUT update_user_settings_profile_index -> /settings/profile/update_user(.:format)
GET settings_profile_index -> /settings/profile(.:format)
What I don't understand is why these paths have _index in them. I would like to get rid of it. Can I? If so, how?
I think that's because you're using a singular name profile for your resources definition, which should be plural by convention. You can try using resources :profiles instead.
For me, with routes it is always easier to work backwards, asking what are the paths I want? I'm not sure what paths you want, but I'll give you some ideas here, I hope.
I'm going to assume you want one index path, since you explicitly include the the only: [:index] statement, but you do not want not a indexed path on your resources.
Try moving your only: [:index] statement into the outer do loop for settings and add an only: [:update] to your profile (or whichever action you're looking for)
namespace :settings, only: [:index] do
resources :profile, only: [:update] do
put :update_user, on: :collection
end
end
Gets you here:
update_user_settings_profile_index PUT /settings/profile/update_user(.:format) settings/profile#update_user
settings_profile PATCH /settings/profile/:id(.:format) settings/profile#update
PUT /settings/profile/:id(.:format) settings/profile#update

How to custom ActiveAdmin using find_by request instead of ID for all actions

I'm just adding ActiveAdmin to my app, I got a problem using show/edit/destroy action cause my link doesn't point to ID but to users name (in order to be more readable for user).
ActiveAdmin correctly create my link like:
edit link:
http://localhost:3000/admin/users/paul/edit (where paul is the user name)
in that case I get:
Couldn't find User with ID=paul
cause of course Paul is not the id but the user name.
How can I custom ActiveAdmin to use find_by_name(params[:id]) like in my application for all the action show/edit/delete?
In other model I got a so called "SID" which is a generated salted ID and I would like to use also the find_by_sid(params[:id]) as well for other models.
There is a cleaner way to do this:
ActiveAdmin.register User do
controller do
defaults :finder => :find_by_slug
end
end
This will do the job in the app/admin/user.rb :
ActiveAdmin.register User do
before_filter :only => [:show, :edit, :update, :destroy] do
#user = User.find_by_name(params[:id])
end
end
If you followed this railscast: http://railscasts.com/episodes/63-model-name-in-url-revised and have custom routes, you can fix the active_admin routes by placing this in the app/admin/user.rb:
before_filter :only => [:show, :edit, :update, :destroy] do
#user = User.find_by_slug!(params[:id])
end
It's really close to the one shown by afiah, just slightly different.

Resources