Rails Routing Based on Session Parameter - ruby-on-rails

For a specific URL, we want to redirect ONLY users in a certain state (defined by session[:state_code] to be directed to a new URL.
All other users should see and redirected to the old URL. How can we do this effectively in Rails? We can't access the session from a route constraint. For complex issues I won't get into, we need to do this redirect in routes.rb and not perform this in the controller.

You could access session throw request parameter - request.session.
Example:
Rails.application.routes.draw do
root to: redirect('<specific URL>'), constraints: lambda { |request|
request.session[:state_code] == <specific value>
}
root to: redirect('<old URL>'), as: 'root_old'
end
For different possibilities to declare advanced constraints look in Rails official doc - Advanced Constraints.
The same could be achieved by providing a block to redirect (for more details see Redirection):
Example:
Rails.application.routes.draw do
root to: redirect{ |path_params, request|
request.session[:state_code] == <specific value> ? '<specific URL>' : '<old URL>'
}
end

I've been working on some something similar. As far as I understand, what you are specifically wanting to do is not possible, or at least easily. The issue is that the routes.rb has not knowledge of the sessions 'hash' so there is no way to look it up.
http://bjedrocha.com/rails/2015/03/18/role-based-routing-in-rails/
This blog helped me, and the alternative is to persist the user state in the database. hope this helps!

Just use request.session[:state_code] as #ych suggests
get('/your_route', constraints: AuthenticatedConstraint.new, to: redirect do |_p, req|
req.flash[:error] = 'not_authorized
'/'
end)
and define AuthenticatedConstraint in app/constraints with def matches?(request) that returns true/false

Related

Redirect to another Domain except if the url contains an identifier

Sorry if the Question Title is a bit off, couldn't think of something more descriptive.
So I have 2 Domains: aaa.com and bbb.com.
I want my second domain bbb.com to redirect always to aaa.com EXCEPT IF it has a certain path: bbb.com/ct/:id
Both domains right now hit the same Heroku App.
So in my Application Controller I guess, or Routes I, have to scan the request URL and if it contains /ct/:id let it continue to the controller action, if not redirect to aaa.com.
Or can I somehow Set up the the DNS to only redirect if the ct/:id is not in the url ?
Basically I want to achieve that a User can't navigate the App from bbb.com except if the specific url bbb.com/ct/:id is present.
I'm using GoDaddy as a Registar and have 2 Cnames set up to my Heroku Apps.
How do I accomplish this behavior ?
I prefer to do your redirects in an actual controller and not in the route file so you can write a controller spec for it.
# app/controllers/application_controller.rb
before_action :redirect_bbb_to_aaa
def redirect_bbb_to_aaa
if request.host == "http://bbb.com"
redirect_to some_aaa_path unless request.url == "http://bbb.com/ct/:id"
end
end
You can create a rack middleware to do the redirects. One such example is here https://github.com/gbuesing/rack-host-redirect/blob/master/lib/rack/host_redirect.rb
OR
Use constraint in routes.rb.
Note: I have not tested it.
get "*path" => redirect("http://aaa.com"), constraint: lambda { |request| !request.url.match(/ct\/\d/) }

Rails route only if resource is present

I want to have a rails route that triggers if the given id is present, but falls back to further route matching if not.
Specifically, I want to have promo codes at the root level of my site. So, you can go to foo.com/save87 or foo.com/xmasspecial, and it'll treat it as a promo code, but if you go to a promo code that's not there (foo.com/badcode), that route will not match and rails will continue down the route list.
In an ideal world, I'd do something like this in my routes.rb file:
get '/:promo_id' => 'promos#show, constraints => lambda { Promo.exists?(promo_id) }
I know that the above code, without the constraints, would work as a catch-all for foo.com/*, and would sorta work if I put it as the last line in the routes file. Unfortunately, that would result in foo.com/badcode a 'promocode not found' error, rather than a normal 'route not found' error.
So, is there a way to accomplish what I'm trying to accomplish? This is in Rails 3, for reference.
Edit: To clarify a bit-
I want a wildcard url as described above so that our promocode urls are short and memorable (foo.com/save87 instead of foo.com/promo_codes/save87)
I'd prefer to have the option of having other routes after this one. I may, at some point, need another wildcard url at the root level- for example, if I want vanity urls for another resource in my system. For example, if I sell a dozen varieties of widgets, I might want foo.com/widget_deluxe, foo.com/widget_extreme, etc in addition to my promo code urls. I'd have to make sure that there's no collision between promo codes and widget varieties, but that's easily handled elsewhere.
In an ideal world, I'd do something like this in my routes.rb file:
No way. In Rails World, this functionality should go inside controller.
In controller you can do something like
def show
if Promo.exists?(promo_id)
#do something
else
raise ActionController::RoutingError.new('Not Found')
end
end
Update
With routes, you can do something like this
constraints(lambda { |req| Promo.exists?(req.params["promo_id"]) }) do
get '/:promo_id' => 'promos#show
end
Please keep in mind that this constraints will query the database for every request with a url matching the pattern /:promo_id (e.q. /users, /faq). To avoid unnecessary database queries that decrease your website performance, you should add this rule as far as possible to the end of your routes.rb.
Using this routing logic, every request to your application would do an extra search for a promo code before it moved on to the rest of the routes. I recommend looking at your business case and consider doing a Promo controller. If you must do routes, something like this would work but I would put it at the end so that it goes to your regular routes first.
get '*', to: 'promos#show'

Rails routes redirect for query string

I have a problem, recently the name of a controller changed.
I changed the routes file to accept calls using the old controller name, for people with bookmarks referencing the old name:
get '/old/about', to: redirect('/new/about')
get '/old/report/:client', to: redirect('/new/report/%{client}')
get '/old/:sub_path', to: redirect('/new/%{sub_path}')
that works fine. But for calls with query string it blocks it to /report/200. for example:
/old/report/200?c_id=257&end=2013-10-19&num_results=294540&start=2013-10-13
it cuts the url to:
old/report/200
and shows me an error because of the lack of parameters. Do you know what can I do? (I thought the :sub_path line in the routes would help but not) :(
Modify redirect to use the path: option to preserve the querystring:
- get '/old/about', to: redirect('/new/about')
+ get '/old/about', to: redirect(path: '/new/about')
This is demonstrated in the API docs for redirect, see http://api.rubyonrails.org/classes/ActionDispatch/Routing/Redirection.html#method-i-redirect
The question mention by Matt helped me to figure out my answer (thanks a lot!). It was a slightly different for my specific case. Im leaving the answer that worked for me for future reference.
match "/old/report/:client" => redirect{ |params, request| "/new/report/#{params[:client]}?#{request.query_string}" }
Building on Alejandra's answer, more verbose but without the ? if there's no query string:
get "/old/report/:client", to: redirect{ |params, request| ["/new/report/#{params[:client]}", request.query_string.presence].compact.join('?') }
So /old/report/:client?with=param will become /new/report/:client?with=param,
and /old/report/:client will become /new/report/:client.
The existing answers work perfectly, but are not quite suitable for keeping things DRY — there is a lot of duplicate code once you need to redirect more than one route.
In this case, a custom redirector is an elegant approach:
class QueryRedirector
def call(params, request)
uri = URI.parse(request.original_url)
if uri.query
"#{#destination}?#{uri.query}"
else
#destination
end
end
def initialize(destination)
#destination = destination
end
end
Now you can provide the redirect method with a new instance of this class:
get "/old/report/:client", to: redirect(QueryRedirector.new("/new/report/#{params[:client]}"))
I have a written an article with a more detailed explanation.

Dynamic root path route

How can I use dynamic root route if it depends on... weather, or current time, or whatever?
I thought about two ways: ApplicationController level and Rack redirect.
With first solution I will check my dynamic state and redirect to particular page.
Second solution is little more native as far as it uses routes level
For example
root :to => proc { |env| [ 302, {'Location'=> some_code }, [] ] }
But what I hope to see is how can I use simple lambda for route option like:
root :to => "mycontroller#myaction", :some_param => proc{ DateTime.now.hour }
It doesn't work but it shows my expectation
I'm not sure why you'd need to initialize a parameter in the routing table when the same thing could be done in the controller:
params[:some_param] = DateTime.now.hour
You can also do the redirection inside the controller as required instead of leaning so heavily on the routing table using the redirect_to method.
Has anyone figured this out? Even though this question is a decade old now I still don't see a clean way of doing this in Rails. I have a route constraint for the root path. My need is once they get to that action then a few conditions would be checked and based on that the request should be ultimately processed by another controller action. I could do redirect_to but then the URL shown in the browser also changes, which is not what I want. If they are hitting abc.com they should still be kept at abc.com and not redirected to abc.com/home or something else. Anyone figured this out?

How to redirect (301) when changed routing translation?

I am looking for an easy way to make redirects in my application.
SITUATION:
I have routes like this:
http://myapp.com/authors/5-hemingway/books/1-moby-dick
The routes are translated this way (using gem 'i18n_routing'):
http://myapp.com/acutores/5-hemingway/libros/1-moby-dick
Now, I changed translation of acutores to scriptores. Easy step but I'd like to redirect all routes that contained an old "acutores" resource name to routes with "scriptores" instead.
My guess is, I should play in routes.rb with:
match "/acutores" => redirect("/scriptores")
But how to do it efficiently for all cases where 'acutores' appear? (especially with nested routes)
This redirects /acutores/something to /scriptores/something but fails with plain /acutores:
match "/acutores/*path" => redirect("/scriptores/%{path}")
This seems to handle both:
match "/acutores(/*path)" => redirect {|params| "/scriptores/#{params[:path]}"}
http://guides.rubyonrails.org/routing.html#redirection
http://guides.rubyonrails.org/routing.html#route-globbing
--edit
This will get rid of all the trailing slashes:
match "/acutores(/*path)" => redirect{ |params| "/scriptores/#{params[:path]}".chomp("/") }
I had issues with browser caching redirects, so empty the cache after modifications.

Resources