Nested singular resource, not showing up in rake routes - ruby-on-rails

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

Related

Should I use resource routing even for non-CRUD routes (Rails)?

Is it considered best practice to use resourceful routes in Rails whenever possible, even if the CRUD verbs don't really match the actions being performed (details follow)?
In my Rails app, I'm implementing an OAuth login system using sorcery's external module. I closely followed their official tutorial, which defines the routes for the OAuth methods like this.
# config/routes.rb
post "oauth/callback" => "oauths#callback"
get "oauth/callback" => "oauths#callback" # for use with Github, Facebook
get "oauth/:provider" => "oauths#oauth", :as => :auth_at_provider
Basically, auth_at_provider is called when the user clicks the "Login via [Provider Name]" button, and the callback is called once they log in via the external provider.
I left the routes as-is, but a teammate reviewing it suggested we use resource routing, like this for example:
resources :oauth only: [:index, :create, :show]
I guess this is technically possible, but for me the singular routes defined in the tutorial are much more intuitive and self-explanatory. So my questions are:
Is it considered better (or common) to use resourceful routes even in cases like this? or
Are resourceful routes just a shorthand for cookie-cutter routes, and should only be used for straightforward controllers?
I wouldn't use the resource(s) helpers. The name tells you it's used for resources and oauth logic are not resources.
You could refactor the routes a little bit though
namespace :oauth do
match :callback, via: [:get, :post]
get ":provider", action: :oauth, as: :at_provider
end
This creates this routes:
oauth_callback GET|POST /oauth/callback(.:format) oauth#callback
oauth_at_provider GET /oauth/:provider(.:format) oauth#oauth
They are basically the same routes, DRYer and without misleading "resource" wording.
*Note the little change from "auth_at_provider" to "oauth_at_provider" introduced by the namespace
It is generally considered best practice to use resourceful routing when you're actually doing CRUD on a resource, ie:
resources :users # for creating, reading, updating, deleting users
If you'd have to create an entirely new resource and controller just for one create endpoint (for example), I don't see any harm in breaking the pattern and using non-resourceful routes, but I try to avoid doing so.
You should try to use resourceful routing with names that makes sense, to keep your routes consistent:
scope path: 'oauth' do
resource :callback, only: [:show, :update] # use show/update instead of callback method
resources :providers, only: [:show] # use show instead of auth_at_provider
end
So your routes would look like:
POST oauth/callback
GET oauth/callback
GET oauth/providers/:id

Overriding routes parameters with NIL in Rails?

Is there a way to use resource routing instead of writing the routes one by one if my methods for which the default expects parameters don't use parameters?
For example, if I had a routes file like below, the expected path for the update method would be like this: /cats/:id (docs)
# routes.rb
Rails.application.routes.draw do
resources :cats, only: [:create, :update]
end
However, I don't require any params for my update method, meaning the path should be /cats.
I know there's a way to rename the params and not use :id, but I didn't find anything on disabling them. I tried adding param: nil to the end of the line but it didn't work.
As I wrote initially, I know this can be done if I write the routes one by one like below. My question is whether I can use resources to do it. Thank you!
# routes.rb
Rails.application.routes.draw do
post 'cats', to: 'cats#create'
put 'cats', to: 'cats#update'
end
This is exactly the use case for singular resources. Quote from the Rails Guides:
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like /profile to always show the profile of the currently logged in user.
Change our routing to
resource :cats, only: [:create, :update]
And the following routes will be created:
cats PATCH /cats(.:format) cats#update
PUT /cats(.:format) cats#update
POST /cats(.:format) cats#create
As far as I know, there is not, resource is just a helper to create the standard verb-based CRUD routes, if you want custom routes you need to define your update route the way you did in your second example, of course, you can still use resource for your create route and just pass only: :create.

Rails routing: What does only: [] do here?

I saw a piece of Rails code for routing like this:
namespace :my do
resource :auth_states, only: [] do
collection do
get 'signed_in'
end
end
resource :password, only: [:edit, :update]
# And all the actions a logged in user can perform under "my" namespace...
# ...
end
Supposedly this app works with devise and cancancan gem. I guess the :auth_states part verifies whether a user is signed in or not before he/she can perform all the below actions. However I'm a bit confused by only: []. Doesn't that mean no actions whatsoever will be generated for :auth_states? How does this thing work then. Does it mean no visitor will be able to visit auth_states from outside, but the app itself will still be able to utilize it? Is only: [] a widely used pattern in Rails?
Thanks
only: [] contains an array of whitelisted actions to be routed for the resource. For instance, if you specify
resource :auth_states, only: [:index]
then only the index action will be generated, therefore
GET /auth_states
will work, whereas (the new action)
GET /auth_states/new
will not. Passing an empty action is a trick to use a resource as a namespace for nested routes. In fact, in your case the router will route
GET /auth_states/signed_in
but, at the same time, will not route
GET /auth_states
GET /auth_states/1234
Sometimes, you will see it in combination with a controller option
resource :authentication, controller: 'auth_states', only: [] do
collection do
get 'signed_in'
end
end
that generates
GET /authentication/signed_in
The router has a namespace method, but it automatically scopes the controller into a Ruby namespace. Using this trick is sometimes more effective, and allows to group altogether routes that belongs under the same umbrella, prefixing them with a same path.
only: [] is used as an optional parameter so that you can specify allow routing to specific actions of that particular controller - auth_states. In your case it does not seem necessary to use that. If you specifiy certain actions say only: [:index] then index action will be only routed but not others. Hope this clears your confusion.

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

The action 'index' could not be found for Controller

I am trying to make a controller with one action and when i try and go to the localhost:3000/controllername/action i get this error:
The action 'show' could not be found for LearnController
Here is my controller:
class LearnController < ApplicationController
def more
end
end
and in routes i do this:
resources :learn
I know that resources creates all the show, edit, index and all that but how do I make it so only the actions i create are created in the routes?
As you mention, resources :learn will create a bunch of routes according to the resourceful convention.
If you don't want those, don't use resources in your config/routes.rb file. Instead, use get, match, and friends to define your routes manually. E.g.
get 'learn/more'

Resources