【Rails】ポートフォリオに動画投稿機能を実装してみた【AWS S3 + EC2 + CarrierWave + Fog】

# はじめに
本記事は [駆け出しエンジニアの第一歩！AdventCalendar2020 1日目](https://qiita.com/advent-calendar/2020/programming-beginner)の記事です。

転職活動に向けて個人アプリを開発しました。
マジシャン向けの動画投稿SNSサイトです。


今回は動画投稿機能について自分の理解を深めるためにもまとめてみようと思います！

Railsを学習し始めて3ヶ月ほどの若輩者ゆえ、必死に色々調べながらなんとか実装できたレベルなので、もし間違っているところや、こうした方が良いというところがあればご意見いただけますと幸いです…！


# 動画投稿機能概要

### アップロード画面
アップロード画面の[ファイルを選択]をクリックし、動画をアップロードします。
タイトル、説明、タグを入力して、[アップロード]をクリックすると投稿されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/ccf1967f-b00f-b5e3-2c6f-58925b5e51d7.png)
### 投稿詳細・再生画面
投稿の詳細画面のサムネイルをクリックすると動画が再生されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/406601dd-7d87-8c96-d78a-77474e0c7e68.png)


# どうやって作ったか

## 開発環境
IDE：Cloud9
Ruby：2.6.3
Rails：5.2.4
ストレージ：Amazon S3

## Gemfile

```ruby:Gemfile
gem 'carrierwave'
gem 'fog-aws'
gem 'dotenv-rails'
```

carrierwaveは動画のアップローダを実装するときに必要なgemです。
fog-awsは外部のストレージ(Amazon S3)へのアップロードを可能にするgemです。
dotenv-railsは環境変数を管理する事が出来るgemです。後ほど登場するキーなどのデリケートな情報を公に漏洩させないようにするために使います。

## STEP1 投稿フォームを作る
まずは形から作りましょうということで動画のアップロード部分のビューのコードです。
(タイトル、説明、タグのフォームは本記事の主旨とは若干異なるので省略しています。)
下記例の場合、Magicモデルの中にvideoカラムを作成し、そこに動画をアップロードしています。

```erb:form.html.erb
<%= form_with model: @magic, url: magics_path, local: true do |f| %>
   <%= f.label "動画のアップロード" %>
   <%= f.file_field :video, :accept => 'video/*' %>
   <%= f.submit "アップロード" %>
<% end %>
```

次に動画のアップローダを作成します。
下記コマンドを実行すると、app/uploaders/video_uploader.rbが作成されます。
こちらはcarrierwaveのコマンドであり、ここで作成したアップローダを後ほどAmazon S3にアクセスできるようカスタマイズする流れとなります。

```terminal:ターミナル
rails g uploader video
```

作成したアップローダをMagicモデルのvideoカラムに紐付けます。

```ruby:magic.rb
# モデルとアップローダの紐付け
mount_uploader :video, VideoUploader
```

## STEP2 Amazon S3を作成する
Amazon S3は動画を保管する倉庫の役割を担います。

AWSコンソールにログインし、サービスより[S3]をクリックします。
Amazon S3のバケット画面が表示されたら、[バケットを作成]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/0a2eb73f-f31c-82ac-fd4f-8e5e781d53f0.png)

バケット名に任意の名前を入力し、リージョンは最寄りのリージョンを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/a8392e6d-5e05-9727-9871-99a8aa1e067b.png)

パブリックアクセスの設定は下二つのみにチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/d12c71de-245c-35c5-96ba-90fa5fa0d41f.png)

その他の設定についてはデフォルトの状態のまま、画面下部のバケットの作成をクリックします。

## STEP3 IAMユーザーを作成する
AWS IAMユーザーはS3バケットにアクセスするためのアカウントだと思っていただければと思います。
ここで作ったユーザーを介してRailsアプリがS3にアクセスするイメージです。

AWSコンソールにログインし、サービスよりIAMをクリックします。
IAMの画面が表示されたら左メニューより[ユーザー]をクリックし、[ユーザーを追加]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/b0be69a1-1122-fedc-28e1-85c24c5ba1fd.png)

任意のユーザー名を入力し、プログラムによるアクセスにチェックを入れて、[次のステップ: アクセス権限]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/4d11b5e9-3d27-9119-4a2d-3270726beb2d.png)

次にこのユーザーのアクセス許可の設定をします。
今回はS3へのアクセスを許可する設定を行うので、[既存のポリシーを直接アタッチ] をクリックします。
その後、S3へのアクセスポリシーを探すために、ポリシーのフィルタの右の検索バーに[S3]と入力します。
そうすると、[AmazonS3FullAccess] というポリシーが見つかりますので、こちらにチェックを入れて、[次のステップ: タグ]をクリックします。

AmazonS3FullAccessは文字通りAmazon S3への全てアクセスを許可してしまいますので、必要に応じてもう少し厳しめのポリシーを設定する方が良いかもしれません。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/b82dffd2-815e-1d99-747d-028a1202d969.png)

タグの設定はせず、[次のステップ：確認]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/a547783e-af2b-388a-26d4-6b65b7099d5d.png)

確認ができたら、[ユーザーの作成]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/8e083f65-c5b5-ec29-e1d2-90c6bf3c63f7.png)

これでAmazon S3へ接続するためのユーザーは作成完了です！
作成完了画面が表示されたら、[アクセスキー] と[シークレットキー]をメモしておきましょう！
念のためCSVもダウンロードしておきましょう！
これらのキーはfogがAWSに接続するために必要になってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/0e54b43f-9931-d0fc-3ba4-c487bedff770.png)


