Empty route globs in Rails 3 - ruby-on-rails

I'm upgrading from Rails 2 to 3.1, and having some problems with route globs. We had working globbed routes in Rails 2 that were like this:
map.connect '/foo/*bar', :controller => 'foo', :action => 'index'
A GET to '/foo/one/two' would give the following parameters at the controller side:
{
:controller => 'foo',
:action => 'index',
:bar => ['one, 'two']
}
Empty globs were fine, so we could also do GET '/foo' and get:
{
:controller => 'foo',
:action => 'index',
:bar => []
}
Upgrading to Rails 3.1, there are a few differences:
match '/foo/*bar', :to => 'foo#index'
The globbed route segment comes through as a string instead of an array, but I've dealt with that in my code. No problem there. A GET '/foo/one/two' gives:
{
:controller => 'foo',
:action => 'index',
:bar => 'one/two'
}
The problem comes when I have nothing in the glob segment. GET '/foo' is no longer routable, and if I try to generate the URL from parameters, it cannot generate. Basically, if params[:bar] is an empty string, it just completely fails to route.
This is awkward in my app, as it depends on the Rails 2.3 behaviour to produce sensible URLs. We also have instances where the glob is mid-path:
match '/foo/*bar/show', :to => 'foo#show'
This means that we had paths like:
/foo/one/two/show
/foo//show
Is there any way to get the routing engine to handle the empty strings as it used to in Rails 2.3? I've tried adding extra routes for the empty-glob version, which works in the simple trailing-glob case, but in the case where the glob is mid-path, it doesn't.

I get tests passing with these routes:
Your::Application.routes.draw do
match '/foo/', :to => 'foo#index', :bar => ''
match '/foo//show', :to => 'foo#show', :bar => ''
match '/foo/*bar/show', :to => 'foo#show'
match '/foo/*bar', :to => 'foo#index'
end
Sequence is important!
Ambiguity: Is /foo/show meant to "show foo-index", or do "index for bar=show"?
Test:
require_relative '../test_helper'
class FooRoutingTest < ActionController::TestCase
test 'root index' do
assert_recognizes({:controller => 'foo', :action => 'index', :bar => ''}, '/foo')
assert_recognizes({:controller => 'foo', :action => 'index', :bar => ''}, '/foo/')
end
test 'root show' do
assert_recognizes({:controller => 'foo', :action => 'show', :bar => ''}, '/foo/show')
assert_recognizes({:controller => 'foo', :action => 'show', :bar => ''}, '/foo//show')
end
test 'wildcard index' do
assert_recognizes({:controller => 'foo', :action => 'index', :bar => 'hello'}, '/foo/hello')
assert_recognizes({:controller => 'foo', :action => 'index', :bar => 'hello/more'},'/foo/hello/more')
end
test 'wildcard show' do
assert_recognizes({:controller => 'foo', :action => 'show', :bar => 'wow'}, '/foo/wow/show')
assert_recognizes({:controller => 'foo', :action => 'show', :bar => 'wow/more'}, '/foo/wow/more/show')
end
end

Related

how url_for can check constraints from my routes.rb

I try define some translate routes on my rails application. The route change with the subdomain define. So I want this result :
describe "url_for" do
context 'with en' do
it 'brand translate' do
url_for(
:controller => 'boats',
:action => 'index',
:subdomain => :en,
:brand => 'hello',
:only_path => true
).should == '/yacht-charter/brand-hello'
end
end
context 'with fr' do
it 'brand translate' do
url_for(
:controller => 'boats',
:action => 'index',
:subdomain => :fr,
:brand => 'hello',
:only_path => true
).should == '/location-bateau/marque-hello'
end
end
end
Like you can see the only change between both url_for params is the subdomain. I try :
constraints => :subdomain => :en do
match '/yacht-charter/brand-:brand' => 'boats#index', :as => 'en_brand_search'
end
constraints :subdomain => :fr do
match '/location-bateau/marque-:brand' => 'boats#index', :as => 'fr_brand_search'
end
But all the time it's the first route define it use. the second is never define.
How Can i do that. It's a rails bug or not ?
Not sure about it being a rails bug or not, but have a look at https://github.com/francesc/rails-translate-routes it works well and may help achieving this.

DRYest routing config for a non-REST controller and its actions

Fairly certain I'm doin' this wrong. How can I DRY it up?
controller 'foo' do
get 'foo/bar', :action => 'bar', :as => 'foo_bar'
post 'foo/bar', :action => 'bar', :as => 'foo_bar'
post 'foo/baz', :action => 'baz', :as => 'foo_baz'
end
rake routes shows the same routing with this:
get 'foo/bar'
post 'foo/bar'
post 'foo/baz'

How do I update routes from Rails 2 to Rails 3?

Here are some routes I have in Rails 2 and want to upgrade to Rails 3:
map.callback "/auth/:provider/callback", :controller => "authorizations", :action => "create" #omniauth
map.failure "/auth/failure", :controller => "authorizations", :action => "failure" #omniauth
map.signup 'signup', :controller => 'users', :action => 'new'
map.signin 'signin', :controller => 'user_sessions', :action => 'new'
map.signout 'signout', :controller => 'user_sessions', :action => 'destroy'
match "/auth/:provider/callback" => "authorizations#create", :as => :callback
match "/auth/failure" => "authorizations#failure", :as => :failure
match "signup" => "users#new", :as => :signup
match "signin" => "user_sessions#new", :as => :signin
match "signout" => "user_sessions#destroy", :as => :signout
That should get you going.
I would definitely checkout the screencast that apneadiving mentioned as well as Rails' take on routes.
Take a look at the rails_upgrade plugin at https://github.com/rails/rails_upgrade and its rake rails:upgrade:routes.
script/plugin install git://github.com/rails/rails_upgrade.git
rake rails:upgrade:routes
This will take your current routes file and rewrites it using the Rails 3 syntax. Copy the console output and look for any potential optimizations after you've read through the documentation in some of the other answers.
This should answer and make you learn:
http://railscasts.com/episodes/203-routing-in-rails-3
You may also find lots of great information at the Rails Routing from the Outside In.

How do you test route constraints using RSpec

Given a couple of cities in the DB:
City.first.attributes => {:id => 1, :name => 'nyc'}
City.last.attributes => {:id => 2, :name => 'boston'}
And a route like:
match '/:city/*dest' => 'cities#do_something', :constraints => {:city => /#{City.all.map{|c| c.name}.join('|'}/}
(so the constraints should evaluate to: /nyc|boston/)
And a spec:
it "recognizes and generates a route for city specific paths" do
{ :put => '/bad-city/some/path' }.should route_to({:controller => "cities", :action => "do_something", :dest => 'some/path', :city => 'bad-city'})
end
I would expect a failure. But it passes.
Likewise:
it "doesn't route bad city names" do
{ :put => '/some-bad-city/some/path' }.should_not be_routable
end
Here I expect it to pass, but it fails.
It seems the constraint is being ignored in the specs, since the matching cities have the same behavior as the bad ones.
Is this a known issue, or am I missing something that I need to do?
This approach works:
In routes.rb
match '/:city/*destination' => 'cities#myaction', :constraints => {:city => /#{City.all.map{|c|c.slug}.join('|')}/}
In the spec:
describe "routing" do
before(:each) do
#mock_city = mock_model(City, :id => 42, :slug => 'some-city')
City.stub!(:find_by_slug => #mock_city, :all => [#mock_city])
MyApp::Application.reload_routes!
end
it "recognizes and generates a route for city specific paths" do
{ :get => '/some-city/some/path' }.should route_to({:controller => "cities", :action => "myaction", :destination => 'some/path', :city => 'some-city'})
end
it "rejects city paths for cities that don't exist in the DB" do
{ :get => '/some-bad-city/some/path' }.should_not be_routable
end
end
Lastly, I added an observer to reload routes whenever the cities table changes.
When you specify constraints, you must include the parameter to constrain:
match '/:city/*dest' => 'cities#do_something', :constraints => { :city => /nyc|boston|philly/ }

Rails routing error with namespaced items

We've got a rails app that keeps producing an error that we can't track down. The problem seems to lie with the routing but we're not sure what is happening. Out routes file looks like:
ActionController::Routing::Routes.draw do |map|
# Default
map.home '', :controller => "home"
# Admin
map.admin_home 'admin', :controller => 'admin/admin', :action => 'index'
map.admin_login 'admin/login', :controller => 'admin/admin', :action => 'login'
map.admin_reminder 'admin/forgot', :controller => 'admin/admin', :action => 'reminder'
map.namespace :admin do |admin|
admin.resources :bookings,
:collection => {
:archive => :get, :reports => :get, :calendar => :get,
:step_1 => :any, :step_1a => :any, :step_2 => :any, :step_3 => :any, :confirmation => :any }
admin.resources :events,
:member => { :status => :get }
admin.resources :blogs,
:collection => { :archive => :get }
admin.resources :blog_replies,
:member => { :publish => :get }
admin.resources :minutes,
:collection => { :archive => :get }
admin.resources :businesses
admin.resources :business_categories
admin.resources :users
admin.resources :pricings
admin.backups 'backups', :controller => 'admin/backups'
admin.download_backup 'backups/download', :controller => 'admin/backups', :action => 'download'
end
map.admin 'admin/:action', :controller => 'admin/admin'
map.connect 'members', :controller => 'admin/admin', :action => 'redirect_to_index'
map.connect 'members/login', :controller => 'admin/admin', :action => 'redirect_to_index'
map.connect 'account', :controller => 'admin/admin', :action => 'redirect_to_index'
map.connect 'account/login', :controller => 'admin/admin', :action => 'redirect_to_index'
map.connect 'home', :controller => 'admin/admin', :action => 'redirect_to_index'
map.connect 'home/login', :controller => 'admin/admin', :action => 'redirect_to_index'
map.blog 'blog/:permalink', :controller => 'blogs', :action => 'show'
map.connect 'blog/:id', :controller => 'blogs', :action => 'show'
map.connect 'book-online', :controller => 'bookings', :action => 'step_1'
map.connect 'book-online/:action', :controller => 'bookings'
# Defaults
map.connect ':controller/:action/:id'
map.connect "*anything", :controller => "public", :action => "unknown_request"
end
We have a set of public controllers in app/controllers and a set of admin controllers in app/controllers/admin. The issue we keep seeing is when a user goes to a url like admin/bookings, admin/bookings/step_1 or admin/events. Sometimes the urls work perfectly but other times I can see from the log file that something like the following happens:
ActionController::UnknownAction (No action responded to index):
Other times we'll get something like:
Processing EventsController#index (for [filtered] at 2009-01-21 10:54:38) [GET]
Session ID: [filtered]
Parameters: {"action"=>"index", "controller"=>"admin/events"}
Rendering template within layouts/public
Rendering events/index
Completed in 0.00863 (115 reqs/sec) | Rendering: 0.00338 (39%) | DB: 0.00000 (0%) | 200 OK [http://www.gresfordtrust.org/admin/events]
from the last example you can see that the url requested was admin/events which should have hit the #index in Admin::EventsController but instead it renders the #index action in EventsController.
We're running the app with Rails 2.0.2.
You don't have a configured route for EventsController, so your error is happening because some request is falling down to the default route, map.connect ':controller/:action/:id'
.
This is happening because someone/something is sending a request of an HTTP method that isn't configured for your AdminEventsController. Your admin.resources :events, :member => { :status => :get } will match the following requests:
GET /admin/events
GET /admin/events/<id>
GET /admin/events/<id>/status
POST /admin/events
PUT /admin/events/<id>
DELETE /admin/events/<id>
Anything else would fall through to the default route. So if you're seeing those ActionController::UnknownAction on this controller look for requests that are using the wrong HTTP method.
The source of your bizarre log message is almost certainly a request that was something like this:
GET /admin/events/index
The solution is to get rid of that default route entirely and ensure you're adding resource[s] routes for all the controllers in the right place.

Resources