Help us understand the problem. What is going on with this article?

【Rails 5.2】 Active Storageの使い方

Active Storageについて調べると、「軽く触ってみた」ノリの記事ばかりで、特に複数のファイルを扱う方法などがなかなか見つからなかったので、使い方のまとめ記事を作りました。:muscle:

Active Storage とは

Active Storageは、Rails5.2で追加された、ファイルアップロードを行うための機能です。これを使えば、フォームで画像の投稿機能などが簡単に作れます。また、Amazon S3, Google Cloud Storage, Microsoft Azure Storageなどのクラウドストレージサービスに対するファイルのアップロードを簡単に行うことができます。クラウドストレージの他に、ローカルディスクにファイルを保存することもできます。
Paperclip, Carrierwave, Dragonflyなどのgemの代わりになるとされていますが、現時点ではバリデーションとキャッシュ関連の機能は残念ながら備えていません。使うにはRails 5.2以上にアップグレードする必要があります。

Active Storage を使えるようにする

$ rails active_storage:install
$ rails db:migrate

ここは気になる方だけ読んでいただければ大丈夫なのですが、このマイグレーションによってactive_storage_blobsactive_storage_attachmentsという名前のテーブルが2つ生成されます。これらのテーブルはBlobAttachmentの2つのモデルが使います。Blobはファイル名、ファイルの種類、バイト数、誤り検出符号などのメタデータを保持するモデルで、Attachmentモデルは、BlobオブジェクトとActive Recordオブジェクトを紐付けるための中間テーブルです。なお、Active Storageを使う際、直接BlobAttachmentモデルに触れる必要はありません。(時間返せ)

Active Record モデルを用意する

コメントに1枚、もしくは、複数枚の画像を添付できるようにしたいとしましょう。まずはCommentモデルを用意する必要があります。generate resourceコマンドで基本的なルートと、モデル、空のコントローラを生成しましょう。

$ rails g resource comment content:text
$ rails db:migrate

ここで、画像用のカラムを用意する必要がない点もActive Storageの特徴の一つです。

1つの添付ファイルの場合

Commentモデルに1つの画像を添付するには、has_one_attachedを使います。

class Comment < ApplicationRecord
  has_one_attached :image
end

:imageはファイルの呼び名で、:photo:avatar:hogeなど、ファイルの用途に合わせて好きなものを指定してください。ここで、Imageモデルなどを作る必要はないです。Active Storageは裏側でBlobAttachmentモデルを使って、こそこそとcomment.imageを使えるようにしてくれます。(有能すぎ)

では、コントローラとビューの中身を書きましょう。

comments_controller.rb
class CommentsController < ApplicationController
  def new
    @comment = Comment.new
  end

  def create
    @comment = Comment.create params.require(:comment).permit(:content, :image) # POINT
    redirect_to @comment
  end

  def show
    @comment = Comment.find(params[:id])
  end

  def edit
    @comment = Comment.find(params[:id]) 
  end

  def update
    @comment = Comment.find(params[:id])
    @comment.update params.require(:comment).permit(:content, :image) # POINT
    redirect_to @comment
  end
end
new.html.erb
<%= form_with model: @comment, local: true  do |form| %>
  <%= form.text_area :content %><br>
  <%= form.file_field :image %><br>
  <%= form.submit %>
<% end %>
show.html.erb
<% if @comment.image.attached? %>
  <%= image_tag @comment.image %>
<% end %>

ほとんど典型的なコードですね。ポイントはcreateupdateアクションのところで、このようにして、imageがあたかもCommentのカラムであるかのように扱うことで、フォームのfile_fieldで選択された画像をCommentオブジェクトと紐付けます。これも、Active Storageの特徴の一つです。

createupdateアクションを使えない場合は、@comment.image.attach(params[:comment][:image])で画像を後からCommentオブジェクトと紐付けることもできます。

例えば、画像の選択を任意にしたい場合は以下のようなコードになります。

@comment = Comment.create params.require(:comment).permit(:content)
if image = params[:comment][:image]
  @comment.image.attach(image)
end

画像を表示するにはimage_tag@comment.imageを渡すだけです。

結果はこのような感じになります。
ezgif-2-b17e951b9f.gif

複数の添付ファイルの場合

複数の添付ファイルを使いたい場合は、上記のコードを少しだけ変更します。

  • has_one_attachedの代わりにhas_many_attachedを使う
  • comment.imageの代わりにcomment.imagesを使う
  • file_fieldmultiple: trueを追記して、複数ファイルの選択を許可
comment.rb
class Comment < ApplicationRecord
  has_many_attached :images
end
comments_controller.rb
class CommentsController < ApplicationController
  def new
    @comment = Comment.new
  end

  def create
    @comment = Comment.create params.require(:comment).permit(:content, images: [])
    redirect_to @comment    
  end

  def show
    @comment = Comment.find(params[:id])
  end

  def edit
    @comment = Comment.find(params[:id]) 
  end

  def update
    @comment = Comment.find(params[:id])
    @comment.update params.require(:comment).permit(:content, images: [])
    redirect_to @comment
  end
end
new.html.erb
<%= form_with model: @comment, local: true  do |form| %>
  <%= form.text_area :content %><br><br>
  <%= form.file_field :images, multiple: true %><br>
  <%= form.submit %>
<% end %>
show.html.erb
<% if @comment.images.attached? %>
  <% @comment.images.each do |image| %>
    <%= image_tag image %> <br>
  <% end %>
<% end %>

一枚の時同様、@comment.images.attach(params[:comment][:images])も使えます。

結果はこちら
ezgif-2-e15b099da1.gif

ファイルの保存先の変更

ファイルの保存先は、各環境の設定ファイルに記載します。
まずは、 config/environments/development.rbproduction.rb の中身を覗いてみましょう。

