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

  • 477
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Rails4のproduction環境でのみ、画像が表示されない問題

問題の概要

先日、弊社サービス STORYS.JP をRails3からRails4.0.2にアップデートしました。

Rails4はRails3に比べて、development環境でのページロードにかかる時間が1/10ぐらいになりました。

読み込みに 1500ms ほどかかっていたのが、Rails4にするだけで本番並みの 150ms で返ってくるようになり、本当に非常に快適に開発ができるようになりました。

しかし、便利なものには弊害も多く、様々な問題がおきます。

その中でも、解決に苦しんだ問題が、 これまでは正常に表示されていた画像が、Rails4のproduction環境でのみ表示されない問題 です。

問題の原因

問題の原因は、 app/assets/images 以下に置いてある画像を直接パスを指定して読み込んでいることでした。

STORYS.JP では基本的に、HTML, SCSSで画像 app/assets/images/hoge.png を参照したいときは次のように書いていました。

hoge.html.erb
<img src="/assets/hoge.png" />
<div style="background-image:url(/assets/hoge.png)" />

SCSSではこんな感じです。

hoge.css.scss
.hoge {
  background-image: url(/assets/hoge.png);
}

しかしこの /assets/hoge.png という書き方では、Rails4からは表示されなくなりました。

Rails4での assets:precompile

productionにdeployするときに実行する assets:precompile ですが、
Rails4からは digest無しのjs, css, image は作成しない仕様になりました

digestとはjs, css, 画像等を assets:precompile するとファイル名の後ろにつく文字列のことです。

/assets/application-d34a3eba396a045b8c71d1605256e1a2.css

つまり、今までは app/assets/images/hoge.pngassets:precompile したときに得られる成果物は、

rails3
public/assets/hoge.png
public/assets/hoge-○○○○.png

であったのに対し

rails4
public/assets/hoge-○○○○.png

となるようになりました。

したがってproduction環境では /assets/hoge.png と指定したファイルを参照することはできません。

解決法1 asset_path関数, image-url関数を使う

一番順当な解決法です。画像のパスの指定をそのまま書くのではなく、Railsが提供している関数を呼ぶことで、productionのときにはdigestを勝手に付与してくれます。

hoge.html.erb
<img src="<%= asset_path "hoge.png" %>" />
<div style="background-image:url(<%= asset_path "hoge.png" %>)" />

SCSSではこんな感じです。

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

Rails GuideのAsset Pipelineの項にもう少し詳しく載っています。

解決法2 public/assets 以下に画像を直接置く

HTML,SCSSをわざわざRailsの関数を使って書きなおすのはめんどくさい・・・そう考えていた時にあることを思いつきました。

そもそもdigestを付与するのは、あるファイルを更新したときに、ブラウザ側で持っているキャッシュとの相違が無いようにするためのものです。

それはjavascriptやstylesheetならば話はよくわかります。application.jsは良く更新されるので。

しかし、画像ファイルで名前を変更せずに中身を変更することはほとんどありません。そうするならば名前も変更するか、新しいファイルを作るからです。

ならばそもそも app/assets/images 以下はdigestを付与しないオプションは無いのかと思い、調べてみたところ、 assets:precompile を管理している sprockets-rails に次のような記述がありました。

Only compiles digest filenames. Static non-digest assets should simply live in public/.

つまり、コンパイルする必要のないものは、そのままpublic以下に置けと言っています。

確かに、 assets:precompile のそもそもを考えればその通りだと思い、今まで app/assets/images 以下にあったものを public/assets 以下に移動しました。

cp -r app/assets/images public/assets/

これならば、既存のHTMLやSCSSを書き換える必要すらなく完璧である・・・と思ったのですが、1つ特定の環境において問題が起こりました。

それは capistranoでのdeploy です。

ここについては詳しくは調べていませんが、gitの管理下に public/assets を含めていても、deploy時には public/assets 以下は削除されてしまうようです。

したがって、git管理下に public/assets/hoge.png が存在したとしても、deployされた先には public/assets/hoge.png は存在しないので画像は表示されません。

もしかしたらcapistranoは public/assets 以下がgit管理下になることを想定されていないのかもしれません。

解決法3 public/images 以下に画像を置く

そこで STORYS.JP ではこの解決法3を採用しました。

画像は基本的に public/images 以下に置くようにしました。

hoge.html.erb
<img src="/images/icons/hoge.png" />
<div style="background-image:url(/images/icons/hoge.png)" />
hoge.css.scss
.hoge {
  background-image: url(/images/icons/hoge.png);
}

これの欠点は、既存のファイルを /assets/icons/hoge.png -> /images/icons/hoge.png に書きなおす必要があること、またproduction環境の nginx, apache 等に記述されているであろう /assets/ へのキャッシュのポリシーを /images にも設定しなくてはいけないことです。

しかし、こうすることでproduction環境でも、無事画像が表示でき、capistranoでのdeployも問題ありませんでした。

この解決法3の良い所はもう一つあります。
それは、完全に app/assets/images を使わないことで、間違って /assets/hoge.png の記述をしにくいところです。

解決法1では、development環境で /assets/hoge.png のような記述をしても問題なく表示されます。
しかし、production環境では動きません

これは結構やっかいで、コードレビュー等をしていたとしても、軽微な違いなだけに気づきにくく、deployしてから表示されないことに気が付きます。

hoge.html.erb
<img src="/assets/icons/hoge.png" />
<img src="<%= asset_path "icons/hoge.png" %>"/>
<!-- この違いを見分けるのは結構難しい !>

しかし、常に app/assets/images を使わないようにすることで、 <%= asset_path "icons/hoge.png" %> のような記述はできなくなり、問題発見が容易になります。

 


 

Rails4のproduction環境でのみ画像が表示されない問題の解決策 は以上になります。

もしこの問題が起きた場合は、自分たちの状況、必要な物に応じて好きな選択肢を選んでください。