Confused about nested resources and authentication in Rails - ruby-on-rails

Let's see if I can explain myself well enough about the doubts I have.
I have a User model that is managed by Devise. So in my routes I have:
devise_for :users
In the User model, I have an association with the model Plan. The assocation is:
User has_many Plans
Plan belongs_to User
At this point I also have a resource for the Plan model, so I can fetch all the Plans, show a particular plan and so on. But I want to go further.
I want to be able to see plans of a particular User and let a particular User to see his own plans and edit them.
So, for example, whenever I go to:
/users/:id/plans
I want to be able to see the plans for that particular :id user. And if the user who is visiting that url is the one that is logged in, I want him to be able to edit those plans.
How can I manage all this behavior? Is there any gem out there that helps with it? Or I need to do conditionals in the views saying if current_user...

Let's start with routes, you can make your routes like this:
resources :users do
resources :plans, only: [:index]
end
resources :plans, except: [:index]
I used resources :plans inside resources :users to have route like this /users/:user_id/plans, while the resources :plans outside is for the rest of the actions (edit, destroy, ...) that don't require a user_id, i.e., a plan is identified by a unique id so you don't need a user_id to fetch it from the db for editing or destroying.
Now for the controller, we can make it like this:
class PlansController < ApplicationController
before_filter :is_plan_owner?, only: [:edit, :update]
def index
#plans = Plan.where(:user_id => params[:user_id])
end
def edit
#plan = Plan.find(params[:id])
end
private
def is_plan_owner?
if current_user != Plan.find(params[:id]).user
# Scream, shout, call 911 and/or redirect else where
end
end
end

This is no different than using any other nested resource. The call to devise_for in the routes.rb file does not provide RESTful routing to the user model. Think about it without the nested resource for a minute, with just a standard Devise install. If you were to rake routes you would get something similar to the following:
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
user_password POST /users/password(.:format) devise/passwords#create
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
sign_in GET /sign_in(.:format) devise/sessions#new
This provides nothing for indexing or showing users, so you would still need to add routes for that:
resources :users, only: [:index, :show]
Now you get:
users GET /users(.:format) users#index
user GET /users/:id(.:format) users#show
Ok, now we're getting somewhere, then it's simply adding the nested resource, all the while Devise pays it no mind.
resources :users, only: [:index, :show] do
resources :plans
end
Which gives you the resourceful routing you desire
user_plans GET /users/:user_id/plans(.:format) plans#index
POST /users/:user_id/plans(.:format) plans#create
new_user_plan GET /users/:user_id/plans/new(.:format) plans#new
edit_user_plan GET /users/:user_id/plans/:id/edit(.:format) plans#edit
user_plan GET /users/:user_id/plans/:id(.:format) plans#show
PUT /users/:user_id/plans/:id(.:format) plans#update
DELETE /users/:user_id/plans/:id(.:format) plans#destroy
And that's really all there is to it. Devise stays out of your way on this one.

Related

Overuse route customizations - customize_count (Rails 5)

