Taking over an unused HTTP method of a resource - ruby-on-rails

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).

Related

New action not being reached in my Ruby app

I'm just learning Ruby on Rails and building a fairly simple app that stores info to a rake generated db. I'm trying to add a new action to the controller but I can't trigger it. There's a before_action getting triggered which shouldn't be.
class GamesController < ApplicationController
before_action :set_entry, only: %i[ show edit update destroy ]
before_action :authenticate_user!
def index
end
def analyse
#The function here doesnt matter, its not reaching it because its hitting the set_entry instead
puts 'howdy'
end
private
def set_entry
#entry = Entry.find(params[:id])
end
end
The error I'm hitting is Couldn't find Game with 'id'=analyse", highlighting the set_entry action, which to my understanding should only be running with the show, edit, update, and destroy actions (which are also in the full controller and running fine). There are other actions in the controller (such as create and new) which don't seem to trigger that set_entry and are running just fine as expected. For example, linking to the new path takes me to /entry/new, while linking to an edit path takes me to /entry/:id/edit, which is all fine. But it keeps linking my new analyse action trying for entry/:id/analyse when I want it to go to entry/analyse.
My button to trigger it is simply:
<%= link_to "Analyse", analyse_path %>
Which is in a navbar in my application.html.erb
And here's my routes.rb:
Rails.application.routes.draw do
resources :entry
devise_for :users
resources :users
root to: "entry#index"
get 'entry/index'
get 'entry/analyse', as: 'analyse'
end
The path /entry/analyse is matched two routes:
entry#show action in resources :entry
entry#analyse action in get 'entry/analyse', as: 'analyse'
Because the route matching is interpreted through config/routes.rb in order, and resources :entry is the first route the path is matched. At the result, entry#show action will handle the requests from /entry/analyse.
The solution is simple, just switch the order of resources :entry and get 'entry/analyse', as: 'analyse' in config/routes.rb. For example:
Rails.application.routes.draw do
get 'entry/analyse', as: 'analyse' # <- to here
resources :entry
devise_for :users
resources :users
root to: "entry#index"
get 'entry/index'
# from here ...
end
Move all of your resources to the end of your route configuration. I am not sure why its getting tripped up but it seems something within your routes is matching analyse to your entry resource and routes within rails are matched in order. All custom routes really should come first to prevent something like a generic route catching your expected action.
Best practices also state your root to: should be at the top of the configuration since it is your most popular route generally in an application.

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 - 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.

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

Custom rails routing helper method

I am having issues getting my helper method names to work properly, any suggestions would be great:
#config/routes.rb
resources :junkie, only: [:show, :index, :destroy], as: :junkie do
get :merge, on: :collection
end
So I was having issues because I the singular form of junkies is junky, but when I make this change and look at the routes it changes the #merge helper to:
merge_junkie_index GET /junkies/merge(.:format) junkies#merge
Is there any way to change this to just merge_junkie? I tried removing it from the resource black and using the match syntax: get "junkies/merge" => "junkies#merge", as: :junkie but for some odd reason this directed me to the show method even though the route was right.
The solution is a ugly one but it works, since the show route is the only one that is affected by the as: :junkie you can break it out put the merge route in a separate block. The ordering of the resource also matters for some reason, if you do not put the merge first, it will interpret the url /junkie/merge/ as a id and hit the show action. So it should look like this in your routes file:
resources :junkies, only: [:index] do
get :merge, on: :collection
end
resources :junkies, only: [:show, :destroy], as: :junkie

Resources