What is the replacement for ActionController::Base.relative_url_root?

I am porting a 2.x rails app to rails3; we'll call it foo-app. Foo-app is one section of a larger rails app and lives at main_rails_app.com/foo-app. Previously we just set up the following in our foo-app production config to ensure that our foo-app routes worked properly:

ActionController::Base.relative_url_root = "/foo-app"

However, with rails3, I now get:

DEPRECATION WARNING: ActionController::Base.relative_url_root is ineffective. Please stop using it.

I have since changed the config entry to the following:

config.action_controller.relative_url_root = "/foo-app"

This mostly works in that all calls to external resources (javascript/css/images) will use /foo-app. However, none of my routes change appropriately, or put another way, foo-app root_path gives me '/' when I would expect '/foo-app'.

Two questions:

  1. What is the replacement for ActionController::Base.relative_url_root
  2. if it is config.action_controller.relative_url_root, then why are my routes not reflecting the relative_url_root value I set?

Solution 1:

You should be able to handle all that within the routes.rb file. Wrap all your current routes in scope; for instance.

scope "/context_root" do
   resources :controller
   resources :another_controller
   match 'welcome/', :to => "welcome#index"
   root :to => "welcome#index"
end

You can then verify your routing via the rake routes they should show your routes accordingly, including your context root(relative_url_root)

Solution 2:

If you deploy via Passenger, use the RackBaseURI directive: http://www.modrails.com/documentation/Users%20guide%20Apache.html#RackBaseURI

Otherwise, you can wrap the run statement in your config.ru with the following block:

map ActionController::Base.config.relative_url_root || "/" do
  run FooApp::Application
end

Then you only have to set the environment variable RAILS_RELATIVE_URL_ROOT to "/foo-app". This will even apply to routes set in gems or plugins.

Warning: do not mix these two solutions.

Solution 3:

I feel like I must be over-complicating this and/or missing something, but this issue has been frustrating me for a while now, and here are my notes.

Summary

There are two separate issues with two points each for dynamic and static routes:

  1. how to get routing to correctly match an incoming URL
    • for routes
    • for static files
  2. how to generate URLs that include the relative_root
    • via url helpers
    • for static assets

One way to solve all four points:

  • Configure Nginx to strip the relative_root portion
    • This solves route matching; just write routes expecting URLs at / like development
    • Also static files are served as in development
  • Set RAILS_RELATIVE_URL_ROOT environment variable
    • This solves generated static asset helpers
  • Use the ScriptName middleware below (modify it to use the value from the environment)
    • This solves generated url helpers, e.g. users_path

Wrapping the Rails application in Rack::URLMap in config.ru (Christoph's answer)

# config.ru
map '/relative_root' do
  run Myapp::Application
end
  • requires incoming URL contain the relative_url_root (Nginx can be configured to remove or retain this; see below)
  • Rack appends the relative_url_root to the Rack env SCRIPT_NAME rack/urlmap.rb:62
  • Rails adds the current request's SCRIPT_NAME to url_for options metal/url_for.rb:41
  • Rails' url_for prepends the script name when generating paths routing/url_for.rb:133

So that covers URLs generated by the url helpers, e.g. given UserController, users_path will be prefixed by the relative url root.

Set SCRIPT_NAME in middleware

# config.ru
class ScriptName
  def initialize(app, name)
    @app = app
    @name = name
  end

  def call(env)
    env['SCRIPT_NAME'] += @name
    @app.call(env)
  end
end

use ScriptName, '/relative_root'
run Rails.application
  • Has same effect as above, but
  • Requires that incoming URL NOT contain the relative_url_root

Setting RAILS_RELATIVE_URL_ROOT

  • value is saved in app.config.relative_url_root configuration.rb:41
  • which in turn affects asset paths asset_url_helper.rb:137
  • but that's it as far as I see
  • notably does not affect url helpers

Setting config.action_controller.relative_url_root

  • ?? May affect assets compilation?
  • Overrides RAILS_RELATIVE_URL_ROOT env var?

Explicitly defining all routes under /relative_root (rizzah's answer)

# config/routes.rb
Myapp::Application.routes.draw do
  scope '/relative_root' do
    ...
  end
end
  • Url helpers will generate correct urls
  • Incoming URL must contain the relative url root (sensitive to Nginx configuration, see below), else "no route matches" exceptions
  • URLs requesting static assets, e.g. /relative_root/images/logo.png will result in "no route matches" exceptions. This may not be an issue if nginx is serving static assets anyway.

Nginx config

Given a config like this:

upstream myapp {
  server localhost:3000;
}

server {
  ...
  location /relative_root {
    proxy_pass http://myapp/;
  }
}

Nginx will strip out the /relative_root, and the Rails app will not see it. If you need the Rails app so see it, one way is to change the proxy_pass line:

...
    proxy_pass http://myapp/relative_root/;
...