I am using the rails_best_practices gem which tells me i have an error:
overuse route customizations (customize_count > 8)
resources :stores do
collection do
get :api
end
member do
get :printer
get :delete
get :inventory
delete :inventory
get :daysheet
get :detailed_daysheet
get :labels
patch :restore
patch :print_labels
post :daysheet
end
end
Resulting in these paths:
api_stores_path GET /stores/api(.:format) stores#api
printer_store_path GET /stores/:id/printer(.:format) stores#printer
delete_store_path GET /stores/:id/delete(.:format) stores#delete
inventory_store_path GET /stores/:id/inventory(.:format) stores#inventory
daysheet_store_path GET /stores/:id/daysheet(.:format) stores#daysheet
detailed_daysheet_store_path GET /stores/:id/detailed_daysheet(.:format) stores#detailed_daysheet
labels_store_path GET /stores/:id/labels(.:format) stores#labels
DELETE /stores/:id/inventory(.:format) stores#inventory
restore_store_path PATCH /stores/:id/restore(.:format) stores#restore
print_labels_store_path PATCH /stores/:id/print_labels(.:format) stores#print_labels
POST /stores/:id/daysheet(.:format) stores#daysheet
After refactoring, I need it to still function as it does now with get routes such as /stores/7/inventory and /stores/18/printer
How can i compress these get routes to accomplish the same routing goals?
One approach would be to do:
resources :stores do
scope module: :stores do
resource :printer, only: [:show]
resource :daysheet, only: [:show, :create]
resource :detailed_daysheet, only: [:show]
resource :inventory, only: [:show, :destroy]
resources :labels, only: [:index]
resources :print_labels, only: [:update]
resource :restore, only: [:update]
end
collection do
get :api
end
member do
get :delete
end
end
Which gives you:
store_printer GET /stores/:store_id/printer(.:format) stores/printers#show
store_daysheet GET /stores/:store_id/daysheet(.:format) stores/daysheets#show
POST /stores/:store_id/daysheet(.:format) stores/daysheets#create
store_detailed_daysheet GET /stores/:store_id/detailed_daysheet(.:format) stores/detailed_daysheets#show
store_inventory GET /stores/:store_id/inventory(.:format) stores/inventories#show
DELETE /stores/:store_id/inventory(.:format) stores/inventories#destroy
store_labels GET /stores/:store_id/labels(.:format) stores/labels#index
store_print_label PATCH /stores/:store_id/print_labels/:id(.:format) stores/print_labels#update
PUT /stores/:store_id/print_labels/:id(.:format) stores/print_labels#update
store_restore PATCH /stores/:store_id/restore(.:format) stores/restores#update
PUT /stores/:store_id/restore(.:format) stores/restores#update
api_stores GET /stores/api(.:format) stores#api
delete_store GET /stores/:id/delete(.:format) stores#delete
stores GET /stores(.:format) stores#index
POST /stores(.:format) stores#create
new_store GET /stores/new(.:format) stores#new
edit_store GET /stores/:id/edit(.:format) stores#edit
store GET /stores/:id(.:format) stores#show
PATCH /stores/:id(.:format) stores#update
PUT /stores/:id(.:format) stores#update
DELETE /stores/:id(.:format) stores#destroy
Naturally, this requires that you create a number of new, nested controllers, such as Stores::Printers which will reside in app/controllers/stores/printers_controller.rb. But, you're now using standard RESTful routes, which I guess some people thing is a good thing.
Also, for your nested routes, you'll have :store_id in the params instead of id.
That collection api and member delete still seem odd, but I'm not sure what the intention is there.

Rails same route (POST /users) for different controller actions [Devise]

I have a rails api application where I am using Devise gem for user management. I created a user model off the devise gem. After that, I noticed that I have two same routes listed in the rake routescommand. I want POST (/users) to call api/v1/users#create action first and then call devise/registrations#create.
user_registration POST /users(.:format) devise/registrations#create
api_users POST /users(.:format) api/v1/users#create {:format=>:json}
When I test POST (/users) using users_controller_spec file, api/v1/users#create action is called. However, when I do a POST (/users) using POSTMAN, the logs indicates that devise/registrations#createaction is called instead.
How do I correct this so that the POST (/users) I do using POSTMAN or curl calls api/v1/users#create first to create the user model and then calls devise/registrations#create to register the user?
I am not 100% sure how devise works so any help here would be helpful.
This is my config/routes.rb
Rails.application.routes.draw do
devise_for :users
# Api definition
namespace :api, defaults: { format: :json }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
# We are going to list our resources here
resources :users, only: [:show, :create, :update, :destroy]
resources :sessions, only: [:create, :destroy]
end
end
end
So, the thing with Rails Routes is, when you make a request, routes are checked as they are defined in the routes.rb from top to bottom.
Now, when you make a request via POSTMAN, the /users path matches with a path generated via devise_for, as it is the first line in the file.
Now, when you are writing tests for the controller, you are not really accessing /users, you are just telling the api/v1/users_controller to invoke the create method, which is bound to hit the api/v1/users#create
Now, a way you can resolve this conflict is by changing what devise names its routes. If you do something like this:
Rails.application.routes.draw do
devise_for :users, path: 'customer'
# Api definition
namespace :api, defaults: { format: :json }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
# We are going to list our resources here
resources :users, only: [:show, :create, :update, :destroy]
resources :sessions, only: [:create, :destroy]
end
end
end
This is what the devise routes will be:
new_user_session GET /customer/sign_in(.:format) devise/sessions#new
user_session POST /customer/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /customer/sign_out(.:format) devise/sessions#destroy
user_password POST /customer/password(.:format) devise/passwords#create
new_user_password GET /customer/password/new(.:format) devise/passwords#new
edit_user_password GET /customer/password/edit(.:format) devise/passwords#edit
...

