Rails routing: What does only: [] do here? - ruby-on-rails

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.

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.

How are resources connected to controllers without a Model

I am writing a basic application that scrapes data. I have the following in my routes.rb.
Rails.application.routes.draw do
constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :apps, :only => :show
end
end
In controllers I have something like this although I am not sure how are resources connected to Controller.
class AppsController < ApplicationController
def show
puts "this works"
respond_to do |format|
format.json { render json: #user }
end
end
def apps
puts "my app"
end
end
Also, I dont have a Model. Does that mean that in my resources :apps calls a method in AppsController called apps?
If I wanted it t call apps then how's it possible ?
how does a controller in rails know what route does it belong to
I am trying to add a GET /apps?filter=5 that returns my scraped data in the form of JSON and with filter as parameter to that it means that return 5 JSON objects to me
#config/routes.rb
constraints subdomain: 'api' do
namespace :api, path: '/' do
get :apps, to: "apps#apps", on: :collection #-> api.url.com/apps
end
end
A much more coherent way to do it would be...
#config/routes.rb
constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :apps #-> api.url.com/apps -> apps#index
end
end
I think you're getting confused with how Rails works, especially with your data.
I post this all the time, maybe it will help you:
As you can see, your request is not tied to a specific "model", nor is a controller bound to it either. I'll explain the importance of the MVC (Model View Controller) aspect of rails in a minute.
Your thought process is that each request / resource has to have a corresponding model / dataset to pull from. Strictly, this is not the case, although many would believe it to be.
Remember that Rails is just an application framework - it has to work with all the same protocols & restrictions as the other frameworks & languages out there.
When you send a request to Rails (through your browser URL), it takes that request, and matches it to the appropriate controller. This controller action will then pull data from your model (if you've set it up like that), render the view with that data, and return the processed HTML to the browser.
Thus, you don't have to have a model bound to a particular controller action, or anything. You just need to make sure your controllers & views are mapped accordingly.
OOP
I think the part you're getting hooked up on is the object orientated nature of Ruby / Rails.
Although every part of the Rails framework is meant to work with objects, this only applies on a request-basis.
For example, whilst it's typically recommended to keep your controllers resourceful, you don't have to adhere to that methodology if you don't want to. Many newbies don't know the difference.
Thus, when you use the following:
#config/routes.rb
constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :apps, only: :show #-> api.url.com/:id -> apps#show
end
end
... what you're denoting is a controller bound by its resourceful nature. This would typically be expected to use model data, but it's not essential...
In controllers I have something like this although I am not sure how
are resources connected to Controller.
Rails.application.routes.draw provides a DSL which hooks into Rack (the interface between the HTTP server and Rails). It generates rules for where to route the response from Rack.
The DSL is provides has a lot of ways to do the same things. In this example, the resources :apps, :only => :show line basically says you want to generate all of the REST verbs for the AppsController, but you only want the :show verb, so the router will only generate a route to AppsController#show. Note that you can run rake routes to get a list of your routes.
Also, I dont have a Model. Does that mean that in my resources :apps
calls a method in AppsController called apps? If I wanted it t call
apps then how's it possible ?
Models are totally separate abstractions. Once the code reaches your controller you are in plain Ruby land until you return out of that controller action. Models are simply plain Ruby objects with the ability to talk to the database.
In your code if you wanted to call apps from the show method (or action) then you can just call it from there since it's in the same scope.
how does a controller in rails know that ok that is my route. In this case, apps
I'm not sure I understand this question, could you elaborate?
I am trying to add a GET /apps?filter=5 that returns my scraped data in the form of JSON and with filter as parameter to that it means
that return 5 JSON objects to me
For one, you'll need to add a route for /apps. There are several ways you can do this. Here's one approach. I'm going to call it index instead of apps since that's more conventional:
# config/routes.rb
get '/apps' => 'apps#index'
# app/controllers/apps_controller.rb
class AppsController < ApplicationController
respond_to :json
def index
limit = params[:filter].to_i
#users = User.first(limit) # Implement this however you wish
respond_with(#users)
end
end
My syntax might be a little off here with the respond_to and respond_with, but it should explain how the controller routes
Routing simply maps URLs to a controller/action, the existence of a model with the same name does not matter.
To get to the apps action that you defined in the AppsController you need to define a route that maps to apps#apps < This syntax means AppsController, apps action.
An example of a route that would map to the AppsController apps action:
get '/apps', to: "apps#apps"
This is a weird example. It's not conventional to have a def apps action inside AppsController, what exactly are you trying to accomplish with this action?
If you want a rest call to /apps that returns a JSON list of apps, then this all you need to do.
router
resources :apps, only: [:index]
controller
class AppsController < ActionController::Base
def index
puts "This is the index route in AppsController"
end
end
In the router, when you specify resource :apps, only: [:index]. This routes the request GET /apps to AppsController#index

Rails: Routing to a different controller based on request format

I'm writing an app where I want all requests for HTML to be handled by the same controller action. I have some other routes that are JSON-specific. Here's what my routes look like:
Blog::Application.routes.draw do
constraints format: :json do
resources :posts
end
match "(*path)" => "web#index"
end
The problem is that constraints is being interpreted as "this route only works with the specified format" rather than "skip this route and try the next one if the request is not in the specified format."
In other words, navigating to /posts in the browser gives me a 406 Not Acceptable because the URL is constrained to the JSON format. Instead, I want it to fall through to web#index if the request is for HTML, and hit the resourceful route if the request is for JSON. How can this be achieved?
(Using Rails 3.2.9.)
I've thought about this, and come to some conclusions.
You might not want all routes to return the single page app HTML, because there are some paths that you'll ideally want to return an HTTP 404 status code from.
Your HTTP routes will be different from your JSON routes
You definitely should be routing different formats to different controllers
It might be advantageous to define all your HTML routes in your Rails router, so that you can use it to generate a javascript router. There is at least one gem that does this
Rails does not have this functionality and this gem https://github.com/svenfuchs/routing-filter doesn't really seem like the right tool. Here is my attempt: Rails routing-filter route all html requests to one action
Having to namespace your JSON API under a module Api to avoid routing collisions is no bad thing.
Don't have content visible to Google on your single page app, or you'll get banned for duplicate content.
I took a slightly different approach, which doesn't really answer the question, but I think it has a few advantages:
Here is my config/routes.rb
FooApp::Application.routes.draw do
# Route all resources to your frontend controller. If you put this
# in a namespace, it will expect to find another frontend controller
# defined in that namespace, particularly useful if, for instance,
# that namespace is called Admin and you want a separate single page
# app for an admin interface. Also, you set a convention on the names
# of your HTML controllers. Use Rails's action_missing method to
# delegate all possible actions on your frontend controllers.
def html_resources(name, options, &block)
resources(name, options.merge(:controller => "frontend"), &block)
end
# JSON API
namespace :api do
resources :things, :except => [:edit, :new]
end
# HTML
html_resources :things, :only => [:edit, :new, :index, :show] do
get :other_action
end
end
class FrontendController < ApplicationController
def action_missing(*args, &block)
render :index
end
end
First of all I think different controllers for the same action is not the "Rails way".
Probably you can solve your problem with low level custom request handler in the middleware.
You can use format in your constraints, e.g.
match '*path', constraints: { path: /foo.*/, format: 'html'}, to: 'home#index', via: [:get]

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