LoginSignup
59
71

More than 3 years have passed since last update.

Rails 6 ActiveStorageを使用してS3に画像ファイルをアップロードし、取得した画像をリサイズして表示する方法

Last updated at Posted at 2019-12-15

はじめまして!スタートアップでサーバーサイドエンジニアをやっています。なかのです!

TechTrain Advent Calendar 2019(User ver)の15日目を担当します!
今回はタイトルの通り、Rails 6 ActiveStorageを使用してS3に画像ファイルをアップロードし、取得した画像をリサイズして表示する方法をお話ししたいと思います。

はじめに

まず、簡単にActiveStorageについて説明しますと

Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。
アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。

(https://railsguides.jp/active_storage_overview.html 参照)

開発環境は以下になります。

Ruby '2.6.5'
Rails '6.0.2'

準備

今回はUserに紐づく画像ファイルをアップロードしたいと思いますので、scaffoldを用いてさくっと必要な部分を作ります!

$ rails new activerecord-sample
$ cd activerecord-sample
$ rails generate scaffold user name:string
$ rails db:migrate
$ rails s

rails sでサーバーを立ち上げ、localhost:3000/usersにアクセスし、下の画像のような画面が表示されたらセットアップ完了です。

スクリーンショット 2019-12-14 21.17.12.png

S3にバケットを作成する

Amazon Simple Storage Service(Amazon S3)は、インターネット用のストレージサービスで、データ (写真、動画、ドキュメントなど)を保存しておくために利用されます。使用するためには事前にバケットを作成しておく必要があります。

S3のセットアップは以下の記事を参考にしてみてください。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/user-guide/create-configure-bucket.html

今回は以下のような設定でバケットを作成しました。

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

S3でアクセスキーを作る。
アクセスキーを作る方法は、こちらを参考にしてみてください。
https://tech-blog.s-yoshiki.com/2019/06/1292/

IAMユーザー作成後に表示された「アクセスキー ID」と「シークレットアクセスキー」 は後ほど使用しますので誰にも教えないように保管しておいてください。

ActiveStorageの導入

ここから実際にActiveStorageを導入していきたいと思います。

以下のコマンド実行してActiveStorageをinstallしてください。

$ rails active_storage:install
$ rails db:migrate

active_storage_attachmentsactive_storage_blobsというテーブルが作成されていれば、インストール成功です。

ActiveStorageは、初期設定ではDisk内(storage以下)にアップロードしたファイルデータを保存するようになっているため、amazon s3のストレージを使用する記述を追加します。

まずは使用するストレージの設定(今回だとamazon s3)を以下のファイルに追加してください。

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

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

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: <%= Rails.application.credentials.dig(:aws, :s3, :region) %>
  bucket: <%= Rails.application.credentials.dig(:aws, :s3, :bucket) %>

次に利用するサービスをActiveStorageに認識させます。

config/environments/development.rb
#ファイルをAmazon S3に保存する
config.active_storage.service = :amazon

S3のためのgemが必要なので、Gemfileに以下の記述を追加してbundle installしてください。

gem "aws-sdk-s3", require: false
$ bundle install

最後にS3の環境設定を追加します。
Rails6 から各環境でcredentialsの管理が出来るようになったので、以下のコマンドを入力し、development環境でのcredentialsファイルを作成して、S3の設定を追加してください。

$ ./bin/rails credentials:edit --environment development

こちらのコマンドを入力した際、もしcredentialsが作成されていなかったら新たにconfig/credentials以下にdevelopment.yml.encdevelopment.keyというファイルを作成してくれます。

以下のように編集してください。

development.yml.enc
aws:
  access_key_id: #先ほど取得したaccess_key_idをいれてください。
  secret_access_key: #先ほど取得したsecret_access_keyをいれてください。
  s3:
    region: ap-northeast-1
    bucket: activestorage-sample-bucket

編集が完了したら、値が取得出来ているか確認してみましょう。

$ rails c
irb(main):001:0> Rails.application.credentials.dig(:aws, :access_key_id)
=> "設定したaccess_key_id"
irb(main):002:0> Rails.application.credentials.dig(:aws, :secret_access_key)
=> "設定したsecret_access_key"
irb(main):003:0> Rails.application.credentials.dig(:aws, :s3, :region)
=> "ap-northeast-1"
irb(main):004:0> Rails.application.credentials.dig(:aws, :s3, :bucket)
=> "activestorage-sample-bucket"

値がちゃんと取れていれば、設定完了です。

ActiveRecordの実装

UserとAttachmentとBlobの関係を以下の図に表します。
スクリーンショット 2019-12-15 17.16.01.png

Userモデルに1つのファイルを紐づける場合

1ユーザーに1つの画像ファイルしか紐づかない場合、上記の図のNが1になります。まずはこのパターンを実装していきたいと思います。

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
end

Userモデルにavatarという属性を追加しました。今回はavatarという命名にしましたが、用途に合わせて自由に指定することが出来ます。
ActiveStorageでは、ファイルデータを保存するためにそれぞれのテーブルに個別にカラムを用意しなくても上記の記述を追加するだけでactive_storage_attachmentsactive_storage_blobsが裏側でよしなに処理してくれます。また、今回は1つのファイルを添付するためhas_one_attachedという記述をしています。

画像を投稿出来るようにフォームを追加します。

app/views/users/_form.html.erbのブロック内に以下の記述を追加してください。

app/views/users/_form.html.erb
<div class="field">
  <%= form.label :name %>
  <%= form.text_field :name %>
</div>

# 以下の部分を追加
<div class="field">
  <%= form.file_field :avatar %>
</div>

ストロングパラメーターもavatarを許可するようにします。

app/controllers/users_controller.rb
def user_params
  params.require(:user).permit(:name, :avatar)
end

最後にユーザー詳細画面で画像が表示されるようにしましょう。

app/views/users/show.html.erb
<p>
  <strong>Name:</strong>
  <%= @user.name %>
# 以下を追記
  <%= image_tag url_for(@user.avatar) %>
</p>

では、早速rails sをして、投稿してみましょう!
てす.gif

Userモデルに複数のファイルを紐づける場合

次は複数投稿投稿出来るようにしてみますz。

app/models/user.rb
class User < ApplicationRecord
  has_many_attached :images
end

今度はUserモデルにimagesという属性を追加しました。
userに複数の画像を紐づけるためにhas_many_attachedを使用しています。

画像を複数投稿出来るようにフォームを追加します。

app/views/users/_form.html.erbのブロック内に以下の記述を追加してください。

app/views/users/_form.html.erb
<div class="field">
  <%= form.label :name %>
  <%= form.text_field :name %>
</div>

# 以下の部分を追加
<div class="field">
  <%= form.file_field :images, multiple: true %>
</div>

multiple: trueにすることで画像を一度に複数選択出来るようになります。

ストロングパラメーターもimagesを許可するようにします。

app/controllers/users_controller.rb
def user_params
  params.require(:user).permit(:name, images: [])
end

最後に、ユーザー詳細画面で画像が表示されるようにします。

app/views/users/show.html.erb
<p>
  <strong>Name:</strong>
  <%= @user.name %>
  # 以下を追記
  <% @user.images.each do |image| %>
    <%= image_tag url_for(image) %>
  <% end %>
</p>

これで複数投稿が出来るようになったので、rails sをして試してみましょう!
てす2.gif

image_processingを用いたリサイズ

画像投稿した後は、画面サイズに合わせて画像を取得したくなりますよね。Rails6からimage_processingというgemを使用することが推奨されているため、そちらを使用してリサイズを行っていきます。
Gemfileimage_processingがコメントアウトされていると思いますので、アンコメントしてbundle install します。

gem 'image_processing', '~> 1.2'

また、ImageMagickをinstallする必要がありますので、Mac OSXを使用している方はbrew install imagemagickでinstallしてください。

これで設定は完了です。

リサイズして表示するように設定していきます。variantメソッドを用いることで簡単にリサイズを行うことが出来ます。先ほど、複数投稿時に使用したviewに追加して、表示を見てみます。

app/views/users/show.html.erb
<% @user.images.each do |image| %>
  <%= image_tag image.variant(resize_to_limit: [100, 100]) %>
<% end %>

スクリーンショット 2019-12-15 21.08.51.png

簡単にリサイズされましたね。

おまけ

ActiveStorageでよく使用するメソッドやValidationについて、軽く書いておきたいと思います。

まず、よく使用するメソッド

attached?メソッド
添付ファイルを持っているかどうかを調べます。

irb(main):001:0> user = User.last
=> #<User id: 20, name: "なかの", created_at: "2019-12-15 11:24:56", updated_at: "2019-12-15 11:24:56">
irb(main):002:0> user.images.attached?
=> true

irb(main):007:0> user2 = User.new
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):008:0> user2.images.attached?
=> false

