Rails - best practice of handling restful update of a single attribute - ruby-on-rails

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?

Related

change routes URL from a random :id to :username in rails

I want to change my routes.rb file in a way so that it changes my current urls
localhost:3000/amitian/1
to localhost:3000/username
I will provide my routes.rb file for reference
Rails.application.routes.draw do
devise_for :amitians
root 'home#index'
resources :amitians do
member do
get :following, :followers
end
end
resources :confessions do
member do
get 'like' , to: 'confessions#upvote'
get 'dislike' , to: 'confessions#downvote'
end
resources :confessioncomments
end
resources :relationships, only: [:create, :destroy]
end
As Gregg mentioned, you can change the name of the parameter using:
resources :amitians, param: :username
But you're essentially just renaming a variable. Whether you expect an id or a username is determined in the controller action amitians#show:
#amitian = Amitian.find(param[:id]) # Treat :id as id
#amitian = Amitian.find_by_username(param[:id]) # Treat :id as username
Now, if you want to specifically route to /:username rather than /amitians/:username, you'll have to override that resource route:
resources :amitians, except: [:show] do
member do
get :following, :followers
end
end
get '/:username', to: 'amitians#show'
However, I would recommend against that. Having a parameter directly off root will cause lots of confusion for you when users type in the incorrect url and get a user page instead of a 404 error. Or even worse, what if a user chooses the username 'login' or 'register'? Either your register page would be unreachable or else that user's page would be.
I should also point out that rails convenience methods such as resources, Amitian.find, url_for #amitian, link_to #amitian etc. all use the REST standard which uses numerical IDs.
If you want to use a username instead of IDs, you'll have to stop relying on these methods and change your controllers and views, in addition to your routes file.
In rails 4 you can do the following in the route
resources :amitians, param: :username

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 routes create resource but don't generate any on the 7 default actions

I have the following routes defined:
resources :users do
resources :suggestions do
post :make, on: :collection
end
end
I would like to tell resources :suggestions not to create any of the 7 default actions because I don't need them right now. With :only and :except I can limit the number of those actions being created, but I don't seem to figure how to create none of them.
Indeed, I could just leave the resources out and just define my :make route, but I would like to benefit from the automatic nesting and param handling, as well as leave a door open for a future need of several of the 7 default actions.
Hope this is helpful:-
resources :suggestions, only: [] do
end

Taking over an unused HTTP method of a resource

I have a restful Rails 4.2.1 controller with routes like so:
resources :users do
resources :labels, only: %w(index show update destroy)
end
Because this controller's model is incredibly tiny (just a name, an ID, and a couple associations that you wouldn't edit with this controller), I'd like to support mass-editing by PUTting or PATCHing the /users/:user_id/labels route. (PUT and PATCH are unused on the collection, and capture the semantic I'm trying to implement—that is, editing the whole collection.)
The problem is, I don't see how I can do that. I can say something like:
resources :users do
resources :labels, only: %w(index show update destroy) do
collection do
patch 'all', to: 'labels#update_many'
end
end
end
But that would result in a route of /users/:user_id/labels/all. Leaving the name string empty or nil seems to silently fail (rake routes doesn't show any indication of the route), and omitting it entirely causes an error. Meanwhile, doing something like this:
resources :users do
resources :labels, only: %w(index show update destroy)
member do
patch 'labels', to: 'labels#update_many'
end
end
Results in a route of /users/:id/labels, which is almost right except that the user ID comes in as :id instead of :user_id, which makes it difficult to handle fetching the user object.
I suppose I could hardcode the entire route in a root-level match command, but that just feels wrong.
Is there some trick I'm not aware of for doing this?
Try this:
resources :users do
resources :labels, only: %w(index show update destroy)
patch 'labels', to: 'labels#update_many'
end
The result route is:
PATCH /users/:user_id/labels(.:format) labels#update_many
This little trick also works:
resources :labels, only: %w(index update destroy) do
root to: 'labels#update_many', via: [:patch, :put], as: ''
end
When you do this, it wants to generate a redundant user_labels_root_path helper; the as: '' suppresses that.
However, Dabrorius's answer above is cleaner, so I'm going to use that one instead (modified to match both PATCH and PUT).

Two controllers or one when creating an admin area?

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

Resources