Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Rails does not correctly support underscores in module namespaces #5849

Closed
bploetz opened this Issue · 12 comments

6 participants

Brian Ploetz Carlos Antonio da Silva Xavier Noria Jim Van Fleet Rafael Mendonça França Steve Klabnik
Brian Ploetz

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.

Carlos Antonio da Silva

This seems to be related to this commit: d38ca78, to add support for acronyms in inflector - issue #1366, pull request #1648.

@fxn thoughts on this one?

Xavier Noria
Owner

ACK, I'll have a look.

Brian Ploetz

In addition, underscores in module namespaces in helpers is problematic in all versions of Rails 3.0 -> 3.2:

# app/helpers/api/v0_1_0/test_helper.rb 
module Api::V0_1_0::TestHelper
end
bundle exec rake test:integration
/Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/activesupport-3.1.0/lib/active_support/inflector/methods.rb:124:in `block in constantize': uninitialized constant Api::V010 (NameError)
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/activesupport-3.1.0/lib/active_support/inflector/methods.rb:123:in `each'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/activesupport-3.1.0/lib/active_support/inflector/methods.rb:123:in `constantize'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/activesupport-3.1.0/lib/active_support/core_ext/string/inflections.rb:43:in `constantize'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/abstract_controller/helpers.rb:136:in `block in modules_for_helpers'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/abstract_controller/helpers.rb:131:in `map!'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/abstract_controller/helpers.rb:131:in `modules_for_helpers'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/action_controller/metal/helpers.rb:89:in `modules_for_helpers'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/abstract_controller/helpers.rb:95:in `helper'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/actionpack-3.1.0/lib/action_controller/railties/paths.rb:17:in `block (2 levels) in with'
    from /Users/bploetz/tmp/test-api-rails31/app/controllers/application_controller.rb:1:in `<top (required)>'
    from /Users/bploetz/tmp/test-api-rails31/app/controllers/api/v0_1_0/base_controller.rb:1:in `<top (required)>'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/engine.rb:416:in `block (2 levels) in eager_load!'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/engine.rb:415:in `each'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/engine.rb:415:in `block in eager_load!'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/engine.rb:413:in `each'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/engine.rb:413:in `eager_load!'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/application/finisher.rb:51:in `block in <module:Finisher>'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/initializable.rb:25:in `instance_exec'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/initializable.rb:25:in `run'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/initializable.rb:50:in `block in run_initializers'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/initializable.rb:49:in `each'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/initializable.rb:49:in `run_initializers'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/application.rb:92:in `initialize!'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/railties-3.1.0/lib/rails/railtie/configurable.rb:30:in `method_missing'
    from /Users/bploetz/tmp/test-api-rails31/config/environment.rb:5:in `<top (required)>'
    from /Users/bploetz/tmp/test-api-rails31/test/test_helper.rb:2:in `require'
    from /Users/bploetz/tmp/test-api-rails31/test/test_helper.rb:2:in `<top (required)>'
    from /Users/bploetz/tmp/test-api-rails31/test/integration/api/v0_1_0/test_controller_test.rb:1:in `require'
    from /Users/bploetz/tmp/test-api-rails31/test/integration/api/v0_1_0/test_controller_test.rb:1:in `<top (required)>'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:10:in `require'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:10:in `block (2 levels) in <main>'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:9:in `each'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:9:in `block in <main>'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:4:in `select'
    from /Users/bploetz/.rvm/gems/ruby-1.9.2-p0@testrails31/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb:4:in `<main>'
rake aborted!
Command failed with status (1): [/Users/bploetz/.rvm/rubies/ruby-1.9.2-p0/b...]

Tasks: TOP => test:integration
(See full trace by running task with --trace)
Brian Ploetz

I updated the title of this issue to reflect the problems with underscores in module namespaces across all versions of Rails. I'm working on a pull request to fix this. Stay tuned.....

Carlos Antonio da Silva

@bploetz a pull request would be awesome, thanks.

Brian Ploetz

Pull request sent

#6105

Carlos Antonio da Silva

@bploetz great, lets continue the discussion on your pull request, thanks.

Brian Ploetz

@carlosantoniodasilva How do I get some attention on that pull request?

Carlos Antonio da Silva

@bploetz It's on @fxn's hands / to-do list now ;), he'll take a look as soon as he can. I'll try to review it later as well. Thanks!

Brian Ploetz

Great, thanks guys!

Jim Van Fleet

@carlosantoniodasilva @fxn Thanks for taking a look so far, this would be nice to see in 3.2. It might make sense to reopen the bug until the pull request is actually merged.

Rafael Mendonça França rafaelfranca reopened this
Rafael Mendonça França
Owner

Yes, I agree with this. I'll reopen since the pull request is linked with this issue and we can close this one when merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.