Testing routes with host constraints via assert_routing in Rails - ruby-on-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.

Related

Rails implement an API using Routes pointing to existing controller actions

I have a Rails app that does everything I need it to do via a HTML interface, now I'd like to bolt on an API providing access to bits of the functionality.
How would I do this selective forwarding of some API controller actions to another controller's actions using the Routes.rb?
I have tried the following:
My regular controller routes work fine using:
match 'stuff' => 'stuff#index'
get 'stuff/index'
get 'stuff/some_get_action'
post 'stuff/some_post_action'
But then when I try for my API:
match 'api' => 'api#index'
match 'api/some_get_action' => 'stuff#some_get_action', :via => :get
match 'api/some_post_action' => 'stuff#some_post_action', :via => :post
or...
match 'api' => 'api#index'
get 'api/some_get_action', :to => 'stuff#some_get_action'
post 'api/some_post_action', :to => 'stuff#some_post_action'
I get an error. When I navigate to /api/index to server a HTML page that contains forms for testing the API URLs the url_for raises a 'Routing error' exception saying 'No route matches...'.
You may want to include ':as => ' and define your route names that you may be using as your link paths.
get 'api/some_get_action' => 'stuff#some_get_action', :as => 'some_get_action'
and the link path in your index file will have 'some_get_action_path'. Not sure that 'match' or 'get' automatically resolves to a path name, which by setting ':as' it definitely will.
I like your idea for setting up this page for testing. I'm always doing it in the console, which is surely more difficult than simply clicking a link. Your links probably need to infer that they are :json requests, not :http.

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

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

Calling Rails.application.routes.recognize_path within an rspec test does not match any route in Rails 3

A Rails 3 application I'm working on contains some logic that invokes the following code (which I cannot change):
Rails.application.routes.recognize_path("/customers", :method => :get)
The "/customers" is of course variable.
I'm writing an associated Rspec test, which invokes the code that contains said logic and the test has a complete Rails environment. When I raise the following:
Rails.application.routes.routes.inspect
it contains the proper routes (e.g. one of the routes it has is "GET /customers").
When I then run the test, the logic results in a:
No route matches "/customers"
Doing the following:
#routes = Rails.application.routes
assert_recognizes({:controller => "customers", :action => "index"}, "/customers")
results in the same error.
Within a helper test, the following:
# this succeeds and returns "/customers"
x = helper.customers_path
Rails.application.routes.recognize_path(x, :method => :get)
results in, once again, the same error (No route matches "/customers")
I'm 100% positively sure that Rails.application.routes contain the proper routes.
Does anybody have any idea what the cause of this is?
Thanks!
Finally came across the cause of this stupid self-induced bug: forgot to define a CustomersController that the routes map to.
After diving into Rails' source found out that routing actually constantizes the controller mapped to the routes, so an actual controller to map to is required in your specs. :)

Ruby on Rails wildcard routing such as /foo.htm /foo.php /foo.something

I'm trying to create a routing situation where by default, any URL's such as this:
/foo
/something
/foo.php
/somethingelse.xml
/something.something.else
etc.
will all route to one controller, assuming they don't route anywhere else.
i can get this to work with the following code in my routes:
map.myroute '/:file_or_folder', :controller => 'mycontroller'
this works fine as long as there are no dots in the URL:
/something
but this wont work:
/something.foo
any ideas?
Dots are not allowed by default. You can specify a regex for what file_or_folder can match, such as this:
map.myroute '/:file_or_folder', :controller => 'mycontroller', :file_or_folder => /.*/

Rails RESTful routs without #new, rspec trouble

I'm currently writing a Rails app, and hit a somewhat strange quirk. I have a controller PermissionsController, which is mainly for display purposes at the moment. So my routing is locked down:
map.resources :permissions, :only => [:index, :show]
Unfortunately, when writing the tests, one of the routing tests fails:
it "does not recognize #new" do
{ :get => "/permissions/new" }.should_not be_routable
end
with the error:
Expected 'GET /permissions/new' to fail, but it routed to
{"action"=>"show", "id"=>"new", "controller"=>"permissions"} instead
Obviously, the #show action's route is matching with /permissions/:id, which also gives the expected error Couldn't find Permission with ID=new if you actually browse to that URL.
This is not a serious error, as it is correctly raising an exception with the bad :id parameter, but it's kind of ugly. Is there any way to actually make Rails reject that route? Some trick in the routing options that I'm missing?
I suppose I should just leave that test out and ignore it, or maybe remove the whole RESTful idea altogether and go back to a simpler map.connect 'permissions/:id' style. I strongly suspect I'll be expanding this in the future, though, and kind of wanted to keep my controllers consistent with each other. Just having to add occasional :only or :except rules made routes.rb nice and clean...
Well it is mapping correctly as you point out. If you want to ensure that :id is always a number then you would not have this problem.
This can be done with a requirement on the route.
map.connect ':controller/:action/:id', :requirements => { :id => /\d?/ }
map.connect ':controller/:action/:id.:format', :requirements => { :id => /\d?/ }

Resources