如何使用 Rails Active Storage 实现文件上传 | has_one_attached·S3 支持
Active Storage 是 Rails 5.2 中内置的文件上传框架。它是替代 <code>CarrierWave</code> 和 <code>Paperclip</code> 的官方解决方案,可以在本地磁盘、AWS S3、Google Cloud Storage 和 Microsoft Azure 存储之间无缝切换。本文系统地介绍了 Active Storage 设置、<code>has_one_attached</code> 定义、验证、S3 配置(config/storage.yml)和图像调整大小(Variant)。
Active Storage 设置
要开始使用 Active Storage,使用 <code>rails active_storage:install</code> 生成迁移,然后用 <code>rails db:migrate</code> 应用。这将创建三个表:<code>active_storage_blobs</code>、<code>active_storage_attachments</code> 和 <code>active_storage_variant_records</code>。
# Active Storage のマイグレーションを生成
rails active_storage:install
# マイグレーションを実行
rails db:migrate
# 生成されるテーブル:
# active_storage_blobs — ファイルのメタデータ(ファイル名、サイズ、MIMEタイプなど)
# active_storage_attachments — モデルとblobの紐付け(ポリモーフィック関連)
# active_storage_variant_records — 変換済み画像のキャッシュ
# Gemfile
gem 'image_processing', '>= 1.2' # Variant(画像リサイズ)に必要
# gem 'mini_magick' # ImageMagick を使う場合
# gem 'ruby-vips' # libvips を使う場合(高速)
bundle install
# ImageMagick のインストール(Ubuntu/Debian)
sudo apt-get install imagemagick
# macOS(Homebrew)
brew install imagemagick vips
定义 has_one_attached 和 has_many_attached
<code>has_one_attached</code> 定义一对一文件附加,<code>has_many_attached</code> 定义一对多附加。只需将其添加到模型中就可以附加、检索和删除文件。
# app/models/user.rb
class User < ApplicationRecord
# 1つのアバター画像
has_one_attached :avatar
# 複数の添付ファイル
has_many_attached :documents
end
# app/models/article.rb
class Article < ApplicationRecord
has_one_attached :cover_image
has_many_attached :attachments
# バリデーション(active_storage_validations gem を使用)
validates :cover_image,
content_type: { in: %w[image/jpeg image/png image/webp], message: 'はJPEG・PNG・WebPのみ有効です' },
size: { less_than: 5.megabytes, message: 'は5MB以下にしてください' }
validates :attachments,
content_type: %w[application/pdf image/jpeg image/png],
size: { less_than: 20.megabytes }
end
# コントローラーでの操作例
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
@user = current_user
if @user.update(user_params)
redirect_to @user, notice: 'プロフィールを更新しました。'
else
render :edit, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :avatar, documents: [])
end
end
<!-- app/views/users/edit.html.erb -->
<%= form_with model: @user do |f| %>
<div>
<%= f.label :avatar, 'プロフィール画像' %>
<!-- enctype は form_with が自動設定 -->
<%= f.file_field :avatar, accept: 'image/jpeg,image/png,image/webp' %>
<%# 現在の画像を表示 %>
<% if @user.avatar.attached? %>
<%= image_tag @user.avatar, width: 100 %>
<% end %>
</div>
<div>
<%= f.label :documents, '添付ファイル(複数可)' %>
<%= f.file_field :documents, multiple: true %>
</div>
<%= f.submit '保存' %>
<% end %>
使用active_storage_validations gem进行验证
由于 Active Storage 标准的模型验证较弱,使用 <code>active_storage_validations</code> gem 可以声明式地验证 Content-Type、大小、宽高像素数等。
# Gemfile
gem 'active_storage_validations'
# app/models/product.rb
class Product < ApplicationRecord
has_one_attached :main_image
has_many_attached :gallery_images
# content_type バリデーション
validates :main_image,
attached: true,
content_type: {
in: %w[image/jpeg image/png image/webp image/gif],
message: 'は画像ファイル(JPEG/PNG/WebP/GIF)を選択してください'
},
size: {
between: 1.kilobyte..10.megabytes,
message: 'は1KB〜10MBの範囲で指定してください'
},
dimension: {
width: { min: 200, max: 4096 },
height: { min: 200, max: 4096 },
message: '縦横ともに200〜4096pxの範囲で指定してください'
}
validates :gallery_images,
content_type: %w[image/jpeg image/png image/webp],
size: { less_than: 5.megabytes },
limit: { max: 10, message: '画像は最大10枚まで添付できます' }
end
config/storage.yml的配置
Active Storage 的存储服务在 <code>config/storage.yml</code> 中定义,通过 <code>config/environments/</code> 中的环境特定设置来切换使用的服务。
# config/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: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: <%= ENV['AWS_REGION'] %>
bucket: <%= ENV['AWS_BUCKET'] %>
# CloudFront を使う場合
# upload: { cache_control: 'max-age=3600' }
google:
service: GCS
project: <%= ENV['GOOGLE_CLOUD_PROJECT'] %>
credentials: <%= ENV['GOOGLE_CLOUD_CREDENTIALS'] %>
bucket: <%= ENV['GOOGLE_CLOUD_BUCKET'] %>
azure:
service: AzureStorage
storage_account_name: <%= ENV['AZURE_STORAGE_ACCOUNT_NAME'] %>
storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>
container: <%= ENV['AZURE_STORAGE_CONTAINER'] %>
# config/environments/production.rb
Rails.application.configure do
# S3 を使用
config.active_storage.service = :amazon
end
# config/environments/development.rb
Rails.application.configure do
# ローカルディスクを使用
config.active_storage.service = :local
end
# Gemfile(S3を使う場合)
gem 'aws-sdk-s3', require: false
# Google Cloud Storage を使う場合
gem 'google-cloud-storage', '~> 1.11', require: false
# Azure を使う場合
gem 'azure-storage-blob', require: false
使用 Variant 进行图像调整大小
使用 Active Storage 的 Variant 功能,你可以动态地调整已保存图像的大小和转换来显示。转换结果会被缓存,因此从第二次开始会快速交付。
# ビューでの Variant 使用例
# app/views/users/show.html.erb
<%# サムネイル表示(100x100にリサイズ)%>
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
<%# アスペクト比を保ちながら幅300pxに収める %>
<%= image_tag @user.avatar.variant(resize_to_limit: [300, nil]) %>
<%# 中央でトリミングして正方形に(400x400)%>
<%= image_tag @user.avatar.variant(resize_to_fill: [400, 400]) %>
<%# WebP に変換(高画質・小容量)%>
<%= image_tag @user.avatar.variant(convert: 'webp', resize_to_limit: [800, 600]) %>
<%# 画質を下げてファイルサイズを削減 %>
<%= image_tag @user.avatar.variant(
resize_to_limit: [1200, 900],
saver: { quality: 80 }
) %>
# モデルでよく使う Variant を定義する(ヘルパーメソッド)
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
def avatar_thumbnail
avatar.variant(resize_to_fill: [80, 80], convert: 'webp')
end
def avatar_medium
avatar.variant(resize_to_limit: [400, 400], convert: 'webp', saver: { quality: 85 })
end
end
# ビューで使用
# <%= image_tag @user.avatar_thumbnail if @user.avatar.attached? %>
本文中可用的测试文件(免费)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 测试图像(1MB)</a> — 用于测试 <code>has_one_attached</code>・<code>Variant</code>
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF 测试文件(1MB)</a> — 用于验证 has_many_attached 和 S3 上传
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP 测试文件(1MB)</a> — 用于验证 content_type 验证
相关文章
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">AWS S3・CloudFront 文件上传限制总结</a>
- → <a href="/ja/blog/laravel-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Laravel 文件上传实现指南|验证、Storage 和 S3 支持</a>
- → <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Web 表单文件验证实现检查清单</a>