Currently I am using a custom constraint that is linked to a single route like:
get '/hello', to: 'account#index',
constraints: AccountConstraint.new
Basically my custom constraint is looking up the request.host, and if it is found in the database the matches? method will return true and then the account#index action will get called.
What I want to do is, if the constraint matches, then based on the path it will go do a different action.
So my constraint would be like:
class AccountConstraint
def matches?(request)
# lookup the database, return true if record found
end
end
Then I want my route.rb file to do something like this (pseudo code below_:
if AccountConstraint matches
get '/', to: "account#index"
get '/hello', to: "account#hello"
end
Is something like this possible? If so, how?
I'm not sure if I'm understanding the question, but it sounds like what you want is a scope:
scope constraints: AccountConstraint.new do
get '/', to: "account#index"
get '/hello', to: "account#hello"
end
The routes inside the scope will only be accessible if AccountConstraint matches.
Related
I tried to add constraints to a group of scoped routes like so:
constraints locale: 'de' do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
# more routes
end
end
It doesn't make use of the restriction.
Whereas putting the restriction to a single route works as expected.
get '', to: 'magazine#index', as: 'magazine', constraints: { locale: 'de' }
I tried to use the constraints block in different positions, inside and outside the scope block. Without any change in the result.
The Rails Guide for Routing has this example which I pretty much copied:
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
Any ideas what's wrong with the code?
Without having the whole routes.rb file it is hard to say why it doesn't work as expected.
Is it possible you have some kind of scope defined for locale??
Imagine sth like
scope '/:locale', locale: /de|en/ do
# lots of routes so you are not aware of the scope
constraints locale: "de" do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
end
end
end
With this your are actually setting a constraint to locale to be either de or en. The constraint from the scope has precedence over the constraints block.
While this is not clear from the rails guide I found a merge request that proves my argumentation.
I hoped route constraints would allow me to have admin.example.com/widgets/ and example.com/admin/widgets/ be effectively the same, with the URL helper admin_widgets_path pointing to the correct one based on the current subdomain. However, the route helper seems to only point to one or the other regardless of constraints.
Am I doing something wrong? Is this a bug? Is there a better way to solve this problem?
A rails app with an example of the problem has been published here, but the relevant details are below.
My config/routes.rb
class Subdomains
# any domain that starts with 'admin.'
def self.admin?
-> (request) { request.host =~ /^admin\./ }
end
# any other domain
def self.primary?
-> (request) { !admin?.(request) }
end
end
Rails.application.routes.draw do
constraints(Subdomains.primary?) do
namespace :admin do
resources :widgets
end
end
constraints(Subdomains.admin?) do
scope module: "admin", as: :admin do
resources :widgets
end
end
root to: "admin/widgets#index"
end
My app/views/admin/widgets/index.html.erb
<%= link_to "Widgets", admin_widgets_url %>
In this configuration, admin_widgets_url always returns /admin/widgets/ which isn't a valid route on admin.example.com so clicking the link results in a routing error on that subdomain. If the admin subdomain constraint block is put first in the routes, then the URL helper always returns /widgets/, breaking the link on a non-admin domain.
The routes otherwise work, but not sure how to get the URL helpers to point to the correct one.
Your expectations are wrong - Rails reads the entire routes definition as part of the setup phase. This is also when the route helpers are created. The constraint is not actually evaluated until later when the request is matched.
Rails splits this into distinct phases to allow forking.
Instead you may need to override the route helpers.
I have the following wildcard routes & constraints setup ...
get '*path' => 'profiles#show', constraints: SlugConstraint.new
get '*path' => 'blogs#show', constraints: SlugConstraint.new
and
class SlugConstraint
def initialize
#slugs = Slug.all.map(&:name)
end
def matches?(request)
request.url =~ /\/(.+)/
#slugs.include?($1)
end
end
... a variation based on the issue I described here:
Rails wildcard route with database lookup & multiple controllers
My issue now is that if the first call to SlugConstraint.new returns false (so that the 2nd routes.rb SlugConstraint.new now gets called) I don't want to have to redo the call to:
Slug.all.map(&:name)
How do I properly save (or scope) the #slugs data from the first constraint call that failed, so that I can access it if needed in the next constraint call?
Thanks.
Routing
You're not going to be able to use 2 routing patterns for the same path
When you send a request to Rails (or any other MVC application), Rails will take the path you've sent & consequently try to assign the right route (controller#action) for it.
This happens sequentially - IE Rails will look through your routes from top -> bottom until it finds one which corresponds. As you have two routes to match the same path, you're not going to be able to use the set up you have
--
App-Wide Slugs
What you're looking for is something called app-wide slugs - which essentially means you're able to manage a single slug path, and have a system in the back-end to accommodate it.
You're on the brink of being able to achieve this, and whilst I don't have any code to help, I do have an idea, which I found here:
#config/routes.rb
get '*path' => MyRouter.new, constraints: SlugConstraint.new
#lib/my_router.rb
class MyRouter
def call(env)
# Matched from routes, you can access all matched parameters
view_name= env['action_dispatch.request.path_parameters'][:view_name]
# Compute these the way you like, possibly using view_name
controller= 'post'
my_action= 'show'
controller_class= (controller + '_controller').camelize.constantize
controller_class.action(my_action.to_sym).call(env)
end
end
This will allow you to pick up the slugged paths, whilst routing to the correct controller. This is TOTALLY untested & just a stab in the dark - if you want to go over it with me, comment & we can have a look
I have a custom route based on http://guides.rubyonrails.org/routing.html#advanced-constraints that checks for redirects based on a url entered.
For instance /site may redirect to /mysite
Here is the code:
class RedirectCheck
def initialize
#redirects = Redirect.all_from_paths
end
def matches?(request)
#redirects.include?(request[:path])
end
end
MyApp::Application.routes.draw do
get ":path" => 'redirects#show', constraints: RedirectCheck.new
end
Redirect.all_from_paths basically is a model method that returns an array of all the accepted routes and then 'redirects#show' does the actual redirect.
Now my problem is that the route..
get ":path" => 'redirects#show', constraints: RedirectCheck.new
..will not accept paths with slashes in them
So for example I can't add a path like /go/some/where my redirect route will not recognize it
How do I change this line so it will accept any path with any number of slashes and passes it as params[:path] to 'redirects#show'?
What you want is:
get "*path", to: 'redirects#show', constraints: RedirectCheck.new
And there are many other examples at the Rails routing guide.
I have to modify the routes file in order to have SEO improvement.
This is my context, a rails backend generate a JSON feed with the route's name in, I have to read it and change the default name.
For example, I have this:
get '/people' => 'people#show', as: :people
and I'd like to change /people in some value read from my JSON feed.
I created a class to get the JSON object in my app
class JSONDatabase
def initialize(kind_of_site)
#kind_of_site = kind_of_site
end
def fetch_database_remote(url)
JSON.parse(open(url).read)
end
end
but how can i access it in routes file?
Thank you
You don't necessarily need to modify your application's routes. What you can do is define a wild card route that leads to a unique controller where you read the updated route. This approach is kind of hackish but gives you the unlimited routes you need without modifying the routes.
Your config/routes.rb file would look something like this:
resources :defined_models
root to: 'controller#action'
# At last we define the wildcard route
get '/:route' => 'routing_controller#routing_action'
Then, at this routing action we can do the job of seeing if this route (now defined in the params[:route] variable) corresponds to the modified one. Just remember to redirect to a 404 if the route given is not defined, since with this approach you loose the Rails default way of dealing with undefined routes.