[Rails]Active Storageによる画像アップロード
はじめに
Active Storageを使って画像のアップロードを実装していきます。
環境
Rails 7.0.4.3
ruby 3.2.1
Active Storageとは
Active Storageは、Railsの一部として提供されるファイルアップロードとストレージのためのライブラリです。Rails 5.2から導入され、ファイルのアップロードと管理を簡単に実装することができるようになりました。
Active Storageを使うと、以下のような機能を簡単に実現できます:
-
ファイルアップロード: ユーザーがWebアプリケーションにファイルをアップロードできます。一般的な用途として、画像のアップロードやPDFファイルのアップロードなどがあります。
-
ストレージの管理: Active Storageは、アップロードされたファイルをローカルファイルシステムやAmazon S3、Google Cloud Storageなどのクラウドストレージに簡単に保存できます。これにより、大容量のファイルをアプリケーションサーバーから切り離し、パフォーマンスの向上やスケーラビリティの向上が期待できます。
-
アクティブストレージの機能: Active Storageはファイルのアップロードとストレージ管理だけでなく、画像のリサイズや回転などの画像処理もサポートしています。これにより、アプリケーション内で簡単に画像の縮小や切り抜きを行うことができます。
Active Storageは、Railsの他のコンポーネントとシームレスに連携し、アプリケーション内でのファイル操作を簡単にすることができます。たとえば、Active Recordモデルと関連付けてファイルを保存したり、ビューでファイルを表示したりすることができます。アプリケーションでファイルアップロードや画像処理が必要な場合は、Active Storageを使うことでこれらの機能を効率的に実装することができます。
Active Storageをインストールする
bin/rails active_storage:install
Copied migration 20230729155113_create_active_storage_tables.active_storage.rb from active_storage
bin/rails db:migrate
== 20230729155113 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {:id=>:primary_key})
-> 0.0401s
-- create_table(:active_storage_attachments, {:id=>:primary_key})
-> 0.0148s
-- create_table(:active_storage_variant_records, {:id=>:primary_key})
-> 0.0398s
== 20230729155113 CreateActiveStorageTables: migrated (0.0949s) ===============
image_processing
をインストールする
Gemfileにコメントアウトされたgemをアンコメントしインストールします。
image_processing
は、Railsにおいて画像処理を行うためのGemです。これを使用することで、画像のリサイズ、トリミング、回転、フィルター処理などを簡単に実装できます。
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
gem "image_processing", "~> 1.2"
bundle install
image_processing
はmini_magick
とvips
を使ってます。
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
-
mini_magick
: Rubyで画像処理を行うためのGemです。画像のリサイズ、トリミング、フィルター処理など、様々な画像操作を行うことができます。ImageMagickという画像処理のライブラリをラップしており、RubyからImageMagickの機能を利用できるようにしています。 -
vips
: VIPSは、高速でメモリ効率が高い画像処理ライブラリです。mini_magick
と同様に、画像のリサイズ、回転、フィルター処理などを行うことができます。mini_magick
よりも高速であり、大量の画像を効率的に処理する際に特に有用です。
Rails.application.configure do
# デフォルト
config.active_storage.variant_processsor = :vips
config.active_storage.variant_processsor = :mini_magick
Active Storageでは、バリアントプロセッサとしてVipsまたはMiniMagickを利用できます。デフォルトで使われるバリアントプロセッサはconfig.load_defaultsのターゲットバージョンに依存し、config.active_storage.variant_processorで変更できます。
<!-- MiniMagick -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
<!-- Vips -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
mini_magick
をインストールする
brew install imagemagick
vips
をインストールする
brew install libvips
blogモデルにアソシエーションを追加する
class Blog < ApplicationRecord
...
has_one_attached :image
# 複数のイメージを持たせる
has_many_attached :images
end
has_one_attachedマクロは、レコードとファイルの間に1対1のマッピングを設定します。レコード1件ごとに1個のファイルを添付できます。
imageにバリデーションを追加する
アップロードできるファイル形式を指定し、それ以外の場合エラーメッセージを表示させるようにします。
ファイルサイズにも上限を追加します。
class Blog < ApplicationRecord
...
has_one_attached :image
validate :image_content_type
validate :image_size
def image_content_type
if image.attached? && !image.content_type.in?(%w[image/jpeg image/png image/gif])
errors.add(:image, ':ファイル形式が、JPEG, PNG, GIF以外になってます。ファイル形式をご確認ください。')
end
end
def image_size
if image.attached? && image.blob.byte_size > 1.megabytes
errors.add(:image, ':1MB以下のファイルをアップロードしてください。')
end
end
end
ストロングパラメーターを追加する
class BlogsController < ApplicationController
...
private
def blog_params
params.require(:blog).permit(:title, :content, :image, images: [])
end
end
フォームにアップローダーフィルドを追加する
<div class="my-3">
<%= form.label :image%><br>
<%= form.file_field :image%>
</div>
# 複数のイメージを持たせる
<div class="my-3">
<%= form.label :images%><br>
<%= form.file_field :images, multiple: true %>
</div>
ビューに画像を表示させる
<% if blog.image.attached? %>
<div><%= image_tag(blog.image) %></div>
<% end %>
# 複数のイメージを持たせる
<% if blog.images.attached? %>
<% blog.images.each do |image| %>
<div><%= image_tag(image) %></div>
<% end %>
<% end %>
image_tag
が必要です。ないとこうなります:
イメージをリサイズする
フルサイズのイメージをそのまま使うとロードに時間かかりますのでサムネバージョンを作ります。
リサイズメソッドを作成する
image
をそのまま使うよりimage_as_thumbnail
メソッドを作成し画像のリサイズ処理を設定します。
複数のイメージを持たせる場合each
文が必要です。
class Blog < ApplicationRecord
...
def image_as_thumbnail
return unless image.content_type.in?(%w[image/jpeg image/png])
image.variant(resize_to_limit: [200, 100]).processed
end
end
可能であれば、
content_type
:オプションも指定しておきましょう。Active Storageは、渡されたデータからファイルのContent-Typeの判定を試みますが、判定できない場合は指定のContent-Typeにフォールバックします。
resize_to_limit
は、image_processing
Gemにおける画像処理のオプションの一つです。このオプションを使用すると、画像のサイズを指定した範囲内に収めるようにリサイズできます。
ビューにサムネを使う
<% if blog.image.attached? && blog.image.content_type.in?(%w[image/jpeg image/png]) %>
<div><%= image_tag(blog.image_as_thumbnail) %></div>
<% end %>
プレビューを追加する
ファイル名しか表示されないので分かりずらいですね。
画像のプレビューも追加していきます。
プレビューコントローラーを作成する
bin/rails generate stimulus previews
create app/javascript/controllers/previews_controller.js
プレビュー用パーシャルを作成する
新規ブログを作成する場合アップロードされた画像を表示させ、ブログを編集する場合既存の画像を表示させるようにif
文を追加します。
<div class="my-3" data-controller="previews">
<%= form.label :image%><br>
<%= form.file_field :image,
direct_uploade: true,
data: { previews_target: "input", action: "change->previews#preview" } %>
<% if blog.image.attached? %>
<%= image_tag blog.image, data: { previews_target: "preview" } %>
<% else %>
<%= image_tag "", data: { previews_target: "preview" } %>
<% end %>
</div>
パーシャルを読み込む
<%= render 'blogs/image_preview', form: form, blog: @blog %>
jsを作成する
アップロードされた画像をpreviews_target
に表示させます。
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="previews"
export default class extends Controller {
static targets = ["input", "preview"]
connect() {
}
preview() {
let input = this.inputTarget;
let preview = this.previewTarget;
let file = input.files[0];
let reader = new FileReader();
reader.onloadend = function(){
preview.src = reader.result;
};
if(file) {
reader.readAsDataURL(file);
} else {
preview.src = "";
}
}
}
プレビューを表示されましたね。
画像を削除する
アップロードした画像を削除したいこともあるので削除機能を追加していきます。
delete_image
アクションを作成する
class BlogsController < ApplicationController
...
def delete_image
@blog = Blog.find(params[:id])
@blog.image.purge
respond_to do |format|
render turbo_stream: turbo_stream.remove(@blog.image)
end
end
end
添付ファイルをモデルから削除するには、添付ファイルに対して
purge
を呼び出します。Active Job
を使うようにアプリケーションが設定されている場合は、バックグラウンドで削除を実行できます。purge
すると、blobとファイルがストレージサービスから削除されます。
# avatarと実際のリソースファイルを同期的に破棄します。
user.avatar.purge
# Active Jobを介して、関連付けられているモデルと実際のリソースファイルを非同期で破棄します。
user.avatar.purge_later
アクションに対応するURLを追加する
Rails.application.routes.draw do
...
resources :blogs do
member do
delete 'delete_image'
end
end
end
ビューに削除ボタンーを追加する
<% if blog.image.attached? %>
<%= link_to '画像を削除する', delete_image_blog_path(blog),
data: { turbo_method: :delete },
class: 'bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow' %>
<%= turbo_frame_tag dom_id(blog.image) do %>
<%= image_tag blog.image, data: { previews_target: "preview" } %>
<% end %>
<% end %>
削除されたことを確認します。
16:56:32 web.1 | [ActiveJob] [ActiveStorage::PurgeJob] [6c1bd72e-dd32-4814-98e2-30673761327d] ActiveStorage::Blob Destroy (0.7ms) DELETE FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1 [["id", 8]]
16:56:32 web.1 | [ActiveJob] [ActiveStorage::PurgeJob] [6c1bd72e-dd32-4814-98e2-30673761327d] TRANSACTION (0.4ms) COMMIT
16:56:32 web.1 | [ActiveJob] [ActiveStorage::PurgeJob] [6c1bd72e-dd32-4814-98e2-30673761327d] Disk Storage (0.2ms) Deleted file from key: vrvs1x21uuntwpdu3pl1lfrf579r
16:56:32 web.1 | [ActiveJob] [ActiveStorage::PurgeJob] [6c1bd72e-dd32-4814-98e2-30673761327d] Disk Storage (0.1ms) Deleted files by key prefix: variants/vrvs1x21uuntwpdu3pl1lfrf579r/
16:56:32 web.1 | [ActiveJob] [ActiveStorage::PurgeJob] [6c1bd72e-dd32-4814-98e2-30673761327d] Performed ActiveStorage::PurgeJob (Job ID: 6c1bd72e-dd32-4814-98e2-30673761327d) from Async(default) in 27.62ms
終わり
Active Storageによる画像アップロードでした。
過去にCarrierWave
gemを使った画像アップロード機能についても実装しましたのでご参考いただけますと幸いです。
Discussion