在部署到子URI时,Rails 3.1中的预编译资产损坏

时间:2011-09-03 15:07:36

标签: ruby-on-rails-3.1 asset-pipeline passenger assets sprockets

我正在更新Rails 3应用程序以使用Rails 3.1,并且作为其中的一部分,我正在利用新的资产管道。到目前为止,我已经把一切都解决了一个我无法解决的烦人问题。

应用程序及其所有资产在开发中运行良好,但在生产中,它使用Passenger(http://the-host/sub-uri/)部署到子URI。这个问题是资产是在部署期间预编译的,我的一个CSS(好吧,它是一个.css.scss文件)文件正在使用来自image-url的{​​{1}}帮助器宝石。由于在预编译过程中,路径被硬编码到预编译的CSS文件中,因此不考虑子uri:

在我的sass-rails文件中:

.css.scss

编译的body { background-image: image-url("bg.png"); } 文件中的结果:

application-<md5-hash-here>.css

使其正常工作应该是什么:

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

这种情况只是要求太多了吗?如果是这样,我将不得不切换回旧的非资产流水线方式,只需从body { background-image: url(/sub-uri/assets/bg.png); } 提供我的图像和CSS。然而它似乎应该被考虑和解决的东西......?我错过了解决方案吗?


编辑1:我应该注意,使用erb solution会产生与预期相同的结果。


编辑2:回应Benoit Garret的评论

不,问题与public无关。我尝试设置(到config.assets.prefix而不是默认的/sub-uri/assets)但事实证明这是错误的做法 - 看起来这个设置已经与Rails应用程序的根有关了,而不是服务器。删除它(从而返回到默认值)已经解决了导致的所有奇怪问题(并且有很多,所有资产都在/assets中结束 - 这一切都非常奇怪)。唯一的问题是/sub-uri/sub-uri/assets助手和朋友在预编译时不会获取子URI。毋庸置疑,这是合乎逻辑的,因为当它被预编译时,它不可能知道当它在Passenger下运行时,它将以这种方式配置。我的问题是如何告知它,从而最终得到预编译结果中的正确路径。如果确实可以做到。

我目前的解决方法是在CSS中引用iamge,如下所示:image-url并将其放在非流水线url(../images/bg.png)位置。非常理想,因为它不会从指纹识别和管道提供的所有内容中受益。

4 个答案:

答案 0 :(得分:4)

最后,我制定了几个解决方法/解决方案。

1)从https://github.com/rails/sass-rails/issues/17来看,这可能会在sass-rails中修复。我已经修好了帮手。我自己沿着上面链接中提出的补丁行。我只需在deploy.rb中的资产预编译行中设置所需的环境变量。

我在单个文件config/initializers/gem_patches.rb中完成所有猴子修补。在这个文件中,我修改了这个方法:

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)或者,如果您可以在CSS中嵌入图像,将样式表更改为具有.erb扩展名,并将image-url("bg.png")替换为url(<%= asset_data_uri "bg.png" %>),则无需更改sass-轨道。 asset-data-uri不作为纯Sass函数存在,因此您必须使用Rails助手asset_data_uri

答案 1 :(得分:2)

在最新的Rails 3.1.3中,你现在需要修补一个不同的模块,以便它可以工作

这就是我做的事情

module Sprockets
  module Helpers
    module RailsHelper

      def asset_path(source, options = {})
        source = source.logical_path if source.respond_to?(:logical_path)
        path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
        path = options[:body] ? "#{path}?body=1" : path
        if !asset_paths.send(:has_request?)
          path = ENV['RAILS_RELATIVE_URL_ROOT'] + path if ENV['RAILS_RELATIVE_URL_ROOT']
        end
        path
      end

    end 
  end
end

在我的deploy.rb中,我有:

desc "precompile the assets"
namespace :assets do
  task :precompile_assets do
    run "cd #{release_path} && rm -rf public/assets/* && RAILS_ENV=production bundle exec rake assets:precompile RAILS_RELATIVE_URL_ROOT='/my_sub_uri'"
  end
end
before "deploy:symlink", "assets:precompile_assets"

答案 2 :(得分:2)

我正在使用Rails 3.1.3并成功部署到子URI。 我没有修补任何东西。

此设置的关键问题已得到更好的讨论here。如您所见,该解决方案已应用于Rails 3.2,并且从未返回到3.1.4。

但是,我使用适用于我的设置的Rails 3.1.3 的解决方案。

试试这个: (我不是专家,只是想为解决困扰我几个小时的问题做出贡献......)

environment.rb中:

#at top:
ENV['RAILS_RELATIVE_URL_ROOT'] = '/rais'

production.rb:

config.assets.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] ? ENV['RAILS_RELATIVE_URL_ROOT'] + '/assets' : '/assets'

routes.rb中:

  Rais::Application.routes.draw do
       scope ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do    #see config/environment.rb
             <<resources here>>
       end
  end

正如您所看到的,我将assets.prefix放在production.rb中,而不是在application.rb中 之后你会:

rake assets:clear
rake assets:precompile

然后,用控制台测试:

RAILS_ENV=production rails console

结果:

foo = ActionView::Base.new
foo.stylesheet_link_tag 'application'
 => "<link href=\"/rais/assets/layout.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/rais/assets/application.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />" 
foo.image_tag('arrow-up.png')
 => "<img alt=\"Arrow-up\" src=\"/rais/assets/arrow-up-ca314ad9b991768ad2b9dcbeeb8760de.png\" />" 

答案 3 :(得分:0)

经过一番挖掘后,我发现了这个问题。问题出在Rails中,特别是Sprockets :: Helpers :: RailsHelper :: AssetPaths#compute_public_path。 Sprockets :: Helpers :: RailsHelper :: AssetPaths继承自ActionView :: AssetPaths并覆盖了许多方法。当通过Sass :: Rails :: Resolver调用compute_public_path #sublic_path方法是sass-rails时,rails sprocket helper接受解析资产的任务。 Sprockets :: Helpers :: RailsHelper :: AssetPaths#compute_public_path遵循super,即ActionView :: AssetPaths#compute_public_path。在这种方法中有一个has_request的条件?在rewrite_relative_url_root上,如下所示:

def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil)
  ...
  source = rewrite_relative_url_root(source, relative_url_root) if has_request?
  ...
end

def relative_url_root
  config = controller.config if controller.respond_to?(:config)
  config ||= config.action_controller if config.action_controller.present?
  config ||= config
  config.relative_url_root
end

如果查看rewrite_relative_url_root的内部结构,它依赖于存在的请求以及从控制器变量派生它以解析相对url根的能力。问题是,当sprockets为sass解析这些资产时,它没有控制器,因此没有请求。

上面的解决方案对我来说在开发模式下不起作用。以下是我现在使用的解决方案:

module Sass
  module Rails
    module Helpers
      protected
      def public_path(asset, kind)
        resolver = options[:custom][:resolver]
        asset_paths = resolver.context.asset_paths
        path = resolver.public_path(asset, kind.pluralize)
        if !asset_paths.send(:has_request?) && ENV['RAILS_RELATIVE_URL_ROOT']
          path = ENV['RAILS_RELATIVE_URL_ROOT'] + path
        end
        path
      end
    end
  end
end