config/environments/development.rb
  # ...

  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :local

  # ...
config/environments/production.rb
  # ...

  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :local

  # ...

初期状態では、開発環境(development)、本番環境(production)ともに保存先は :local に設定されています。
この local とは、 config/storage.yml で定義された保存先の名前です。
これを変更するには、:local のところを :amazon, :google, :microsoft のいづれかと置き換え、config/storage.ymlの方に、必要な認証情報などの値を入力します。

今度は、config/storage.ymlの中身を覗いてみましょう。

config/storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket

# Remember not to checkin your GCS keyfile to a repository
# google:
#   service: GCS
#   project: your_project
#   credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
#   bucket: your_own_bucket

# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
#   service: AzureStorage
#   storage_account_name: your_account_name
#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
#   container: your_container_name

先ほど見た保存先の local は、使用するサービスが Disk (ローカルディスク)に設定れていて、railsアプリ直下の/storageディレクトリがファイルの保存先に指定されています。

あと、親切に、コメントがたくさんついてますね。この中から適切なところのコメントを解除することで、好きなストレージサービスを使うことができます。

また、お使いのサービスのgemをGemfileに追記する必要があります。これは、aws-sdk-s3, google-cloud-storage, azure-storageのいづれかになります。


なお、アクセスキーは、セキュリティ上、Rails Credentialsを使って入力するのが好ましいです。
これは、APIキーなどの機密情報を暗号化して保存するための機能で、実はこれもRails 5.2で追加された新機能です。これまでのsecrets.ymlの代わりになります。
<%= Rails.application.credentials.dig(...) %>の部分は、まさにCredentialsに入力されたデータを読み込んでいますのでコメントをそのまま使いましょう。

  • Credentialsにデータを入力・編集するには $ rails credentials:edit と叩きます。
    エラーが出た場合は、$ EDITOR=vim rails credentials:editで、お使いのエディターを指定してください。例: vim, emacs, atom, code, subl
  • 入力した内容はconfig/master.keyを用いて暗号化され、config/credentials.yml.encが生成されます。
  • 復号された中身は $ rails credentials:show で確認できます。

Amazon S3 を使う場合

S3は基本的に、ファイルをアップロードし、そのURLを取得できる、AWSのストレージサービスです。

🔰 Amazon S3でバケットを用意する方法

バケットは画像などのファイルをアップロードできる入れ物です。
AWSコンソール → サービス → ストレージ → S3 → 「バケットを作成する」

項目 入力・選択
パケット名 例: my-rails-app-bucket
リージョン 例: ap-northeast-1 (東京)
パブリックアクセス許可を管理する このバケットに読み取りアクセス権限をする
上記以外 全部デフォルトのまま

🔰 Amazon S3でアクセスキーを作る方法

AWSコンソール → サービス → セキュリティ → IAM → 「ユーザー」 → 「ユーザーを追加」

項目 入力・選択
ユーザー名 例: s3user
アクセスの種類 プログラムによるアクセス

「既存のポリシーを直接アタッチ」 → 「S3」で検索 → 「AmazonS3FullAccess」を選択 → 「次へ」 → 「ユーザーの作成」
表示された「アクセスキー ID」と「シークレットアクセスキー」 をメモ帳などにコピペして保管する。(一度しか表示されません)

保存先をAmazon S3に指定する

まず最初に必要なAWS S3のGemをインストールします。

Gemfile
gem "aws-sdk-s3", require: false

Gemfile変更後、$ bundle installと叩きます。

次に、保存先をAmazon S3に指定します。

config/environments/production.rb
# 本番環境(production)の保存先を:localから:amazonに変更

config.active_storage.service = :amazon
config/environments/development.rb
# 開発環境(development)でAmazon S3の動作を確認したい場合はこちらの方も変更しましょう

config.active_storage.service = :amazon
config/storage.yml
# 以下の部分をコメント解除する
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  # 以下の2行を変更
  region: ap-northeast-1 #東京の場合
  bucket: my-rails-app-bucket #自分で作成したS3のバケットの名前

前にも述べましたが、<%= Rails.application.credentials.dig(...) %>の部分は、Credentialsの情報を読み込んでいます。早速、CredentialsにAmazon S3へのアクセスキーを入力しましょう。

$ EDITOR=vim rails credentials:edit

上の例ではVimが開きます。Atomをお使いの場合、EDITOR=atomとしてください。
VSCodeなら、EDITOR=code、SublimeTextならEDITOR=subl

aws:
 access_key_id: 123 #ここに自分のアクセスキーIDをコピペ
 secret_access_key: 456 #ここに自分のシークレットアクセスキーをコピペ

:bulb: Vim: iで入力開始、escZZで終了

入力した内容は$ rails credentials:showで確認できます。

以上で保存先をAmazon S3に変更できました。:tada:

:warning: 重要

Credentialsをお使いの場合は、config/credentials.yml.encの中身を復号化するために必要なconfig/master.keyファイルを本番環境にも配置する必要があります。しかし、master.keyはセキュリティ上、Gitで管理してはいけないため、標準で .gitignore されてあり、PaaSなどのGitリポジトリをそのままデプロイする本番環境(Herokuなど)の場合は、master.key の中身をコピーして、環境変数 RAILS_MASTER_KEY として用意する必要があります。

ちなみに、Herokuをお使いの場合は $ heroku config:set RAILS_MASTER_KEY=123で環境変数を用意できます。

参考

https://www.engineyard.com/blog/active-storage
https://qiita.com/yatmsu/items/08b95e837ac7f24fb443
https://afreshcup.com/home/2017/07/06/introduction-to-active-storage-part-3-google-cloud-storage

hmmrjn
社会人1年目でWebエンジニアやってます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした