Deploying Rails 3.0.7 to a Sub Directory
By Frank Showalter
Jun 06, 2011
The Problem
You want to deploy your Rails 3.0.7 app (let's call it "myapp") to a subdirectory (let's call it "apps"), rather than the root of your website (let's call it "www.example.com"). So, you want to access your app from this URL:
http://www.example.com/apps/myapp
404. Not Found.
Undaunted (hey, no deployment works the first time) you check your web server logs and find that Passenger is starting just fine. Hmnn... you move on to your Rails production log and find something like this:
No route matches "/apps/myapp/widgets/index"
(Where /widgets/index is the resource mapped to your root route.)
This tells us that Rails doesn't realize "/apps/myapp" is the root it should use for the relative URLs it generates. This is an easy enough fix, just wrap our routes in a scope, something like:
scope "/apps/myapp" do
resources :widgets
root :to => "widgets#index"
end
Restart Passenger, and you can see your site!
(Alternatively, we could wrap the whole rack application in config.ru, but I prefer to do it in the routes.)
But Wait!
Something's wrong. You can see your site, but all the content's unstyled and the image links are broken. You check the outputted markup and discover, sure enough, all the asset links are still referencing root as their relative URL. For instance, your stylesheet is pointing to:
/stylesheets/application.css
Instead of:
/apps/myapp/stylesheets/application.css
Ditto any javascript or image links.
What?
Here's where things get rough. A bit of Googling will point you toward setting:
config.action_controller.relative_url_root = "/apps/myapp"
And indeed that did work in older versions of Rails, only when you try and set it in 3.0.7, it throws an error:
/actionpack-3.0.7/lib/action_controller/deprecated/base.rb:11:in
`relative_url_root=': wrong number of arguments (1 for 0) (ArgumentError)
This is because the method was deprecated, but incorrectly, as the signature looks like this:
def relative_url_root=
ActiveSupport::Deprecation.warn "ActionController::Base.relative_url_root= is ineffective. " <<
"Please stop using it.", caller
end
This bug has been present since Rails 3.0's initial release, and it's true, setting it is ineffective, unfortunately, the variable is still being used. Take a look in:
/actionpack/lib/action_view/helpers/asset_tag_helper.rb
And you'll see that all the javascript_include_tag and stylesheet_link_tag helpers call down to this method:
def compute_public_path(source, dir, ext = nil, include_host = true)
return source if is_uri?(source)
source += ".#{ext}" if rewrite_extension?(source, dir, ext)
source = "/#{dir}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source, config.asset_path)
has_request = controller.respond_to?(:request)
if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/}
source = "#{controller.config.relative_url_root}#{source}"
end
source = rewrite_host_and_protocol(source, has_request) if include_host
source
end
Which, of course, uses controller.config.relative_url_root to set the root URL, which will always be blank. Indeed, the variable does seem to be getting set in:
/actionpack/lib/action_controller/metal/compatibility.rb
# ROUTES TODO: This should be handled by a middleware and route generation
# should be able to handle SCRIPT_NAME
self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
The Solution
So, you'd think setting ENV['RAILS_RELATIVE_URL_ROOT'] in your production.rb config would work, but, like the deprecated comment says, setting config.relative_url_root is ineffective. My solution was to monkey patch compute_public_path to read directly from ENV['RAILS_RELATIVE_URL_ROOT'].
While this certainly isn't ideal, it'll do, as this looks fixed in the 3.1 branch (but not 3.0.8).
So how'd this bug make it through Rails regression testing? It looks like they're using a mock controller with a config method in the tests, and use that method to set config.action_controller.relative_url_root on the mock, which isn't deprecated, and thus works.