Rails: Asset Pipeline: production 環境で、CSS で指定している画像が表示されない問題について

Rails の Asset Pipeline を使っていると、
development 環境では動作していたのに、production 環境で画像が表示されない、
アセット(CSS、JS)が正しく取得できないという問題が起きることがある。

この問題は、いろいろとバリエーションがあるのだが、
今回は CSS 上で使用している画像が表示されないケースについて説明しようと思う。

想定環境は次のとおり。

環境 バージョン
Ruby 2.0.0p598
Ruby on Rails 4.1.9
nginx 1.6.2
unicorn 4.8.3

次のような設定で、production 環境を起動しているとする。

config.serve_static_assets = false # アセットの配信は、Nginx が担当する
config.assets.digest = true # フィンガープリントあり

原因1:CSS にアセットのファイル名を直接記入している

Rails では「config.assets.digest = true」とした場合、アセットにフィンガープリントが付与される。
たとえば「image.png」という画像の場合、
production 環境では 次のようなファイル名に変換される。

image-908e25f4bf641868d8683022a5b62f54.png

このハッシュ値は、assets:precompile 時に動的に決定される。
そのため、CSS で次のような書き方をしている箇所は、画像は表示できない。

/* global.css.scss */
background-image: url("image.png");

これについては「アセットパイプライン — Rails ガイド」が詳しい。



原因2:image-url(), image-path() を使っている

CSS 上で、画像パスを下記のように記述しているとする。

/* global.css.scss */
background-image: image-url("image.png");

この image-url は css の機能ではなく、Asset Pipeline 機能である。
image-url で記述された箇所は、次のように変換される。

background-image: image-url("image.png");
↓↓(precomiple)↓↓
background-image: url("/assets/image.png");

「原因1」でも説明したが、アセットにフィンガープリントを付与している場合は、
image-url, image-path では対応できないようだ。




同じ問題に悩まされている人がいる。

ruby on rails - Rails4: image_url not generating digest in scss - Stack Overflow

image-url('image.jpg'); -> http://www.mydomain.it/images/image.jpg
image_url('image.jpg'); -> same as above
url(image-path('header.jpg')); -> http://www.mydomain.it/images/image.jpg
asset-url('image.jpg', image); -> http://www.mydomain.it/image.jpg
problem still remains: assets are compiled but requested without digest.


一方、こちらの記事には image-url でフィンガープリントまで対応できると書いてあるが、
これは、何かの間違いだろう。

Rails の Asset Pipeline 有効ではまった箇所 | EasyRamble

background-image: image-url("logo.png");
こうすると、ダイジェスト値を付加したURLを指定してくれて、正常に動作する。

解決方法

先ほど紹介した記事にもあったが、個人的にはこの方法がベストな気がしている。

ruby on rails - Rails4: image_url not generating digest in scss - Stack Overflow

Should use asset_path. Also, it needs to run under ERB tag, as SCSS does not compile asset_path. Rename common.scss to common.scss.erb

.body { background-image: url(<%= asset_path 'bg.jpg' %>) }


具体的には、先ほどのコードをこのようにする。

/* global.css.scss.erb */
background-image: url(<%= asset_path "image.png" %>);

一度 ERB で解釈する必要が有るため、拡張子に「erb」を付けなければならないのが、
ちょっとカッコ悪いが。。



その他の方法

下記の記事には、その他の方法がいくつか提案されている。

Rails4ではbackground:url("assets/hoge.png")の書き方は動かない話 - Qiita

余談

image-url で、ハッシュまで付与してくれれば、一番スマートなんですけどね。。