Rails: How to suppress the generation of certain routes

I have this route in my routes.rb
resource: session
It generates the following routes
session_path POST /session(.:format) sessions#create
new_session_path GET /session/new(.:format) sessions#new
edit_session_path GET /session/edit(.:format) sessions#edit
I do not require edit_session_path (at least I don't know yet whether I require it or not) and I have a custom route for signin, so I don't want new_session_path.
Is there a way to tell Rails to not generate these two paths?
resources :sessions, :except => [:new, :edit]
Alternatively, if you know which actions you want you can provide it directly via only, instead of excepting them:
resources :sessions, only: [:create]
If you need more than 1 resource to configure
with_options(except: [:new, :edit]) do |opt|
opt.resource :session
opt.resource :another_resource
opt.resources :people
end
like in this post
or like this - similar to the upper answer.

How to separate (new,create,delete) actions and show action in Rails for a blog?

Im trying to build a blog and having some issues. The blog have a separate dashboard with its own layout /dashboard. Imagine that I have a model named post. In the blog, the blog post URLs will be like /post/hello-world. Normally its easy to add, edit and delete these posts. I just have to add resources :posts in the routes and edit like /post/new.
But I need to move the new, create and destroy actions to the dashboard. So it will be like dashboard/post/new. I tried to do this by adding the new action in dash controller and trying to post to post create action. It failed. Tried to change only the path for create (post) and new actions and it also failed.
My question is, in a situation like this, what is the standard way to do things?
You can use :only/:except options in the routes
resources :posts, :except => [:new, :create, :destroy]
scope 'dashboard' do
resources :posts, :only => [:new, :create, :destroy]
end
Output of rake routes
posts GET /posts(.:format) posts#index
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#update
POST /dashboard/posts(.:format) posts#create
new_post GET /dashboard/posts/new(.:format) posts#new
DELETE /dashboard/posts/:id(.:format) posts#destroy
Note: If you want a separate controller for for dashboard, you can use namespace 'dashboard' instead of scope
I'd recommend to set dashboard layout in post controller
class PostsController < ApplicationController
layout "dashboard"
def create
# your code for create
end
end

Rails routes map incorrectly

I have the following statement in routes:
resource :users, :only => [:index, :show, :update, :destroy] do
get "users/dashboard"
end
However, when I do rake routes, I got:
dashboard_users GET /users/users/dashboard(.:format) users/users#dashboard
users GET /users(.:format) users#show
PUT /users(.:format) users#update
DELETE /users(.:format) users#destroy
Note that users_path was incorrectly mapped to users#show. I was expecting:
users GET /users/:id/(.:format) users#show
What might cause this issue and how I can fix it?
you should use resources instead of resource. e.g.
resources 'users'
the difference between resource and resources is:
resource is for operations for a single model. when using resource model, there's no "index" action.
see: http://guides.rubyonrails.org/routing.html
further more, I notice that your route is a bit strange. if you just want to add an action named 'dashboard' with GET accessing method, just declare like this:
resources 'users' do
get :dashboard
end
Instead of doing this
resource :users, :only => [:index, :show, :update, :destroy] do
get "users/dashboard"
end
Do this
namespace :dashboard do
resource :users, :only=>[:index,:show,:udpate,:destroy]
end
I think this will work for you.

Resources