## STEP4 fogを使ってAmazon S3へアクセスする
まずはSTEP1で作成したアップローダの設定を行います。
stoeage :fogとすることで動画の保存先をfogの接続先にすることができます。
store_dirにはS3バケットのどのディレクトリに保存するかを記述します。
下記のように書くと、保存先はuploads/モデル名/投稿IDディレクトリに保存されます。


```ruby:app/uploaders/video_uploader.rb
class VideoUploader < CarrierWave::Uploader::Base
  (省略)
  # Choose what kind of storage to use for this uploader:
  # storage :file
  storage :fog
  (省略)
  # 動画ごとに保存するディレクトリを変える
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
  end
end
```

ちなみにstoeage :fileとすると、 public配下に動画が保存されます。
そのため、開発環境やテスト環境はpublicに、本番環境のみS3に保存したい場合は下記のように書きましょう。

```ruby:app/uploaders/video_uploader.rb
class VideoUploader < CarrierWave::Uploader::Base
  (省略)
  # developmentとtest以外はS3を使用
  if Rails.env.development? || Rails.env.test? 
    storage :file
  else
    storage :fog
  end
  (省略)
  # 動画ごとに保存するディレクトリを変える
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
  end
end
```

次にfogの設定を行っていきます。
fogの設定は/config/initializers/carrierwave.rbで行うのですが、
もしファイルが存在しなければ、新規に作成しましょう。

こちらのファイルの内容をざっくりと説明すると、fogの接続先(Amazon S3)を指定しています。


```ruby:/config/initializers/carrierwave.rb
# CarrierWaveの設定呼び出し
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  if Rails.env.production? # 本番環境の場合
    config.fog_provider = 'fog/aws'
    config.fog_directory  = 'STEP2で作成したバケット名'
    config.fog_credentials = {
      provider: 'AWS',
      aws_access_key_id: ENV['AWS_ACCESS_KEY'],
      aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
      region: 'STEP2で作成したバケットのリージョン',
      path_style: true
    }
  end
  #日本語ファイル名の設定
  CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
  config.cache_dir = "#{Rails.root}/tmp/uploads"
end
```

さてコードの↓部分ですが、STEP3で作成したユーザーのアクセスキーとシークレットキーを格納することになります。
ただ、これらの情報はGithubなどにアップロードすべきではない情報であるため、環境変数に格納します。
今回はdotenv-railsというgemを使って、.envファイルに環境変数を書いていきます。

```ruby:/config/initializers/carrierwave.rb
aws_access_key_id: ENV['AWS_ACCESS_KEY'],
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
```

```ruby:.env
AWS_ACCESS_KEY="STEP3で作成したユーザーのアクセスキー"
AWS_SECRET_ACCESS_KEY="STEP3で作成したユーザーのシークレットキー"
```

## STEP5 コントローラを設定する
ここまで来ればほぼ完成なのですが、最後にアプリの投稿・表示部分のコントローラーのコードも一応載せておきます。
ご自身の投稿用のコントローラに合わせて実装していただけたらと思います。

```ruby:app/controllers/magics_controller.rb
class MagicsController < ApplicationController

  (省略)

  def show
    # IDに基づく投稿を取得
    @magic = Magic.find(params[:id])
  end

  def create
    # 投稿内容を取得
    @magic = Magic.new(magic_params)
    if @magic.save
      flash[:notice] = "動画を投稿しました。"
      redirect_to magic_path(@magic)
    else
      # エラーが発生した場合
      render :new
    end
  end

  (省略)
  
end
```

動画を表示する部分のビューはこちらになります。
※本アプリ内ではFFmpegを用いて自動でサムネイルを作成しているのでimage_tagを使って表示していますが、この部分はご自身のアプリに合わせて変えていただければと思います。

```erb:show.html.erb
<%= link_to @magic.video_url.to_s do %>
  <%= image_tag(@magic.video_url(:screenshot).to_s ,:size => '256x144', id: "video", class: "magic-image", :alt => "screenshot") %>
<% end %>
```

# まとめ

RailsアプリからAmazon S3に動画を保存する際の全体の動きをまとめるとこのようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620431/63b9c443-03ed-cc50-e3e4-89811f5f957a.png)


この記事を書く１週間前、泣きながら実装していたのが早くも懐かしく感じます。
これから動画投稿アプリを作ってみようという方の参考になれば嬉しいです！

# 参考サイト
[【Rails】CarrierWaveによる画像投稿 【AWS EC2】 - Qiita](https://qiita.com/Rubyist_SOTA/items/f1c36cab1eceedf00008)
[【Rails5】Carrierwave + Fog で AWS S3 に画像をアップロードする | RemoNote](https://remonote.jp/rails-carrierwave-fog-s3)
[Railsでcarrierwaveを使ってAWS S3に画像をアップロードする手順を画像付きで説明する - Qiita](https://qiita.com/junara/items/1899f23c091bcee3b058)
[【Rails】Carrierwaveとfog-awsを用いて、画像をAWS S3へアップロードする方法 - Qiita](https://qiita.com/matsubishi5/items/96721426cff2b1d67cdd)
[Rails gem CarrierWave + fog を利用して AWS S3 へ画像をアップロードする - Qiita](https://qiita.com/ryo-ichikawa/items/a30dc626cba1ec909d57)