콘텐츠로 건너뛰기

Rails Active Storage로 파일 업로드 구현하는 방법 | has_one_attached·S3 지원

카테고리: Ruby on Rails

Rails 5.2부터 기본 제공되는 Active Storage는 파일 업로드를 위한 프레임워크입니다. 기존의 CarrierWavePaperclip을 대체하는 공식 솔루션으로, 로컬 디스크, AWS S3, Google Cloud Storage, Microsoft Azure 스토리지 간에 원활하게 전환할 수 있습니다. 본 문서는 Active Storage 설정부터 has_one_attached 정의, 검증, S3 설정(config/storage.yml), 이미지 리사이징(Variant)까지 체계적으로 설명합니다.

Controller Model has_one_attached active_storage_blobs (metadata) active_storage_attachments (join table) Service disk / s3 / gcs
그림: Active Storage 구성 (Model → blobs/attachments → Service)

Active Storage 설정

Active Storage를 사용하기 시작하려면 rails active_storage:install로 마이그레이션을 생성하고 rails db:migrate로 적용합니다. 이렇게 하면 active_storage_blobs, active_storage_attachments, active_storage_variant_records의 3개 테이블이 생성됩니다.

# 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의 정의

has_one_attached는 1대1, has_many_attached는 1대다 파일 첨부를 정의합니다. 모델에 추가하기만 해도 파일 첨부, 조회, 삭제를 할 수 있습니다.

# 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 표준의 모델 검증이 약하므로, active_storage_validations 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의 스토리지 서비스는 config/storage.yml에서 정의되며, 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

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

❓ 자주 묻는 질문

Rails의 Active Storage와 CarrierWave의 차이는?
Active Storage는 Rails 5.2부터 기본 탑재된 공식 기능으로, 추가 gem 없이 로컬·S3·GCS·Azure를 지원합니다. CarrierWave는 오랫동안 쓰여 온 서드파티 gem으로 모델에 통합하는 방식이 다릅니다. 신규 프로젝트에는 Active Storage가 권장되지만, 기존 프로젝트에서 CarrierWave를 쓰고 있다면 이전 비용을 따져 판단합니다.
Active Storage에서 파일을 삭제하려면?
has_one_attached의 경우 user.avatar.purge로 파일과 관련 레코드를 동기 삭제하고, purge_later로 백그라운드에서 삭제합니다. has_many_attached의 경우 user.documents.purge로 전체를 삭제하고, 개별 삭제는 user.documents.find(id).purge를 사용합니다. purge는 실제 파일도 삭제하지만 detach는 레코드 연결만 해제합니다.
Active Storage에서 S3의 비공개 파일에 서명된 URL로 접근하려면?
rails_blob_url(user.avatar, disposition: 'attachment') 나 url_for(user.avatar) 를 사용하면 Active Storage가 자동으로 서명된 URL을 생성합니다. 유효 기간은 config/environments/production.rb 에서 config.active_storage.service_urls_expire_in = 1.hour 처럼 설정할 수 있습니다. S3 버킷을 퍼블릭으로 두지 않고 서명된 URL로만 접근하게 하면 보안을 확보할 수 있습니다.

이 기사에서 사용할 수 있는 테스트 파일 (무료)