Maintain whitelist of Rails routes for Devise - ruby-on-rails

In a Ruby Rails project, I have an array of strings matching the controller/action syntax used by the Rails routing protocols. These are my public routes, requiring no authentication. I want to compare the list to the current controller#action in order to enforce login authentication.
The problem: I cannot figure out exactly how Rails parses a routing string and determines the appropriate controller#action. I need to replicate this functionality for the comparison, but Rails core code is quite soupy, and I haven't been able to pinpoint the logic.
To put it in terms of pseudo-code, here's a sample of my whitelist array in YAML syntax, coming out of a config file...
public_routes: [
'public',
'auth/sessions#new',
'auth/sessions#create',
'admin#login'
]
Then in my ApplicationController...
before_filter :check_authentication!
...
def check_authentication!
Settings.public_routes.each do |this_route|
# parse string this_route into a namespace::controller#action
return if [current route matches parsed route]
end
# enforce authentication procedures here
end
I already have logic to allow all actions on the PublicController (line 1 of public_routes array). Matching the other three is where I am getting tripped up.
P.S. The login enforcement is happening globally within ApplicationController in order to DRY out my controllers, and to centralize my whitelist of publicly permissible routes. I could do it inside each controller, but that's not the goal.

you can get back the current namespace, controller and action value from the params, like params[:controller], so inside your comparison loop, just make up the url. Or you can get back the request url with request.fullpath method.

Related

How to redirect old url to new url in rails using just routes?

The old sitemap of my application has already been indexed to Google now. For some one visiting my rails app with old url shouldn't go to 404.
The old url looked like this
/search?sub_category=210
Now after making them friendly, it looks like this:
/search?sub_category=milling-and-drilling.
I tried redirecting it from controller but it causes too much issues on other things. Such as filters which are using the same params. Is there a way I can do it from routes file?
Instead of using redirect_to route_path(id) you would do redirect_to route_path(object.find_by_id(id).name)
How to redirect old url to new url in rails using just routes?
AFAIK (As far as I know), no. (if through params)
The main job of routes.rb is to determine what "code" will handle the request, particularly that which matches request.url and the request.method. It does not concern yet of the request parameters nor its values: these would be handled in the controllers itself. Although, you can route based on parameters (or any information about the "request") through a routes constraint
Alternative Solution:
Instead of finding by ID, now find by "Slug name or ID".
In your models, particularly in this specific example of yours, add something like:
class SubCategory < ApplicationRecord
# if you're using a gem for the "friendly" urls, you don't need this method, and just use theirs.
def self.friendly_find!(friendly_id)
# this may not necessarily be `name`, maybe `slug`? or depending on your case
find_by(name: friendly_id) || find(friendly_id)
end
end
And in your controllers, just replace wherever you're finding a record (maybe also to other models not just SubCategory as needed be), particularly in your search action:
def search
sub_category = SubCategory.friendly_find!(params[:sub_category])
end
NOTE: friendly_find! will raise an Error if no record is found.

Dynamic routes in rails

I have a rails application which, among other things, provides a simple wrapper around API calls to a third-party service. I want to set up a route which starts with /api, but anything added on to the end of it is taken as a string variable. For example, if a client requests:
/api/apps/guid/details
...then I want to invoke the index action of the controller ApiController and make the string /apps/guid/details available to it.
I have read through the documentation on controllers and routes, but everything seems to assume that /apps/guid/details will be resources within my app, when actually I don't care about the structure of anything after /api.
How can I set up a route which allows me to do this?
You can use globbing in your config/routes.rb:
get "/api/*path", to: "api#index"
Which would be accessible in the controller via params[:path]
Details can be found in the rails guide.

Why is the scaffold generating routes like this? Why do they work?

The book "Agile development with Rails" shows in the second chapter, that you can say:
<%= link_to "Goodbye",say_goodbye_path %>
Instead of hardcoding the path to "/say/goodbye". Makes sense, I thought to myself. Probably Ruby is splitting the say_goodbye_path by _, assigns the first part as the controller name, the second part as the action name. But, afterwards, I generated the following scaffold:
rails generate scaffold User name:string mail:string
And I noticed in the index.html.erb view, that it had methods like: edit_user_path(user). I tried to rewrite it to user_edit_path(user), but of course, it didn't work. My question is, why are the scaffold links the other way around? How would I know if I should write them in the way the author uses them in link_to, or in the way they are generated by the scaffold. Can you shed some light on this?
The helper functions like user_edit_path are automatically generated by rails to map operations on resources to the matching routes and thus HTTP paths and HTTP verbs. You have to understand that you are dealing with resources here, not necessarily with simple controllers.
While most of the time your resources can map to a single controller, it doesn't have to be that way. You can have nested or combined resources which can result in rather complex routing definitions.
Resources are typically defined by giving it a name (userin this case) and defining some allowed operations on them. Rails encourages to follow the REST pattern there, so you can have shortcuts to have some operations pre-defined. One of them is edit, which by default matches to a GET request to users_controller#edit. Default operations on RAILS resources are:
HTTP verb path matching controller action
===================================================
GET /users #=> index
GET /users/1 #=> show
GET /users/new #=> new
GET /users/1/edit #=> edit
PUT /users/1 #=> update
POST /users #=> create
DELETE /users/1 #=> destroy
These mappings can be customized on your routes.rb (changing methods, adding or removing operations, ...) Generally you are encouraged to use the default mappings as these are supported by standard helpers and make your app easier to understand.
Scaffolds are code generated by a template which is not related to the routing.
Routing is based on the route.rb in your config folder. All resources are routed by default (when generated by scaffolds) but there's a default rule /:controller/:action/:id that you can enable. Think of a "catch all" case.
One way to see what routes to have is to edit route.rb and run rake routes and see how they change. There's an official guide here too: http://guides.rubyonrails.org/routing.html

Create a routes to a controller without model with a /id

I have product with a foreign collection_id key
I want to pass an id to a controller.
To do so i have the following routes for my controller :
controller :magasin do
get "magasin" => "magasin#index"
end
The only view in my controller is magasin/index.html.erb
The link to magasin is link_to collection.nom, magasin_path(collection)
This kind of syntax usually works in controllers with models. Here my link is : http://localhost:3000/magasin.2 instead of http://localhost:3000/magasin/2
Later on i will need to call the same view with product_kind_id instead of collection_id and i will add sort by name/price ....
How can i have the ID as a normal argument (/:id)instead of a type(.id)?
A popular URL schema to follow is RESTful routing. Rails has this built-in and will set it up for you if you initialize your resource via rails generate scaffold Magasin nom:string.
If you put resources :magasins in your routing file, it will route /magasins to MagasinsController#index and /magasins/1 to MagasinsController#show with params[:id] set to "1". It will also set up a few other routes that will be useful in the future but for now will just raise an action not found exception.
You don't want to use the dot as an argument delimiter, since Rails places what comes after the dot in the request.format method or just in params[:format] (ordinarily accessed through the respond_to method that comes with the generated scaffolds). Save that dot for later when you are working on delivering alternative display formats like XML and JSON.
I realize I've said a lot in a small space, so feel free to ask any follow up questions once you've consulted the Rails Guide on the issue, and I'll be very glad to help!

How to map non-REST URLS to REST ones?

I have a small rails app that has default scaffold generated routes eg. /stadia/1.xml. However I have to support legacy client app that can't construct such URLs correctly. What I need to do is to map URL in the form:
/stadia?id=1?format=xml to /stadia/1.xml
Or even something like:
/myApp?model=<model_name>?id=<id>?format=xml to /<model_name/<id>.xml
Is it possible to craft appropriate route in Rails?
I don't have good answer for this. What I would do is change first part of url to /stadia_legacy for legacy urls or change first part of urls for RESTful routes.
Then you can map in routes:
map.stadia_legacy :stadia_legacy, :controller => 'stadias', :action => 'please_redirect_me'
Then in stadias controller in action please_redirect_me you can check all params (they are availble in params hash: params[:id], params[:format] etc.) and redirect to correct url. Or you can write all routes manualy to correct controller and action.
What if you do some url rewrite in apache ?
I had a similar question. No answers so far, so it seems routes.rb config doesn't offer an easy way of doing this (routing based on query parameters), which I find surprising actually.
So an ugly workaround would be to have a 'myApp' default route, and then have a special redirecting controller which would look at the query params (because in controllers you do have access to that) and redirect accordingly.

Resources