purgeメソッド
Attach、Blob、S3から添付ファイルを削除します。

irb(main):009:0> user = User.last
=> #<User id: 20, name: "なかの", created_at: "2019-12-15 11:24:56", updated_at: "2019-12-15 11:24:56">
irb(main):010:0> user.images.attached?
=> true
irb(main):011:0> user.images.purge
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):012:0> user.images.attached?
=> false

こちらのメソッドはRollbackが起きた際にS3とデータの不整合が起こりやすいため、使用時は注意しなくてはいけません。ActiveRecordのトランザクションとActiveStorageについての話は、こちらの記事がとてもわかりやすいのでおすすめです。

https://tech.smarthr.jp/entry/2018/09/14/130139

detachメソッド
Attachから添付ファイルのレコードを削除します。

irb(main):001:0> user = User.last
=> #<User id: 21, name: "なかの", created_at: "2019-12-15 12:26:06", updated_at: "2019-12-15 12:26:06">
irb(main):002:0> user.images.attached?
=> true
irb(main):003:0> user.images.detach
irb(main):004:0> user.images.attached?
=> false

ActiveStorageでは専用のValidationが用意されていないため、各自で作成しなくてはいけません。
簡単にContent_Typeを確認するValidationを作ってみました。

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
  validate :validate_avatar

  def validate_avatar
    errors.add(:avatar, "画像データではありません。") unless image?
  end

  def image?
    return '' unless avatar.attached?

    %w[image/jpg image/jpeg image/png image/gif].include?(avatar.blob.content_type)
  end
end

おわりに

ActiveStorageを使ってみて、思ったより簡単に導入できるので、単純な画像投稿機能などにはオススメだと思いました。
ただ、デフォルトで署名付きURLを取得してしまうため、CDNなどを使用する際は少し工夫が必要になるかなと思いました。
次回書けたら書きます・・・。

59
71
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
71