Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Asset precompilation does not work when deploying under sub-path #3259

Closed
markembling opened this Issue Oct 8, 2011 · 25 comments

Comments

Projects
None yet
10 participants

Overview

When deploying a Rails application (3.1.1) to a sub-path under Passenger, there is no way to provide this sub-path when precompiling assets. The result of this is that SCSS files have incorrect paths in them to images etc when the image-url and other helpers are used.

Brief Steps to Reproduce

Production environment is set up as follows for Apache/Passenger:

# Rails applications

RailsBaseURI /sample
<Directory /var/www/sample>
    Options -MultiViews
</Directory>

RailsBaseURI /another-app
<Directory /var/www/another-app>
    Options -MultiViews
</Directory>

Unfortunately the apps this affects (for me) are not public (neither the source or the running app), so I can't link directly to it. However it will be a problem common to all Rails apps deployed to a sub-path under Passenger using capistrano. The following steps (assuming a new app) should (hopefully) reproduce the problem.

Add a background image to app/assets/images and add the following to application.css.scss:

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

Make sure the application is to be deployed using capistrano:

Gemfile:

# Deploy with Capistrano
gem 'capistrano'

Capfile:

load 'deploy' if respond_to?(:namespace) # cap2 differentiator
Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }

load 'config/deploy' # remove this line to skip loading any of the default tasks
load 'deploy/assets' 

config/deploy.rb

require "bundler/capistrano"

set :application, "SampleApp"
set :repository,  "git@git.example.com:SampleApp"
set :scm, :git
set :branch, "master"

set :deploy_to,   "/var/rails_apps/example"
set :user, "mark"
set :deploy_via, :copy

role :web, "server.example.com"              # Your HTTP server, Apache/etc
role :app, "server.example.com"              # This may be the same as your `Web` server
# Commented out because its irrelevant to the sample.
#role :db,  "your primary db-server here", :primary => true # This is where Rails migrations will run
#role :db,  "your slave db-server here"

default_run_options[:pty] = true

# If you are using Passenger mod_rails uncomment this:
# if you're still using the script/reapear helper you will need
# these http://github.com/rails/irs_process_scripts

_cset :asset_env, "RAILS_GROUPS=assets"
_cset :assets_prefix, "assets"

namespace :deploy do
  task :start do ; end
  task :stop do ; end
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
  end
end

End Result

When the application is deployed, the assets will be precompiled on the server and the resulting stylesheet when sent to the browser will be missing the sub-path.

Desired: background-image:url(/sample/assets/bg-<whatever-hash>.png)
Actual: background-image:url(/assets/bg-<whatever-hash>.png)

This is expected, because the precompilation process is not happening from within the context of the running application under Passenger, thus the sub-path is not known. The problem is that there seems to be no way to get this information into it.

Contributor

atambo commented Oct 9, 2011

The best solution to this problem would be for all assets referenced from stylesheets to be precompiled with relative paths from the stylesheet to the asset. That way your precompiled assets would work in any deployment scenario.

Contributor

kennyj commented Oct 9, 2011

Easiest fix is the next commit.

kennyj/rails@f41b07a

It will be work fine if you merge this commit, and specify RAILS_RELATIVE_URL_ROOT
(meybe _cset :asset_env, "RAILS_GROUPS=assets RAILS_RELATIVE_URL_ROOT=/foo")

That is too realistic?

Owner

guilleiguaran commented Oct 9, 2011

@markembling this is working with live compilation? (without precompile)

Owner

guilleiguaran commented Oct 10, 2011

I want try to get the behavior that we get under 3.1.1.rc1 where this was working fine, I will research the problem in detail today.

Contributor

kennyj commented Oct 10, 2011

@guilleiguaran

My "precompile" test report.
(I think that precompile is default behavior in capistrano via load 'deploy/assets')

For your information.

■Environment

rails 3.1.1

4 files exists in app/assets/stylesheets.

application.css
/*
*= require_self
*= require_tree .
*/

a.css.scss
a { background-image: image-url("rails.png"); }

b.css.erb
b { background-image: url(<%= asset_path("rails.png") %>); }

c.css.scss.erb
c { background-image: image-url("rails.png"); }

■First

$ bundle exec rake assets:precompile
$ cat public/assets/application.css

a{background-image:url(/assets/rails.png)}b{background-image:url(/assets/rails.png)}c{background-image:url(/assets/rails.png)}

It's work. But I can't specify relative url root.

■Second (with my commit)

$ bundle exec rake assets:precompile RAILS_RELATIVE_URL_ROOT=/foo
$ cat public/assets/application.css

a{background-image:url(/foo/assets/rails.png)}b{background-image:url(/foo/assets/rails.png)}c{background-image:url(/foo/assets/rails.png)}

It's work fine. Relative Url Root is prepended to some pathes.

■Third (relative url is specified in production.rb. without my commit)

$ vim config/environments/production.rb
config.action_controller.relative_url_root = '/bar'

$ bundle exec rake assets:precompile
$ cat public/assets/application.css

a{background-image:url(/bar/assets/rails.png)}b{background-image:url(/bar/assets/rails.png)}c{background-image:url(/bar/assets/rails.png)}

It's work fine. Relative Url Root is prepended to some pathes.

Owner

guilleiguaran commented Oct 10, 2011

