Search code examples
ruby-on-rails-3.1asset-pipelinepassengerassetssprockets

Broken precompiled assets in Rails 3.1 when deploying to a sub-URI


I'm in the process of updating a Rails 3 app to use Rails 3.1 and as part of that, am making use of the new asset pipeline. So far, I've got everything working apart from one rather annoying problem I can't solve.

The application and all its assets works fine in development, but in production it is deployed to a sub-URI using Passenger (http://the-host/sub-uri/). The problem with this is that the assets are pre-compiled during deployment and one of my CSS (well, it's a .css.scss file) files is making use of the image-url helper from the sass-rails gem. Since during the pre-compilation process, the paths are hard-coded into the precompiled CSS file, the sub-uri is not taken account of:

In my .css.scss file:

body { background-image: image-url("bg.png"); }

The result in the compiled application-<md5-hash-here>.css file:

body { background-image: url(/assets/bg.png); }

What it should be to make it work correctly:

body { background-image: url(/sub-uri/assets/bg.png); }

Is this scenario just asking too much? If so, I'll have to switch back to the old non-asset-pipelined way and just serve my images and CSS from public. However it seems like something which should have been thought about and solved...? Am I missing the solution?


Edit 1: I should note that using the erb solution instead yields the same result, as one would expect.


Edit 2: in response to Benoit Garret's comment

No, the problem isn't related to the config.assets.prefix. I tried setting that (to /sub-uri/assets rather than the default of /assets) but it turned out that was the wrong thing to do - it seems like this setting is already in relation to the root of the Rails app, not the server. Removing that (and thus returning to the default) has fixed all the weird issues that caused (and there were many, all the assets ended up in /sub-uri/sub-uri/assets - it was all very strange). The only problem is that the image-url helper and friends do not pick up the sub-URI when they are pre-compiled. Needless to say, this is logical since when it is pre-compiled, it couldn't possibly know that when it's running under Passenger, it'll be configured in this way. My question is how to inform it of this and thus end up with the correct paths in the precompiled result. If indeed it can be done.

My current workaround is to reference the iamge in the CSS like this: url(../images/bg.png) and place it in the non-pipelined public/images location. Hardly ideal since it doesn't benefit from the fingerprinting and everything which the pipeline provides.


Solution

  • Finally I've worked out a couple of workarounds/solutions.

    1) From https://github.com/rails/sass-rails/issues/17 it looks like this could get fixed in sass-rails. I've monkey-patched helpers.rb myself along the lines of the proposed patch in the link above. I simply set the required environment variable in the asset precompile line in deploy.rb.

    I do all my monkey patching in a single file config/initializers/gem_patches.rb. In this file I patched this method as:

    module Sass
      module Rails
        module Helpers
          protected
          def public_path(asset, kind)
            path = options[:custom][:resolver].public_path(asset, kind.pluralize)
            path = ENV['PRODUCTION_URI'] + path if ENV['PRODUCTION_URI']
            path
          end
        end
      end
    end
    

    2) Alternatively if you are ok to embed images in the CSS, changing the stylesheet to have a .erb extension, and replacing the image-url("bg.png") with url(<%= asset_data_uri "bg.png" %>) will work without any need to change sass-rails. asset-data-uri doesn't exist as a pure Sass function so you have to use the Rails helper asset_data_uri.