capistranoで構築した環境でassetsの中身を1から作り直したい

## なぜそんなことをしたくなったのか

capistrano-railsのassets:precompileが走っている時にEC2がメモリ不足に陥り、インスタンスを再起動させた結果、deployが中途半端なところで終わってしまいました。
もし中途半端なコンパイル結果をもとにWebサーバーが動いて実行時エラーなんて起きたときには目も当てられないので、なんとかまっさらな状態に戻そうとしてあれこれしました。

## バージョン情報

```
rails (5.2.4.2)
capistrano (3.11.0)
capistrano-rails (1.6.1)
sprockets (3.7.2)
sprockets-rails (3.2.1)
```

## 前提条件

current_pathはこのパスとする

- /var/www/app_name/current

shared_pathはこのパスとする

- /var/www/app_name/shared

## 先に結論

結論から言うと、やるべきことはこれです。

1. /var/www/app_name/shared/public/assets の中身を消す
2. /var/www/app_name/shared/tmp/cache/assets の中身を消す
3. いつもどおりデプロイ

注意点があります。
capistranoには`deploy:clobber_assets`のようなassetsをいかにも消しそうなコマンドが標準で用意されていますが、
これを使わずちゃんと手で消すということです。理由は後述します。

## 説明

### capistranoのディレクトリ構成

まず、capistranoによってデプロイされたサーバーの`/var/www/app_name`の中は、下記のような構造になっています。

```
├── current （releases内の最新のディレクトリにシンボリックリンクが貼られてる）
├── releases
│   ├── 20200601101105
│   ├── 20200601102714
│   └── 20200601105159
├── repo
├── revisions.log
└── shared
```

このcurrentの下、つまり、releases/20200601105159の下には、あなたのプロジェクトのリポジトリにあるような構成になっているでしょう。
そこにはapp/があり、config/があり、public/があるはずです。

このpublic/の下にはビルドされた静的ファイルが詰め込まれたassets/が入っている……ように見せかけて、
`/var/www/app_name/current/public/assets`は`/var/www/app_name/shared/assets`へのシンボリックリンクになっています。これはrails対応の公式gemであるcapistrano-rails側で`linked_dirs`の設定がされるためです。

そしてちゃんと設定をしていたのであれば、`/var/www/app_name/current/tmp/cache`は同様に`/var/www/app_name/shared/tmp/cache`へのシンボリックリンクになっているはずです。 [capistrano-railsのREADME](https://github.com/capistrano/rails)に書かれている通りに設定していた場合はそうなっているはずです。

```rb:config/deploy.rb
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache' ...以下省略
```

### assets:precompileのキャッシュの仕組み

[Railsガイド アセットのキャッシュストア](https://railsguides.jp/asset_pipeline.html#%E3%82%A2%E3%82%BB%E3%83%83%E3%83%88%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%82%B9%E3%83%88%E3%82%A2)

> デフォルトのSprocketsは、development環境とproduction環境でtmp/cache/assetsにアセットをキャッシュします。

こう書かれている通り、tmp/cache/assetsにコンパイル結果のキャッシュがあるので、これを消します。
capistranoだと`/var/www/app_name/shared/tmp/cache/assets`がこれにあたるので消します。

### Sprocketsのデプロイ結果のキャッシュの仕組み

assets:precompileのキャッシュとは別に、Sprocketsはデプロイ結果のファイルも取っておいてあります。
[Sprockets](https://github.com/rails/sprockets/)のコードにこんなのが書かれています。

```rb
module Sprockets
  module Exporters
    # Writes a an asset file to disk
    class FileExporter < Exporters::Base
      def skip?(logger)
        if ::File.exist?(target)
          logger.debug "Skipping #{ target }, already exists"
          true
        else
          logger.info "Writing #{ target }"
          false
        end
      end

      def call
        write(target) do |file|
          file.write(asset.source)
        end
      end
    end
  end
end
```

deploy:assets:precompileしているときに、標準出力でログがずらっと出てきているのを目にしていませんか？　アレを実現しているのがおそらくは上のコードです。
`logger.info "Writing `のところを見てください。

こんな感じ：

```
      01 I, [2020-06-01T08:56:09.691790 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:09.692557 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:17.418572 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:17.418856 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:21.414096 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
      01 I, [2020-06-01T08:56:21.414368 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
```

この`skip?`の判定に引っかかった場合。
つまり、この例だと`/var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…`のdigest込みのファイルが既に存在していた場合、
Sprocketsはファイルを書き出しません。

なので、capistranoだと`/var/www/app_name/shared/assets`に同じファイルがあった場合、このskip?でスキップされてしまうということです。
消しておく必要がありますね。

### おや？　便利なコマンドがありそうだが……？

capistranoは`cap -T`で全てのコマンドを表示してくれます。
assets関係だとこんなコマンドが見つかります。

```
cap deploy:cleanup_assets          # Cleanup expired assets
cap deploy:clobber_assets          # Clobber assets
```

標準で`deploy:clobber_assets`というコマンドが用意されているではありませんか！
clobberとはぶん殴るという意味です。強制的に新しいassetsにしてくれそうだ！　こりゃいいや！　と思って使うと、実は思ったような挙動をしてくれません。

```
$ bundle exec cap review deploy:clobber_assets

00:00 deploy:clobber_assets
      01 bundle exec rake assets:clobber
      01 I, [2020-06-01T11:22:46.062204 #6756]  INFO -- : Removed /var/www/app_name/releases/20200601094959/public/assets
      01 Removed webpack output path directory /var/www/app_name/releases/20200601094959/public/packs
```

いかにもassetsをディレクトリごと消してくれていそうな雰囲気ですが、よく見るとRemoveしているのはただのシンボリックリンクで、肝心の中身は消えていません。マジか。
そして、そもそもコンパイル時のキャッシュであるtmp/cacheの方は消してくれていません。

このことについては[issue](https://github.com/rails/sprockets-rails/issues/391)も上がっているようです。

なので、「結論」に書いた通り、手動でコマンドを実行して消すか、自分でコマンドを定義してやるのが良さそうです。