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

# Rails4のproduction環境でのみ、画像が表示されない問題
## 問題の概要
先日、弊社サービス **[STORYS.JP](http://storys.jp)** をRails3からRails4.0.2にアップデートしました。

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

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



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

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

## 問題の原因

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

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

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

SCSSではこんな感じです。

```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.png` を `assets: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を勝手に付与してくれます。

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

SCSSではこんな感じです。

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

[Rails GuideのAsset Pipelineの項](http://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets)にもう少し詳しく載っています。


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

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

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

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

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

ならばそもそも `app/assets/images` 以下はdigestを付与しないオプションは無いのかと思い、調べてみたところ、 `assets:precompile` を管理している **[sprockets-rails](https://github.com/rails/sprockets-rails#changes-from-rails-3x)** に次のような記述がありました。

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

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


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

```bash
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](http://storys.jp) ではこの解決法3を採用しました。

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

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

```scss: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してから表示されないことに気が付きます。

```html+erb: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環境でのみ画像が表示されない問題の解決策** は以上になります。

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