Rails5 Active Storageを使って画像アップロード機能を実装する

ファイルアップロード機能の実装方式

Rails5.1以前においては以下のgemを使用するのが有力な選択肢だった。

Rails 5.2になって、Rails本体の機能としてActive Storageが追加されたことで、これが上記の選択肢に加わった。

Active Storage

rails/activestorage at master · rails/rails · GitHub

概要はこちらのRails Guideを参照されたい。
Active Storage Overview — Ruby on Rails Guides

で、どれを使えばいいのか?

Active Storageはビルトインならではの簡便さが魅力であるものの、CarrierWaveやShrineと比較すると、まだ機能的に未成熟のようだ。(自分で触ったわけではないが)

なので要件に照らして、Active Storageで事足りる、あるいは機能不足を許容できるのであれば、お手軽に使えるActive Storageを使えばよいし、そうでなければCarrierWaveやShrineを選ぶことになるだろう。

今後の変化の激しさも考えると、現時点でActive Storageを採用するのは悪手な気もするのだが、 今回はお試しということでActive Storageを使ってみることにした。

環境

  • Rail 5.2.1
  • Active Storage 5.2.1
    ファイルアップロード機能実装に必要
  • MiniMagick 4.9.2
    画像の加工に必要
  • ImageMagick 6.7.8.9-15
    MiniMagickが使用。OSにインストールする。

Active Storageを利用するにはRails 5.2.0以上にアップデートしなければならない。
5.1.5からのアップデート方法であれば、こちらの記事に非常にわかりやすくまとまっている。 qiita.com

画像アップロード機能の要件

  • 1つのモデルレコードに対し、複数の画像を添付できる
  • オリジナルサイズだけでなく、指定のサイズでリサイズされたサムネイルを生成する
  • バリデーションする
    • file type: jpegpngのみ許可

実装

以下、rails newで新規プロジェクトを作成した直後の状態を想定する。

MiniMagickのgemを追加

Gemfileに以下を追記

# Gemfile
gem 'mini_magick', '~> 4.8'
$ bundle install

Active Storageが使用するtableを作成

Active Storageはファイル添付機能の実装にあたり、添付先のモデルにカラムの追加を必要としない。代わりにBlobとAttachmentの2つのモデルを用いる。

  • Blob(Binary Large Object):
    ファイルのメタ情報を管理。ファイルの実態はローカルファイルシステムまたはS3などのクラウドサービスに格納される。Blobが管理する情報は以下。
    • id
    • key
    • filename
    • content_type
    • metadata
    • byte_size
    • checksum
    • created_at
  • Attachment:
    添付先のモデルとBlobを紐づける中間テーブルとしての役割を担う。Attachementが管理する情報は以下。
    • id
    • name
    • record_type
    • record_id
    • blob_id
    • created_at

上記2つのモデルを以下の2コマンドで作成できる。

# migrationファイルの作成
$ rails active_storage:install
# テーブルの作成
$ rails db:migrate

scaffoldでmodel, view, controller等を作成

$ rails g scaffold post title body:test

routeの追加

# config/routes.rb
root to: 'posts#index'

modelの編集

# app/model/post.rb
has_many_attached :images
validate :image_type

def thumbnail input
  return self.images[input].variant(resize: '300x300!').processed
end

private

def image_type
  if images.each do |image|
    if !image.content_type.in?(%('image/jpec image/png'))
      errors.add(:images, 'needs to be a JPEG or PNG')
    end
  end
end
  • validate :image_type , def image_type ファイルタイプでバリデーションを行う。今回はjpegpngファイルのみアップデートを許可し、それ以外はerrorを返す。
  • thumbnailメソッド:
    アップロードされた画像をオリジナルサイズで扱うのではなく、リサイズしてサムネイルとして表示するためのメソッド。processedを使用することで、毎回リサイズの処理を走らせずに済む。
    • input(引数):
      複数画像を添付可能とするため、何枚目の画像かをinputという引数で指定できるようにしておく。(さらにいえば、リサイズ処理におけるサイズ指定を変数化して、第二引数、第三引数で指定できるようにしておくとよい。本例では省略。)
    • resize: '300x300!':
      リサイズのサイズ指定。"!"を付けるとオリジナル画像のアスペクト比を無視して、指定されたサイズによるリサイズを強制する。

controllerの編集

# app/controller/posts_controller.rb

# privateメソッドのpost_paramsを修正
  def post_params
    params.require(:post).permit(:title, :body, images: [])
  end

viewの編集

_formページ

viewのフォームに画像アップロード用のフィールドを追加する。
複数アップロードを可能にしたいので、multiple: trueが必要。

# app/view/posts/_form.html.erb
<%= form.label :images %>
<%= form.file_field :images, multiple: true %>
showページ
# app/vie/posts/show.html.erb
<% (0...@post.images.count).each do |image| %>
  <%= image_tag(@post.thumbnail(image) %>
<% end %>

参考

www.youtube.com