Rails static page routes vs :constraints => { :url => /.+/ } -- pros and cons - ruby-on-rails

I'm creating Page admin setup. Pages may include sub pages and I have a bunch of functionality to create the trailing url strings. Anyways, just wondering what the pros and cons here is. Speed-wise or anything I didn't really consider (aside maybe a bit more/less flexibility with other route matching):
Option 1 -- Match everything to pages:
get ':url' => 'pages#show', :constraints => { :url => /.+/ }
# with #page = Page.find_by_url("/"+params[:url]) in my controller
Option 2 -- Statically map routes to pages, and reload routes after each save
if Page.table_exists? # Otherwise on rake db:migrate this file will be called and throw an error
Page.all.each do |page|
match page.url, :controller => 'pages', :action => 'show', :page_id => page.id
end
end
# Then after pages save it calls MyApp::Application.reload_routes!
Either way may work just as well.. just curious.

Option-1 is superior and will actually work properly regardless of your deployment environment, Option-2 will fail in any non-trivial deployment.
Suppose you have two web server processes, P1 and P2; they may be on the same machine, separate machines, or perhaps in separate VMs on the same machine. Suppose some saves a new page that just happens to go to P1; then P1 will update the database (a shared resource between P1 and P2) and rebuild its routing table. But now P1 has the correct routing information but P2 is stuck with the old one because no one told it that something had changed.
You could set up some polling or broadcast system but that would just be a bunch of pointless complexity when Option-1 will work just fine as-is.

You can use routes constraints:
get ':url' => 'pages#show', :constraints => lambda {|request| Page.all.map(&:url).include?(request.path[1..-1]) }
Can yet to use a "catch all" to "page not found"
get '*not_found' => 'page#not_found'
http://guides.rubyonrails.org/routing.html#advanced-constraints

Related

Consolidating messy routes in Rails 4. How to organize routes to not point urls to static links?

Here comes a rails nub organizational question:
What exists:
A "Railsy" link would look something like this (in slim)
span
a href= signout_path
signout_path correspond to AuthController#signout.
So, in my applications there's several types of users - Firefighters, Chiefs and Captains.
Each one has very similar routes:
/dashboard
/dashboard/:date
( this is in case they want to see a previous date;
same dashboard, different data in the controller )
/account
/account/update
/entry/view
/entry/edit
Entries may or may not belong to a user. Because entries should be visible to any user (though not relevant actions)
Take a look at my disgusting mess of routes:
scope :firefighters do
get '/dashboard' => 'firefighters#dashboard'
get '/dashboard/:date' => 'firefighters#dashboard'
get '/account' => 'account#account'
post '/account/update' => 'firefighters#account_update'
get '/account/change_password' => 'account#change_password'
get '/account/change_cell' => 'account#change_cell'
get '/account/change_email' => 'account#change_email'
get '/account/change_notifications' => 'account#change_notifications'
post '/account/update_cell' => 'account#update_cell'
# post '/account/update_pass' => 'account#update_pass'
# post '/account/update_cell' => 'account#update_cell'
# post '/account/update_email' => 'account#update_email'
# post '/account/update_notifications' => 'account#update_notifications'
# get '/account/change_cell_number' => 'firefighters#account_change_cell_number'
# post '/account/request_cell_change' => 'firefighters#account_request_cell_change'
# post '/account/update_cell' => 'firefighters#account_update_cell'
# get '/account/change_password' => 'firefighters#account_change_pass'
# post '/account/update_password' => 'firefighters#account_update_pass'
# get '/account/change_email' => 'firefighters#account_change_email'
# post '/account/request_email_change' => 'firefighters#account_request_email_change'
# post '/account/update_email' => 'firefighters#account_update_email'
get '/approve' => 'firefighters#approve'
get '/set_tester' => 'firefighters#set_tester'
end
scope :entries do
post '/verify' => 'entries#verify'
post '/approve' => 'entries#approve'
post '/finalize' => 'entries#finalize'
post '/comment' => 'entries#comment'
post '/update' => 'entries#update'
get '/edit/:entry_id' => 'entries#edit'
end
I used scope to avoid writing a million routes. I moved Entry routes under resources :entries for this reason.
Currently, every user type has their own dashboard page with slight variations, just to make it work
Right now there is a separate dashboard.slim, account.slim, etc for Firefighters, Chiefs, you get it.
How I'd like things to be
I'd like to have one dashboard to rule them all. However links like firefighters_dashboard_path isn't terribly useful if you only have one view file. I work around this problem by having a slightly different dashboard page for each user. This seems wrong and not at all DRY.
How do I organize routes so that there is one dashboard and depending on the user, links like account_path would lead one to firefighter's account and not an admin's account, for instance?
In other words, I login as captain, the link to my account should be site.com/account. I login as manager, my link to management should be 'site.com/managing'. Saving "identity" in session is craziness.
This is a bit to take in, apologies for the wall of text but help would be much appreciated.

Rails routing for an online shop, eg: /somecategory/someitem, causes issues with gets for /javascripts/all.js

