Saltar al contenido

Cómo implementar carga de archivos con Rails Active Storage | Soporte has_one_attached y S3

Categoría: Ruby on Rails
Este artículo está disponible actualmente solo en japonés. Estamos trabajando en las traducciones.

Active Storage, incluido por defecto en Rails 5.2, es un marco de trabajo para cargas de archivos. Es la solución oficial que reemplaza <code>CarrierWave</code> y <code>Paperclip</code>, permitiendo cambiar sin problemas entre almacenamiento en disco local, AWS S3, Google Cloud Storage y Microsoft Azure. Este artículo cubre sistemáticamente la configuración de Active Storage, la definición de <code>has_one_attached</code>, validación, configuración de S3 (config/storage.yml) y cambio de tamaño de imagen (Variant).

Controller Model has_one_attached active_storage_blobs (metadata) active_storage_attachments (join table) Service disk / s3 / gcs
Diagrama: arquitectura de Active Storage (Model → blobs/attachments → Service)

Configuración de Active Storage

Para comenzar a usar Active Storage, genere migraciones con <code>rails active_storage:install</code> y aplíquelas con <code>rails db:migrate</code>. Esto crea tres tablas: <code>active_storage_blobs</code>, <code>active_storage_attachments</code> y <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

Definición de has_one_attached y has_many_attached

<code>has_one_attached</code> define archivos adjuntos de uno a uno, y <code>has_many_attached</code> define archivos adjuntos de uno a muchos. Solo agrégalos al modelo para adjuntar, recuperar y eliminar archivos.

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

Validación con la gema active_storage_validations

Como la validación estándar del modelo de Active Storage es débil, el uso de la gema <code>active_storage_validations</code> permite validar de forma declarativa Content-Type, tamaño, ancho y alto en píxeles, entre otros.

# 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

Configuración de config/storage.yml

Los servicios de almacenamiento de Active Storage se definen en <code>config/storage.yml</code>, y cambia qué servicio usar mediante la configuración específica del entorno en <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

Cambio de tamaño de imagen con Variant

Usando la función Variant de Active Storage, puede cambiar el tamaño y transformar dinámicamente imágenes guardadas para mostrarlas. Los resultados de la conversión se almacenan en caché, por lo que se entregan rápidamente a partir de la 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? %>

Archivos de prueba para este artículo (gratis)

  • → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Imagen de prueba PNG (1MB)</a> — para probar <code>has_one_attached</code> y <code>Variant</code>
  • → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Archivo de prueba PDF (1MB)</a> — Para verificar has_many_attached y carga a S3
  • → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Archivo de prueba ZIP (1MB)</a> — para validar la validación de content_type

Artículos relacionados

  • → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Resumen de Límites de Carga de Archivos para AWS S3 y CloudFront</a>
  • → <a href="/ja/blog/laravel-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Guía de Implementación de Carga de Archivos en Laravel | Validación, Storage y S3</a>
  • → <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Lista de Verificación de Implementación de Validación de Archivos en Formularios Web</a>