【Rails】carrierwaveとAWS S3とHerokuで画像をアップロード

画像アップロードに苦戦したいい思い出が掘り起こされたので備忘録的にメモ

「Railsアプリケーションで画像アップロードをしたいけど方法がわからない…」
「ローカル環境ではうまく動いたのに本番環境だとなぜか表示されない…」　　
「せっかくだしAWSを使って画像をホストしてみたい…」
という方向けです（主に自分がそうでした）

##手順
①gemを用意
②carrierwaveを使って画像アップローダーの準備
③画像の保存先を開発環境と本番環境でそれぞれ設定
④AWS S3の設定
⑤viewファイルにイメージタグを設置
⑥Heroku用に色々設定する

##①gemを用意
```
gem 'carrierwave'   #画像アップローダーを設置するためのgem
gem 'fog-aws'       #AWS S3用gem
gem 'dotenv-rails'  #S3へのアクセスキーを環境変数として保存させる
```

`$ bundle install`しましょう。

##②画像アップローダー準備

```
$ rails g uploader Image  　　#アップローダー名は任意。頭文字大文字の単数形で
```

これで`/app/uploader/image_uploader.rb`が作成されます。

つづいて、画像をアップロードさせるモデルとカラムを用意します。
今回はUserモデルにImageカラムがある想定で行います。
（カラムが無ければマイグレーションファイルを作成してAddColumnしましょう！）

モデルファイルに以下を追記しましょう。

```ruby:User.rb
class User < ApplicationRecord

  mount_uploader :image, ImageUploader  #画像保存用カラムを指定
  #mount_uploader :カラム名, アップローダー名（キャメルケース） という形で設定する

end
```

これでアップローダーとモデルが紐付けられました。

あとはviewファイルのお好きな場所にアップローダーを設置すれば準備完了です。
アップローダーはrubyタグで設置できます。

```
<%= f.file_field :image %>
```

###余談
コントローラーでストロングパラメーターの設定を忘れないようにしましょう！

```ruby:user_controller.rb
  def user_params
    params.require(:user).permit(:image, :remove_image)
  end
```
また、`:remove_image`を追加すると、以下のような画像削除用のチェックボックスも使用できます。

```
<%= f.check_box :remove_image %>
```


##③保存先の設定

`/app/uploader/image_uploader.rb`には主に保存先とそのパスの設定が書かれています。

```ruby:image_uploader.rb
class TesUploader < CarrierWave::Uploader::Base

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

　#デフォルトでコメントアウトされている長い文章は鬱陶しいから省略してます

end
```

デフォルトだと`public/uploads/モデル名/カラム名/id`に保存されることになっております。
これを「本番環境ではAWS・その他はデフォルト」というように条件によって変えます。

`image_uploader.rb`の`storage :file`という箇所がきな臭いのでここから手を付けます。
以下のように環境によって場合分けすれば良さそうです。

```ruby:image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base

  if Rails.env.production?
    storage :fog
  else
    storage :file
  end
 　#production環境ならクラウドサービス、その他はローカルに保存するよ〜という設定

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end
```

##④S3の設定
アップローダーの準備は整いましたので、あとは保存先の細かな設定をします。
ここは説明していると長くなるので簡単に…
主な流れとして以下の様になります。

①AWS IAMでS3用のユーザーを取得する
（IAMにはアクセスキーが発行されるので、そのキーを通してS3へのアクセスを実現します）
②S3のバゲットを作成
③作成したIAMユーザーとバゲットを元にアプリケーション側で設定

①②に関してはAWS関連の記事等を参考にしていただいてアカウントとバゲット作成をしましょう。
③をもう少し詳しく解説します。

`config/initialize/carrierwave.rb`を作成し以下の様に記載します。

```ruby:carrierwave.rb
unless Rails.env.development? || Rails.env.test?
  CarrierWave.configure do |config|
    config.fog_provider = 'fog/aws'
    config.fog_credentials = {
      provider: 'AWS',
      aws_access_key_id: ENV['S3_ACCESS_KEY_ID'],
      aws_secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
      region: 'us-east-1'
    }

    config.fog_directory  = 'syaberuba1'
    config.cache_storage = :fog
  end
end
```

・regionにバゲットのリージョン情報
・fog_directoryにバゲット名
・aws_access_key_id/aws_secret_access_keyにIAMユーザーのIDとアクセスキー

…なのですが、直接ここに打ち込んでデプロイしてしまうとアクセス情報がダダ漏れのため危険です。
そこでここでは`ENV['S3_ACCESS_KEY_ID']`というように環境変数を使用し、別のファイルでキーを設定します。

`gem dotenv`を使用します。
アプリケーションのルート階層に`.env`ファイルを作成してそこにキーを記載しておけば環境変数の設定をしてくれます。

```:.env
S3_ACCESS_KEY_ID = hogehogehoge
S3_SECRET_ACCESS_KEY = hogehogehoge
```

※.envはgitignoreしてリモートリポジトリにプッシュされないようにしましょう。

##⑤viewファイルにイメージタグ設置
アップロードした画像は以下のタグで表示できます。

```
<%= image_tag @user.image.to_s, size: 150, alt: 'プロフィールイメージ' %>
```

`インスタンス.画像用カラム.to_s`で保存先のurlを取得しています。
size等のオプションも使用できるのでお好きなように設置しちゃいましょう。

##⑥Herokuへデプロイ
仕上げにHerokuへデプロイしましょう。

`.env`ファイルはgitignoreされてしまうのでHerokuへはプッシュされません。
なので、Heroku用に別途環境変数を設定してあげないといけませんね。

```
$ heroku config:set S3_ACCESS_KEY=hogehoge
$ heroku config:set S3_SECRET_KEY=hogehoge
```

なんてクッッッッッソめんどくさい且つ簡単に打ち間違えそうなことをしなければいけませんが、
ここで`.env`をそのまま活用できる小技があります。

heroku用のプラグインである`heroku-config`を導入しましょう。

```
$ heroku plugins:install heroku-config
$ heroku config:push
```

これで`.env`に記載されている環境変数が直接設定できます。

ちなにみ、Heroku公式サイトにアクセスしても環境変数は設定できます。
ログイン後、設定したいアプリケーションを選択して、Settingsタブ ＞ Config Vars　＞ Reveal Config Vars　
で環境変数を分かりやすく確認、設定できるのでお試しください。
※herokuは環境変数をクオーテーションで囲ったらなぜか動かなかったので要注意です…

##おわり

他にもバゲットのセキュリティ設定とかで躓いたりもしますが、google先生に頼りながら頑張りましょう…
幸いにもこのトピックは記事も多いので、それぞれの環境に合わせて実現していきましょう!