I'm having issues with rails routing for an online shop. I want routes eg:
/cars/camaro
/bikes/nightrod
To achieve this, i've got this in routes.rb:
match '/:cat/:item', :to => 'browse#item'
It works fine, as in i can browse all good. But it causes issues with http GET's for (mostly i've noticed) '/javascripts/all.js' - these appear to be being routed to my browse#item action wrongly, which crashes because it cannot find that category or product.
Can someone suggest how i can solve this? Through better routing? I'd rather not give up my cool url's, but a last resort is, i guess, to route to /browse/category/product...
FWIW i'm hosting in heroku.
Obviously you've now created a default path for everything, which isn't a great solution. I haven't actually tried this, but I think :constraints might be what you're looking for... i.e.
match '/:cat/:item', :to => 'browse#item', :constraints => { :cat => /(cars|bikes|trucks|vans)/ }
but any time a cat changes, you'll have to add that there...
Alternatively if your categories are stored in the DB, you can try:
match '/:cat/:item', :to => 'browse#item', :constraints => { :cat => /#{Category.all.map{|c|c.name}.join('|')}/ }

Handling ambiguous routes in Rails

Here's my dilemma: I have two types of routes which are semantically very different, and should go to different controllers.
ny/new-york/brooklyn/cleaners # should go to a list of cleaners for a neighborhood
ny/new-york/cleaners/mrclean # should go to an individual cleaner's page
Note that "brooklyn" and "cleaners" here are just examples. The app has many service types (e.g. "cleaner") and many neighborhoods, so it's impossible to hard-code a list of either into a regular expression and use that to distinguish the two routes.
Is it possible to involve an arbitrary method, which accesses ActiveRecord models, in the routing decision? I'm using Rails 2.3.8.
Edit : new answer with dynamic services
Looking at this blog entry it seems possible to use ActiveRecords in the routes.
Maybe you could do something like this :
service_names = Service.all.map(&:short_name) # assuming the property 'short_name' is the value used in urls
service_names.each do |service_name|
map.connect ':state/:city/#{service_name}/:company' :controller => ‘company’, :action => ‘show’ # to show the company's page
map.connect ':state/:city/:neighborhood/#{service_name}_finder' :controller => ‘company_finder’, :action => ‘find’ # to list the companies for the given service in a neighborhood
end
That should still prevent conflicts since the routes for a certain service is before a route for a neighborhood
Old bad answer
Can't you use the two following routes ?
map.connect ':state/:city/cleaners/:cleaner' :controller => ‘cleaners’, :action => ‘show’ # to show the cleaner's page
map.connect ':state/:city/:neighborhood/cleaners' :controller => ‘cleaner_finder’, :action => ‘find’ # to list the cleaners of a neighborhood
In your controller, you should be able to retrieve :state, :city and others value using params[:state], params[:city], etc.
Putting the :state/:city/cleaners/:cleaner on the first line should prevent ambiguity.

Rails Routing Conditional with multiple value options

I have a rails route that is based on two conditions, that the domain and subdomain are a specific value. The problem is that there are multiple possible values for subdomain to work, but I can't seem to be able to pass them as an array or hash.
map.with_options(:conditions => {:domain => AppConfig['base_domain'], :subdomain => 'www'..'www3'}) do |signup|
signup.plans '/signup', :controller => 'accounts', :action => 'plans'
...[truncated]...
end
The above example works as accepting www, www1, www2 & www3 as a value for the subdomain. However, that doesn't really solve my needs. I need to be able to accept a value of '' (nothing), 'www' and 'www2' so I tried something to the extend of:
map.with_options(:conditions => {:domain => AppConfig['base_domain'], :subdomain => ['','www','www2']}) do |signup|
That's similar to how you would set it up in ActiveRecord but it doesn't seem to be the same for routes.
Does anybody know now I can specify three values that aren't sequential?
If you can render it as a regular expression, you can use it as a condition. Converting an array to a regular expression is quite easy:
:subdomain => Regexp.new(%w[ www www3 ].collect { |p| Regexp.escape(p) }.join('|'))
Since you're just dealing with a simple pattern anyway, why not express it as this?
:subdomain => /www\d*/
It is important to note that the regular expressions used by routes are not supposed to be anchored using ^ or $ like you usually would. They must match completely to be valid, and partial matches are ignored.

Testing routes with host constraints via assert_routing in Rails

I have a route which I'm using constraints to check the host and then a route which is essentially the same but without the host restriction (these are really namespaces but to make things simple this example will do):
match "/(:page_key)" => "namespace_one/pages#show", :constraints => proc {|env| env['SERVER_NAME'] == 'test.mysite.local' }
match "/(:page_key)" => "namespace_two/pages#show"
These work exactly as expected when accessing via the browser and in integration tests when defining the host and doing get "/page_key" etc.
However I want to write tests that ensures that these routes work so far I'm not having much luck as the following test (which is currently in an ActionController::IntegrationTest so I can set the host) is matching the one without the constraint:
assert_routing '', { :controller => 'namespace_one/pages', :action => 'show' }
=> The recognized options <{"action"=>"show", "controller"=>"frontend/pages"}>
did not match <{"action"=>"show", "controller"=>"namespace_two/pages"}>,
difference: <{"controller"=>"namespace_one/pages"}>
If I try dumping the env in the constraints proc all I get is --- :controller.
If I get rid of the assert_routing and just do a get :show call and dump the #controller it does resolve to the correct controller (as expected as these routes all work fine via HTTP requests).
Just had this problem myself. This was fixed by a Rails patch that allows you to specify full urls in routing tests.
Change your test to
assert_routing 'http://test.mysite.local', { :controller => 'namespace_one/pages',
:action => 'show' }
and it will work fine.
You have to include "://" in the full url b/c rails uses a regex to look for %r{://} in the path or it will automatically hack off the host portion of the url, and the test will error out.

Resources