今回はRails5.2から標準搭載のファイルアップローダーであるActiveStorageを使い、画像アップロード機能を実装しましたのでまとめです。 AWS S3などのクラウドストレージへのアップロードも設定で手軽に実装できるとのことですが、今回はクラウドストレージへのアップロードは実装しておりません。
学んだこと✏️
1. Active Storageをセットアップ
以下を実行し、ActiveStorageでアップロードファイルを管理するテーブルのmigrationファイルを生成。
$ rails active_storage:install
db/migrate/[timestamp]_create_active_storage_tables.active_storage.rb
が作成されるので$ rails db:migrate
を実行。
rails db:migrate
で生成されるテーブルactive_storage_blobs
テーブル...アップロードされたファイル自体のメタデータを格納。(ファイルの名前、種類、サイズ、SHA256ハッシュ、作成日時、更新日時など) Active Storageはこのテーブルに格納されたメタデータを使用し、アプリ内でのファイルのアップロード、ダウンロード、削除などを処理。ファイル自体はActive Storageが対応するストレージサービスにアップロードされ、そのURLがactive_storage_blobsテーブルに保存される。その為、アプリから容易にファイルを取り扱える。active_storage_attachments
テーブル...Active Storageの中で、実際のアップロードされたファイルの保存場所を示すために使用。アップロードされたファイルをどのレコードにアタッチしたかを記録。
例)ユーザーが投稿した画像を、Postモデルに紐付ける場合、active_storage_attachments
テーブルには、Postモデルの id と、アップロードされた画像のidが保存される。
blob_id
カラムはactive_storage_blobs
テーブルに保存されているアップロードされたファイルのメタデータを参照するために使用。
つまり、アップロードされたファイルと、それがどのレコードにアタッチされたかを追跡するために使用される。このテーブルの情報を使い、アプリケーション内でアップロードされたファイルを管理することができる。
- アタッチ【attach】...システム上で何らかの主体に対象を取り込んで有効にする動作や操作。
2.モデルにActiveStorageの関連付けを追加
各userにavatar画像を持たせたい場合、以下のようにUser
モデルを定義。
has_one_attached
...レコード1件ごとに1個のファイルを添付できる。has_many_attached
...レコード1件ごとに、多数の添付ファイルを添付できる。
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_one_attached :avatar end
以下でavatar画像をアップロード。
<div class="field"> <%= f.label :avatar %> <%= f.file_field :avatar %> </div>
以下でavatar画像を表示。
<%= image_tag user.avatar if user.avatar.attached? %>
- 生成されたビューの送信ボタンのすぐ上に
file_field
を追加。アップロードした画像をimage_tag
で表示。attached?
で存在チェック。
3.バリアントプロセッサをVipsへ変更
画像を処理するための機能を提供する仕組み。▶︎バリアントプロセッサ
MiniMagick
...ImageMagickと呼ばれる画像処理ツールをRubyから使用するためのライブラリであり、簡単にリサイズ、トリミング、変換などの画像操作ができる。Vips
...MiniMagickよりも高速で、メモリ使用量が少なく、大規模な画像処理をより効率的に行うことができます。MiniMagick
とVips
は画像処理ツール。ActiveStorage
はファイル管理フレームワーク。ActiveStorage
はMiniMagick
またはVips
などの画像処理ツールを使用して、アップロードされた画像を処理することができる。
Active StorageのデフォルトのバリアントプロセッサはMiniMagickの為、Vipsを指定。
画像プロセッサをVipsに切り替えるには、config/application.rb
に以下を追加。
# 別の画像プロセッサとしてVipsを使う config.active_storage.variant_processor = :vips
4. 画像をリサイズ
Gemfile
のコメントを解除。
# Use Active Storage variant gem 'image_processing', '~> 1.2'
image_processinggemを使用するため、macにimagemagick
とvips
をインストール。
$ brew install imagemagick vips
ビューで、画像を表示するために、variant
メソッドを使用。これで添付ファイルごとに特定のサイズ違いの画像を生成できる。
resize_to_fill
、resize_to_limit
やresize_to_fit
などのオプションがある。適切なオプションを選択して、画像をリサイズする。
<%= image_tag user.avatar.variant(resize_to_fit: [50, 50]) if user.avatar.attached? %>
resize_to_fit
...image_processing gem
が提供するActiveStorageの画像リサイズオプションの一つで、指定したサイズに画像を縮小することができる。
resize_to_fitは、指定したサイズ内に収まるように画像を比率を維持しながらリサイズするため、画像の横幅または縦幅が指定したサイズに合うようにリサイズされる。
元の画像のアスペクト比を維持しながらリサイズするため、画像の横幅または縦幅が指定したサイズよりも小さい場合は、元の画像のサイズのままになる。しかし、横幅または縦幅が指定したサイズよりも大きい場合は、指定したサイズに合わせてリサイズされる。
画像の比率を維持しながら指定したサイズに収まるようにリサイズするため、元の画像の一部が切り落とされる場合がある。また、縦横比が指定したサイズと異なる場合は、余白が追加されることになる。
補足:Active Storageを使用する際のログの情報
Started POST "/posts" for 127.0.0.1 at 2022-02-25 09:30:00 +0900 Processing by PostsController#create as HTML Parameters: {"post"=>{"title"=>"Example Post", "image"=>#<ActionDispatch::Http::UploadedFile:0x0000000000000000 @tempfile=#<Tempfile:/tmp/RackMultipart20220225-1234-5678abcdef>, @original_filename="example.jpg", @content_type="image/jpeg">}} ... ActiveStorage::Blob Create (0.1ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?) [["key", "example.jpg"], ["filename", "example.jpg"], ["content_type", "image/jpeg"], ["byte_size", 123456], ["checksum", "0123456789abcdef"], ["created_at", "2022-02-25 09:30:01.000000"]] ... ActiveStorage::Attachment Create (0.1ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?) [["name", "image"], ["record_type", "Post"], ["record_id", 1], ["blob_id", 1], ["created_at", "2022-02-25 09:30:02.000000"]] ... Redirected to http://localhost:3000/posts/1 Completed 302 Found in 123ms (ActiveRecord: 1.0ms | Allocations: 4567)
- リクエストが開始された時間とIPアドレス
- コントローラーのアクションが処理される際に、送信されたパラメーター
- Active Storageが、アップロードされたファイルに対して行った操作の詳細(例:Blobオブジェクトの作成など)
- Active Storageが、アップロードされたファイルを保存した際に生成されたAttachmentオブジェクトの詳細
- リダイレクト先のURLと、リクエストが完了した際の処理時間とロケーションの数
5. N+1問題
- Active Recordを使用する際に発生するパフォーマンスの問題。 Active Recordは、データベースから複数のレコードを取得するために使用。しかし、N+1問題は、複数のレコードを取得する際に、必要以上にデータベースにアクセスし、パフォーマンスを低下させる問題。 1つの親レコードに関連する複数の子レコードを取得する場合に発生する。この場合、最初に親レコードを取得し、その後に子レコードを取得する必要がある。しかし、Active Recordがデフォルトで使用する方法では、1つの親レコードを取得するために1回のデータベースクエリを実行し、その後、それぞれの子レコードを取得するためにさらにN回のデータベースクエリを実行する必要がある。合計N+1回のデータベースクエリを実行することになる。
→解決方法:with_attached_属性名
スコープを使用することでN+1問題を回避しながら、関連するファイルを一度のクエリで取得できる。
app/controllers/users_controller.rb
でwith_attached_avatar
とすることでN+1問題を回避する。
class UsersController < ApplicationController def index @users = User.with_attached_avatar.order(:id).page(params[:page]) end : end
まとめ
他にも、active_storage_validations gemを使用し、アップロード可能なファイルの種類を限定する方法などがあります。時と場合に応じて使い分けられるようにしていきたいです。