Two controllers or one when creating an admin area? - ruby-on-rails

When you use the namespace method to create an admin area, you are presented with these routes:
resources :stories
namespace :control_panel do
resources :stories
end
gives me:
control_panel_stories GET /control_panel/stories(.:format) control_panel/stories#index
POST /control_panel/stories(.:format) control_panel/stories#create
new_control_panel_story GET /control_panel/stories/new(.:format) control_panel/stories#new
edit_control_panel_story GET /control_panel/stories/:id/edit(.:format) control_panel/stories#edit
control_panel_story GET /control_panel/stories/:id(.:format) control_panel/stories#show
PATCH /control_panel/stories/:id(.:format) control_panel/stories#update
PUT /control_panel/stories/:id(.:format) control_panel/stories#update
DELETE /control_panel/stories/:id(.:format) control_panel/stories#destroy
Rails seems to be pushing me towards creating two controllers for the Story resource. One at app/controllers/stories_controller.rb and one at app/controllers/control_panel/stories_controller.rb
Should I use these two controllers? If I were to just use stories_controller, it would save a file, but it would be fiddly having to redirect back to the control_panel namespaced views in every single action if the user is admin. Should I use two controllers?

Use the controller option.
Something like:
namespace :control_panel do
resources :stories, controller: 'stories'
end
For custom actions use actions option
resources :stroies, actions: [:index, :show]
namespace :control_panel do
resources :stories, controller: 'stories', except: [:index, :show]
end
So you can see stories without namespace, but managing them works just in control_panel namespace.
Additional, try active_admin gem for administration. It is easy and helpful

If you have a namespaced controller, it basically puts it in another folder
There's some important inheritance stuff that goes on in the backend, but simply, you'll have another folder called apps/controllers/control_panel which you'll have to add the file to:
#app/controllers/control_panel/stories_controller.rb
Class ControlPanel::StoriesController < ApplicationController
# stuff
end
This is different to your "standard" controller, which will just reside in the standard controllers section. This is a great tutorial for you to see how this works
DRY
You may wonder why you'd use this if it wasn't DRY
The answer is the two controllers allow you to perform different tasks / functionality. For example, in our admin areas, we use this:
#config/routes.rb
resources :stories, only: [:index, :show]
namespace :admin do
resources :stories, except: :show
end
This gives us the ability to define the actions which each controller can perform (making it more secure). You can use #asiniy's solution, but it will put all your code in one controller, which could be a problem

Related

rails - Best practice for creating similar routes?

In my application, a User can make a Post, and a User can make Correction (think of it as a comment) on another user's post. Each User can have many Posts, and each Post can have many Corrections.
On each show page for a Post, there is a form to create a new Correction. This uses the user_post_corrections path.
On the show page for each User, I would like to display each Correction they've submitted for any Post. This requires a user_corrections path.
In order to achieve this, I have the following in my routes.rb:
resources :users do
resources :posts do
resources :corrections
end
end
resources :users do
resources :corrections
end
This intuitively feels bad to me, as I've created two nested routes that are very similar to one another.
Is there a better way to do this? My code is working fine as it is but is there a best practice method for implementing this kind of model?
Routing concerns are an excellent but underused tool for DRYing out your routes:
concern :correctable do
resources :corrections
end
# just an example of multiple concerns
concern :commentable do
resources :comments
end
resources :users, concerns: :correctable
resources :posts, concerns: [:correctable, :commentable]
However you should take when creating nested routes so that you are not nesting needlessly.
Often you might want the collective actions [new, index, create] to be scoped by the parent:
GET|POST /posts/:post_id/corrections
GET /posts/:post_id/corrections/new
While you want the member actions to be unscoped since you can always access a record directly if it has a unique id.
GET /corrections/:id
GET /corrections/:id/edit
PATCH /corrections/:id
DELETE /corrections/:id
To do this you would declare the routes like so:
resources :corrections, only: [:show, :update, :edit]
concern :correctable do
resources :corrections, only: [:new, :index, :create]
end
resources :users, :posts, concerns: [:correctable]
The shallow: true option does something like this but does not work well when you declare the same resources several times as it adds unscoped routes for every call.

Rails - best practice of handling restful update of a single attribute

