Skip to content

Rails does not correctly support underscores in module namespaces #5849

@bploetz

Description

@bploetz

In API development it's often useful to map API versions to module namespaces. If your version naming convention contains dots (i.e. v2.1.0) you typically translate dots to underscores for the module names (i.e. V2_1_0). In Rails 3.0.x and 3.1.x, you could work around a quirk in the Rails inflector and get Rails to honor underscores in module names by specifying two underscores in the string passed to the :module argument of scope in your config/routes.rb file. For example:

scope :module => "V2__1__0", :constraints => { :format => /(json|xml)/ } do
  match '/foos.(:format)' => 'foos#index', :via => :get
end

In Rails 3.0/3.1, this would correctly route to app/controllers/v2_1_0/foos_controller.rb, which contains:

class V2_1_0::FoosController < ApplicationController
  def index
    render :text => 'v2.1.0'
  end
end

Client:

curl http://localhost:3000/foos.json
v2.1.0

Server:

Started GET "/foos.json" for 127.0.0.1 at 2012-04-14 18:21:36 -0400
  Processing by V2_1_0::FoosController#index as JSON
Rendered text template (0.0ms)
Completed 200 OK in 24ms (Views: 24.1ms | ActiveRecord: 0.0ms)

As of Rails 3.2.x, this no longer works. When requesting /foos.json with the same exact setup as above now results in the following RoutingError:

ActionController::RoutingError (uninitialized constant V210):
  activesupport (3.2.3) lib/active_support/inflector/methods.rb:229:in `block in constantize'
  activesupport (3.2.3) lib/active_support/inflector/methods.rb:228:in `each'
  activesupport (3.2.3) lib/active_support/inflector/methods.rb:228:in `constantize'
  actionpack (3.2.3) lib/action_dispatch/routing/route_set.rb:69:in `controller_reference'
  actionpack (3.2.3) lib/action_dispatch/routing/route_set.rb:54:in `controller'
  actionpack (3.2.3) lib/action_dispatch/routing/route_set.rb:32:in `call'
  journey (1.0.3) lib/journey/router.rb:68:in `block in call'
  journey (1.0.3) lib/journey/router.rb:56:in `each'
  journey (1.0.3) lib/journey/router.rb:56:in `call'
  actionpack (3.2.3) lib/action_dispatch/routing/route_set.rb:600:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
  rack (1.4.1) lib/rack/etag.rb:23:in `call'
  rack (1.4.1) lib/rack/conditionalget.rb:25:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/head.rb:14:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/params_parser.rb:21:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/flash.rb:242:in `call'
  rack (1.4.1) lib/rack/session/abstract/id.rb:205:in `context'
  rack (1.4.1) lib/rack/session/abstract/id.rb:200:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/cookies.rb:338:in `call'
  activerecord (3.2.3) lib/active_record/query_cache.rb:64:in `call'
  activerecord (3.2.3) lib/active_record/connection_adapters/abstract/connection_pool.rb:467:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
  activesupport (3.2.3) lib/active_support/callbacks.rb:405:in `_run__4355974194936707360__call__1035143701529953630__callbacks'
  activesupport (3.2.3) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.3) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'
  activesupport (3.2.3) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.3) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/reloader.rb:65:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'
  railties (3.2.3) lib/rails/rack/logger.rb:26:in `call_app'
  railties (3.2.3) lib/rails/rack/logger.rb:16:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/request_id.rb:22:in `call'
  rack (1.4.1) lib/rack/methodoverride.rb:21:in `call'
  rack (1.4.1) lib/rack/runtime.rb:17:in `call'
  activesupport (3.2.3) lib/active_support/cache/strategy/local_cache.rb:72:in `call'
  rack (1.4.1) lib/rack/lock.rb:15:in `call'
  actionpack (3.2.3) lib/action_dispatch/middleware/static.rb:62:in `call'
  railties (3.2.3) lib/rails/engine.rb:479:in `call'
  railties (3.2.3) lib/rails/application.rb:220:in `call'
  rack (1.4.1) lib/rack/content_length.rb:14:in `call'
  railties (3.2.3) lib/rails/rack/log_tailer.rb:14:in `call'
  rack (1.4.1) lib/rack/handler/webrick.rb:59:in `service'
  /Users/bploetz/.rvm/rubies/ruby-1.9.2-p0/lib/ruby/1.9.1/webrick/httpserver.rb:111:in `service'
  /Users/bploetz/.rvm/rubies/ruby-1.9.2-p0/lib/ruby/1.9.1/webrick/httpserver.rb:70:in `run'
  /Users/bploetz/.rvm/rubies/ruby-1.9.2-p0/lib/ruby/1.9.1/webrick/server.rb:183:in `block in start_thread'

Note that the underscores are being dropped altogether at some point in the processing, as it's looking for the constant V210 instead of the correct V2_1_0.

I think this is due to a change made to ActiveSupport::Inflector.camelize. Observe:

Rails 3.0/3.1:

rails console
Loading development environment (Rails 3.0.7)
ruby-1.9.2-p0 :001 > test = "V2__1__0"
 => "V2__1__0"
ruby-1.9.2-p0 :002 > test.camelize
 => "V2_1_0"

Rails 3.2:

rails console
Loading development environment (Rails 3.2.3)
ruby-1.9.2-p0 :001 > test = "V2__1__0"
 => "V2__1__0"
ruby-1.9.2-p0 :002 > test.camelize
 => "V210"

Ideally you wouldn't need the double underscore hack at all, but at least it worked in Rails 3.0/3.1. If there's another undocumented way to get Rails to honor underscores in module names in Rails 3.2 let me know. If Rails doesn't support underscores in module names anymore, it should be documented somewhere, as underscores are obviously perfectly valid characters for module names in Ruby.

Thanks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions