【初心者】Active Storageについて

# はじめに
プログラミング学習を初めて３ヶ月程度の初心者ですが、Railsチュートリアルで学んだことを備忘録として残したいと思います。この記事は、Railsチュートリアルを参考に書きました。



# Active Storageとは

Railsでファイルをアップロードするのに最も便利な方法は、Railsに組み込まれているActive Storageという機能を用いることです。これを使えば、フォームで画像の投稿機能などが簡単に作れます。
アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。


# Active Storageを使えるようにする
```
$ rails active_storage:install
$ rails db:migrate
```
このマイグレーションにより、`active_storage_blobs`と`active_storage_attachments`という名前の2つのテーブルが生成される。｀active_storage_blobs`は実際にアップロードしたファイルが保存されるテーブルで、`active_storage_attachments`は中間テーブルになります。

# 1つのファイルを追加する
今回は、Micropostモデルに１つの画像を追加したとします。

```ruby:micropost.rb
class Micropost < ApplicationRecord
 has_one_attached :image
end
```
:imageはファイル名で、:photo、:avatarなど、ファイルの用途に合わせて好きなものをして下さい。
`has_one_attached`メソッドは、指定のモデルと、アップロードされたファイルを関連付けるのに使います。この場合はimageを指定して`Micropost`モデルと関連付けます。

次に、マイクロポストのフォームに`file_fiele`タグを追加します。

```ruby:app/views/shared/_micropost_form.html.erb
<%= form_with model: @micropost, local: true  do |f| %>
  <%= f.text_area :content %>
  <%= f.file_field :image %>
  <%= f.submit %>
<% end %>
```

最後に、`Microposts`コントローラを更新して、新たに作成した`micropost`オブジェクトに画像を追加できるようにします。`Actuve Storage API`にはそのための`attach`メソッドが提供されていますので、これを使って行えます。具体的には、`Microposts`コントローラの`create`アクションの中で、アップロードされた画像を`@micropost`オブジェクトにアタッチします。このアップロードを許可するために、`micropost_params`メソッドを更新して`:image`を許可済み属性リストに追加し、Web経由で更新できるようにする必要もあります。

```ruby:microposts_controller.rb
class MicropostsController < ApplicationController
 def create
    @micropost = current_user.microposts.build(micropost_params)
    @micropost.image.attach(params[:micropost][:image])
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @feed_items = current_user.feed.paginate(page: params[:page])
      render 'static_pages/home'
    end
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content, :image)
    end
 end
```
一度画像がアップロードされれば、micropostパーシャルの`image_tag`ヘルパーを用いて、関連付けられた`micropost.image`を描画（レンダリング）できるようになります。また、投稿は画像を添付されていたり（画像の無いテキストのみ等）、されていなかったりするため、`attached?`という論理値を返すメソッドを使って、画像が添付されている投稿なのか、添付されていない投稿かを調べ、画像が添付されていたら、画像を表示するようにします。

```ruby:app/views/microposts/_micropost.html.erb
 <%= image_tag micropost.image if micropost.image.attached? %>
```

# 画像の検証

上記のアップローダーには、いくつかの目立つ欠点があります。特に、アップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまいます。この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装します。
Active Storageは、こうしたフォーマット機能やバリデーション機能がネイティブでサポートされていません。なのでActive Storageバリデーション用のgemを追加します。

```ruby:Gemfile
gem 'active_storage_validations'
```

忘れずに、bundle installを実行します。

```
$ bundle install
```
このgemのドキュメントを読んでみると、次のように`content_type`を検査することで画像をバリデーションできることがわかります。

```ruby
content_type: { in: %w[image/jpeg image/gif image/png],
                message: "must be a valid image format" }
```

これにより、サポートする画像フォーマットに対応する画像をチェックします。


同様に、ファイルサイズも以下のようにバリデーションできます。

```ruby
size: { less_than: 5.megabytes,
        message: "should be less than 5MB" }
```

今回は、画像のサイズを５MBに制限します。
これらのバリデーションをMicropostモデルに追加します。

```micropost.rb
class Micropost < ApplicationRecord
 validates :image,   content_type: { in: %w[image/jpeg image/gif image/png],
                                      message: "must be a valid image format" },
                      size:         { less_than: 5.megabytes,
                                      message: "should be less than 5MB" }
end
```

また、これらのバリーデーションに加え、クライアント側（つまりブラウザ）にも、画像アップロードのサイズやフォーマットをチェックする仕組みを追加します。`JavaScript`（具体的には`jQuery`）をちょっぴり加えて、ユーザーがアップロードしようとする画像が巨大過ぎるときにアラートを表示してみましょう（こうしておけばユーザーがアップロードで無駄な時間を使わずに済みますし、サーバーの負荷も軽くなります）。

```ruby:app/views/shared/_micropost_form.html.erb
<script type="text/javascript">
  $("#micropost_image").bind("change", function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert("Maximum file size is 5MB. Please choose a smaller file.");
      $("#micropost_image").val("");
    }
  });
</script>
```
これにより、もしファイルサイズが大きすぎた場合、`alert`メソッドで警告を出します。

最後に、`accept`パラメータを`file_field`入力タグで用いれば、有効なフォーマットでないとアップロードできないことをユーザーに伝えられるようになります。

```ruby:app/views/shared/_micropost_form.html.erb
<%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
```

最初に有効な画像フォーマットだけを選択可能にしておき、それ以外のファイルタイプを灰色で表示します。


# 画像のリサイズ

画像をリサイズするためには、画像を操作するプログラムが必要になります。今回は`ImageMagick`というプログラムを使うので、これを開発環境にインストールします。（本番環境が`Heroku`であれば、既に本番環境で`ImageMagickが使えるようになっています。）

クラウドIDEでは、次のコマンドでこのプログラムをインストールできます。

```
$ sudo apt-get -y install imagemagick
```

クラウドIDEや同等のLinux環境を使っていない場合、それぞれの環境に応じてImagiMagickをインストールする手順が変わります。例えばMacの場合であれば、Homebrewを導入しておかないとbrew install imagemagickコマンドでインストールできません。

Homebrewの場合のインストール方法

```
$ brew install imagemagick
```

次は、画像処理のためにいくつかのgemが必要です。
`image_processing gem`や、Ruby製ImageMagickプロセッサである`mini_magick gem`などを必要とします。

```ruby:Gemfile
gem 'image_processing'
gem 'mini_magick'
```

```
$ bundle install
```

おそらく、インストール後に`Railsサーバー`の再起動が必要になると思います。

# variatメソッド

Active Storageが提供する`variant`メソッドで変換済み画像を作成できるようになります。特に`resize_to_limit`オプションを用いて、以下のように画像の幅や高さが500ピクセルを超えることのないように制約をかけます。

```
image.variant(resize_to_limit: [500, 500])
```

このコードは別途display_imageメソッドに置いて利便性を高めておきます。

```ruby:micropost.rb
# 表示用のリサイズ済み画像を返す
def display_image
 imge.variant(resize_to_limit: [500, 500])
end
```

これでdisplay_imageをmicropostパーシャルで使えるようになります。

```ruby:app/views/microposts/_micropost.html.erb
<%= image_tag micropost.display_image if micropost.image.attached? %>
```