I have :users resource with some typical attributes (first name, last name, email...) that user can update, but I also have some user attributes that typical user can not change himself or can change in a different way, for example role or deactivated_at.
A non-RESTful solution would be:
resources :users do
member do
put :change_role
put :activate
end
end
I am writing an API and my goal is to stick to REST constraints as much as possible. So my options as I see them are:
send everything to PATCH :update, and then add some more logic in that controller action
expose endpoints for those attributes and add new controllers for such cases, for example
resources :users do
resource :role, only: :update
end
expose endpoints for those attributes and route them to a new action of existing controller
resources :users do
resource :role, only: :update, to: 'users#update_role'
end
and still I am not sure what I would do with :activate?
Is there any other way, and what would be a recommended practice in this situation?

Routing to different controllers having same name at different locations

I am using Rocket_Pants gem to provide API access for my existing Rails app. I have two of controllers with the same name but stored in different locations:
I store the controller that handles HTTP requests in app/controllers, and the controller that handles API calls in sub-directory API of app/controllers.
I have the following in my Routes:
# HTTP routing
resources :posts do
collection do
get 'search'
end
end
# API routing
api :version => 1 do
resources :posts, :only => [:index, :show]
resources :posts do
collection do
get 'search'
end
end
end
However, when I rake routes, I got:
GET /:version/posts/search(.:format) posts#search {:version=>/(1)/, :format=>"json"}
GET /:version/posts(.:format) posts#index {:version=>/(1)/, :format=>"json"}
GET /:version/posts/:id(.:format) posts#show {:version=>/(1)/, :format=>"json"}
This means that my API routes are actually pointing to http controller and not API controller. How can I point my API routes to API/posts#search instead?
Given this is your 2nd question tonight on the basics of Rails routing, I strongly recommend reading the Rails Guide on the topic.
For this issue, you're looking for a scope. This is explained in the guide above.
api version: 1 do
scope module: "api" do
resources :posts, only: [:index, :show] do
collection do
get 'search'
end
end
end
end
Also
Your directory should be named app/controllers/api, not app/controllers/API
Your controller should be at app/controllers/api/posts_controller.rb with class name like class Api::PostsController < ApplicationController
I should also mention, I don't know or use rocket_pants, but their README suggests namespacing controllers for different versions in modules as well (another level of scoping above).
Better "pants rockety" answer.
routes.rb
api :version => 1, module: "api" do
resources :foobars
folder structure
-controllers
-api
foobars_controller.rb
etc
api_controller.rb
class ApiController < RocketPants::Base
foobars_controller.rb
class Api::EventsController < ApiController
version 1
etc

Making a custom route

The goal is to create a URL like this for a GET, REST API:
/manager/someID/report
example: /manager/2/report
I can get it to show in rake routes if do it this way:
get 'manager/:id/report', to: 'report#show'
But in some weblogs I read, thats the way unskilled developers write their routes! and looks like the better way is to use "nested resources" so I am banging my head over desk to get nested resources working the same way...but no success
this is what I have written so far:
resources :manager, only: [:show] do
resources :report, only: [:show], controller: 'report' do
member do
## WAT ?!
end
end
end
First, you might want to consider reading different blogs if they're calling routes like that "unskilled".
What you proposed is actually fine considering it's a non-standard RESTful route, and maybe even preferable in some cases. If you want an alternative approach, you have a couple different options. I don't think any one is more right than the other, but I prefer the first because it takes up less vertical space.
resources :manager, only: [:show] do
get 'report' => 'report#show', on: :member
end
or
resources :manager, only: [:show] do
member do
get 'report' => 'report#show'
end
end

Nested singular resource, not showing up in rake routes

I created a search and replace controller, with just an index action. Since it's meant to go under one of my restful controllers created by a scaffold, i setup the following in the routes file:
resources :sites do
resource :search_and_replace, only: [:index]
end
However, it does not appear when I run rake routes. If I switch to resources, it does. But the method name is site_search_and_replace_index. The pluralization of resource doesn't feel right either, since this is not around multiple records in a table.
The index action doesn't exist in a singular resource. This makes sense if you think of the meaning of the action: index of what, there's only one resource? Use show instead:
resources :sites do
resource :search_and_replace, only: [:show]
end
Are you sure you want to have search and replace as a resource? There might be other options that are more useful: Adding more restful actions

Resources