Skip to content

Como implementar upload de arquivos com Active Storage no Rails | has_one_attached・Compatibilidade com S3

Categoria:Ruby on Rails
Este artigo está disponível atualmente apenas em japonês. As versões traduzidas serão publicadas sequencialmente.

Active Storage, incluído por padrão no Rails 5.2, é um framework para upload de arquivos. É o mecanismo oficial que substitui as soluções legadas <code>CarrierWave</code> e <code>Paperclip</code>, permitindo alternar perfeitamente entre armazenamento em disco local, AWS S3, Google Cloud Storage e Microsoft Azure. Este artigo explica sistematicamente desde a configuração inicial do Active Storage, a definição de <code>has_one_attached</code>, validações, configuração do S3 (config/storage.yml) até redimensionamento de imagens (Variant).

Controller Model has_one_attached active_storage_blobs (metadata) active_storage_attachments (join table) Service disk / s3 / gcs
Figura: Configuração do Active Storage (Model → blobs/attachments → Service)

Configuração do Active Storage

Para começar a usar o Active Storage, gere a migração com <code>rails active_storage:install</code> e aplique-a com <code>rails db:migrate</code>. Isso cria três tabelas: <code>active_storage_blobs</code>, <code>active_storage_attachments</code> e <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

Definição de has_one_attached e has_many_attached

<code>has_one_attached</code> define anexo de arquivo 1-para-1, e <code>has_many_attached</code> define 1-para-muitos. Ao adicionar ao modelo, é possível anexar, obter e deletar arquivos.

# 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 %>

Validação com a gem active_storage_validations

Como o Active Storage padrão tem validação de modelo fraca, você pode usar a gem <code>active_storage_validations</code> para validar declarativamente Content-Type, tamanho, número de pixels em altura e largura, entre outros.

# 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

Configuração de config/storage.yml

Os serviços de armazenamento do Active Storage são definidos em <code>config/storage.yml</code> e você alterna qual serviço usar nas configurações específicas do ambiente em <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

Redimensionamento de imagem com Variant

Ao usar a função Variant do Active Storage, você pode redimensionar e converter dinamicamente imagens salvas para exibição. Os resultados de conversão são armazenados em cache, para que sejam entregues rapidamente a partir da segunda vez.

# ビューでの 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? %>

Arquivo de teste disponível para usar neste artigo (gratuito)

  • → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Imagem PNG de teste (1MB)</a> — Para teste de <code>has_one_attached</code> e <code>Variant</code>
  • → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste PDF (1MB)</a> — Para verificar has_many_attached e upload S3
  • → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste ZIP (1MB)</a> — Para verificação de validação de content_type

Artigos relacionados

  • → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Resumo dos limites de upload de arquivos do AWS S3 e CloudFront</a>
  • → <a href="/ja/blog/laravel-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Guia de implementação de upload de arquivos no Laravel | Validação, Storage e suporte S3</a>
  • → <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Lista de verificação de implementação de validação de arquivo para formulários web</a>