Saltar al contenido

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

Categoría: Ruby on Rails

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 CarrierWave y Paperclip, 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 has_one_attached, 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 rails active_storage:install y aplíquelas con rails db:migrate. Esto crea tres tablas: active_storage_blobs, active_storage_attachments y active_storage_variant_records.

# 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

has_one_attached define archivos adjuntos de uno a uno, y has_many_attached 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 active_storage_validations 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 config/storage.yml, y cambia qué servicio usar mediante la configuración específica del entorno en config/environments/.

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

❓ Preguntas frecuentes

¿Cuál es la diferencia entre Active Storage y CarrierWave?
Active Storage viene con Rails desde 5.2 y soporta disco local, S3, GCS y Azure sin gems adicionales. CarrierWave es un gem de terceros veterano que se integra con los modelos de otra forma. En proyectos nuevos conviene Active Storage; en los que ya usan CarrierWave, hay que sopesar el coste de migrar.
¿Cómo elimino archivos con Active Storage?
Con has_one_attached, user.avatar.purge elimina el archivo y su registro de forma síncrona, y purge_later lo hace en segundo plano. Con has_many_attached, user.documents.purge lo borra todo y user.documents.find(id).purge elimina uno concreto. Ten en cuenta que purge borra el archivo real, mientras que detach solo rompe la asociación.
¿Cómo accedo a archivos privados de S3 con URLs firmadas en Active Storage?
rails_blob_url(user.avatar, disposition: 'attachment') y url_for(user.avatar) hacen que Active Storage genere una URL firmada. Define la validez en config/environments/production.rb, por ejemplo config.active_storage.service_urls_expire_in = 1.hour. Mantén el bucket privado y sirve todo mediante URLs firmadas.

Archivos de prueba para este artículo (gratis)