Then setting config.action_controller.relative_url_root in production.rb is doing the job and you must add it manually before of precompile since Passenger don't set it until the app is started, right?

Contributor

kennyj commented Oct 10, 2011

I think so.
Therefore I think that we should use external variables (RAILS_RELATIVE_URL_ROOT) if precompile is used.

and assets is precompiled by capistrano after 'deploy:update_code'
https://github.com/capistrano/capistrano/blob/master/lib/capistrano/recipes/deploy/assets.rb

Owner

guilleiguaran commented Oct 11, 2011

@kennyj agree, rake assets:precompile RAILS_GROUPS=assets RAILS_RELATIVE_URL_ROOT=/foo isn't working for without your patch?

There is a Compatibility module in ActionController but I'm not sure if this is loaded during precompile: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/compatibility.rb#L15

Contributor

kennyj commented Oct 11, 2011

This module has not been loaded during precompile ;-)
config.action_controller.relative_url_root was empty.

Owner

guilleiguaran commented Oct 11, 2011

ok, then open a pull request with your patch :)

Contributor

kennyj commented Oct 11, 2011

My patch has two problem now.
1: not specified RAILS_RELATIVE_URL_ROOT case
2: some testunit enhancement

I will open a pull request after fixing them. thanks.

@kennyj kennyj added a commit to kennyj/rails that referenced this issue Oct 11, 2011

@kennyj kennyj fix #3259 fcf7714
Contributor

kennyj commented Oct 11, 2011

I opend a pull request
#3294

@kennyj kennyj added a commit to kennyj/rails that referenced this issue Oct 11, 2011

@kennyj kennyj added ENV["RAILS_RELATIVE_URL_ROOT "] support to rake assets:precompile,
because of asset precompilation did not work when deploying under sub uri
(fixed #3259)
39fd1ce

This very much ought to be fixed in the app configuration, not with an environment variable.

Only your application should define the environment variables it expects. The libraries and frameworks your application depends on should be configured directly by your application.

Rails or sprockets should honor a config.sprockets.mount_path or similar option.

If that's done, then you will simply be able to do:

# config/environments/production.rb
Rails.configuration.sprockets.mount_path = ENV["MOUNT_PATH"].presence

That code would also be able to go in a vendor/plugins/rails3_sprockets_mount_path/init.rb file that your production deployment process automatically injects into your deployed application, if that's how you roll. It could go in that plugin file that your deployment process injects instead of in your production environment file that is part of your application codebase.

Owner

guilleiguaran commented Oct 24, 2011

@yfeldblum are you talking about config.assets.prefix?

Yeah, like you could do it by playing with config.assets.prefix too.

The overall point remains that, for this type of case, we should be using some application configuration option that either Rails or Sprockets recognizes, such as config.assets.prefix, and that we set the value for that option ourselves based on an environment variable if we so choose, rather than Rails or Sprockets directly depending on that environment variable.

sgbmn commented Nov 17, 2011

A similar problem I had was Rails.application.routes.url_helpers.url_for doesn't have a was of specifying a sub-pat while precompiling assets (jruby/jruby-rack#72). As a hack I used @kennyj's method and was able to do something like

var sub_path = "<%= ENV['RAILS_RELATIVE_URL_ROOT'] || '' %>"

in a javascript asset and then added sub_path anywhere I created a url like

sub_path+"<%= Rails.application.routes.url_helper.url_for ... %>"

Am I right in thinking that there is currently no real solution to this since @kennyj's pull request was not merged? How are people with apps using relative url root working around this right now? I ended up copying assets.rake into my app, re-namespacing it, modifying it to grok the RAILS_RELATIVE_URL_ROOT env variable, and changing the deploy task to use the new task.

@josevalim didn't like the env variable approach, do we have any better ideas?

Contributor

atambo commented Dec 11, 2011

I run the following rake task assets:relativize after running assets:precompile in order to make all of the embedded assets be relative paths so that they will work no matter what sub directory I deploy under:

namespace :assets do
  desc "Make all embedded assets in stylesheets relative paths"
  task :relativize => :environment do
    Dir[Rails.root.join("public/assets") + "*.css"].each do |asset|
      contents = File.read(asset)
      File.open(asset, "w") do |file|
        file.puts contents.gsub("url(/assets/", "url(")
      end
    end
  end
end

@atambo - that makes me wonder why sass-rails doesn't just generate relative image urls for stylesheets. There must be some cases where that doesn't work, but I can't think of any.

Owner

guilleiguaran commented Dec 12, 2011

Fixed in #3946

Very nice, thank you - will this go into 3.1.4?

Owner

guilleiguaran commented Dec 12, 2011

Right now is in master (3.2) I will ask to one of Core Team members to backport it to 3-1-stable (for 3.1.4)

Contributor

graywh commented Mar 23, 2012

Did this ever get into 3.1.4?

Contributor

jonkessler commented Aug 20, 2012

@guilleiguaran It doesn't look like this ever made it into 3-1-stable. Could you or someone else please backport the change? We're gonna have to monkey patch 3.1 to get our assets to work in production.

Member

steveklabnik commented Aug 20, 2012

3-1 does not receive bug fixes, only security fixes.

@wvengen wvengen referenced this issue in foodcoops/foodsoft May 1, 2014

Open

Assets when running on suburi #283

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment