액티브 스토리지 개요
이 가이드는 액티브 레코드 모델에 파일을 첨부하는 방법을 다룹니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 하나 이상의 파일을 레코드에 첨부하는 방법.
- 첨부된 파일을 삭제하는 방법.
- 첨부된 파일에 링크하는 방법.
- 이미지를 변환하는 데 변형을 사용하는 방법.
- PDF 또는 비디오와 같은 비이미지 파일의 이미지 표현을 생성하는 방법.
- 브라우저에서 직접 저장 서비스로 파일 업로드를 우회하는 방법.
- 테스트 중에 저장된 파일을 정리하는 방법.
- 추가 저장 서비스에 대한 지원을 구현하는 방법.
액티브 스토리지란 무엇인가?
액티브 스토리지는 Amazon S3, Google Cloud Storage 또는 Microsoft Azure Storage와 같은 클라우드 저장 서비스에 파일을 업로드하고 이를 액티브 레코드 객체에 첨부하는 기능을 제공합니다. 개발 및 테스트를 위한 로컬 디스크 기반 서비스와 백업 및 마이그레이션을 위한 하위 서비스 미러링을 지원합니다.
액티브 스토리지를 사용하면 애플리케이션이 이미지 업로드를 변환하거나 PDF 및 비디오와 같은 비이미지 업로드의 이미지 표현을 생성하고 임의의 파일에서 메타데이터를 추출할 수 있습니다.
요구 사항
액티브 스토리지의 다양한 기능은 Rails가 설치하지 않는 타사 소프트웨어에 의존하며 별도로 설치해야 합니다:
- 이미지 분석 및 변환을 위한 libvips v8.6+ 또는 ImageMagick
- 비디오 미리보기 및 ffprobe를 위한 ffmpeg v3.4+
- PDF 미리보기를 위한 poppler 또는 muPDF
이미지 분석 및 변환에는 image_processing
젬도 필요합니다. Gemfile
에서 주석을 해제하거나 필요한 경우 추가하세요:
gem "image_processing", ">= 1.2"
팁: ImageMagick에 비해 libvips는 더 잘 알려져 있고 더 널리 사용되지만, libvips는 최대 10배 빠르고 메모리 사용량이 1/10입니다. JPEG 파일의 경우 libjpeg-dev
를 libjpeg-turbo-dev
로 대체하면 2-7배 더 빨라집니다.
경고: 타사 소프트웨어를 설치하고 사용하기 전에 이로 인한 라이선스 영향을 이해해야 합니다. 특히 muPDF는 AGPL 라이선스 하에 있으며 일부 용도에는 상용 라이선스가 필요합니다.
설정
$ bin/rails active_storage:install $ bin/rails db:migrate
이렇게 하면 구성이 설정되고 액티브 스토리지가 사용하는 세 개의 테이블 active_storage_blobs
, active_storage_attachments
및 active_storage_variant_records
가 생성됩니다.
테이블 | 목적 |
---|---|
active_storage_blobs |
업로드된 파일에 대한 데이터(파일 이름 및 콘텐츠 유형 등)를 저장합니다. |
active_storage_attachments |
모델과 blobs를 연결하는 다형성 조인 테이블입니다. 모델의 클래스 이름이 변경되면 이 테이블의 record_type 기반 데이터를 업데이트해야 합니다. |
active_storage_variant_records |
변형 추적이 활성화된 경우 생성된 각 변형에 대한 레코드를 저장합니다. |
경고: UUID를 기본 키로 사용하는 경우 Rails.application.config.generators { |g| g.orm :active_record, primary_key_type: :uuid }
를 구성 파일에 설정해야 합니다.
config/storage.yml
에서 액티브 스토리지 서비스를 선언합니다. 애플리케이션에서 사용하는 각 서비스에 대해 이름과 필요한 구성을 제공합니다. 아래 예제에서는 local
, test
및 amazon
이라는 세 개의 서비스를 선언합니다:
local: service: Disk root: <%= Rails.root.join("storage") %> test: service: Disk root: <%= Rails.root.join("tmp/storage") %> amazon: service: S3 access_key_id: "" secret_access_key: "" bucket: "" region: "" # 예: 'us-east-1'
어떤 서비스를 사용할지 Rails.application.config.active_storage.service
에 설정합니다. 각 환경이 다른 서비스를 사용할 가능성이 높기 때문에 환경별로 설정하는 것이 좋습니다. 개발 환경에서 이전 예제의 디스크 서비스를 사용하려면 config/environments/development.rb
에 다음을 추가합니다:
# 파일을 로컬에 저장합니다. config.active_storage.service = :local
프로덕션에서 S3 서비스를 사용하려면 config/environments/production.rb
에 다음을 추가합니다:
# 파일을 Amazon S3에 저장합니다. config.active_storage.service = :amazon
테스트 시 테스트 서비스를 사용하려면 config/environments/test.rb
에 다음을 추가합니다:
# 업로드된 파일을 로컬 파일 시스템의 임시 디렉토리에 저장합니다. config.active_storage.service = :test
참고: 환경별 구성 파일이 우선합니다. 예를 들어 프로덕션에서는 config/storage/production.yml
파일(있는 경우)이 config/storage.yml
파일보다 우선합니다.
버킷 이름에 Rails.env
를 사용하여 프로덕션 데이터를 실수로 삭제하는 위험을 더 줄이는 것이 좋습니다.
amazon: service: S3 # ... bucket: your_own_bucket-<%= Rails.env %> google: service: GCS # ... bucket: your_own_bucket-<%= Rails.env %> azure: service: AzureStorage # ... container: your_container_name-<%= Rails.env %>
내장 서비스 어댑터(예: Disk
및 S3
)와 이들이 요구하는 구성에 대해 자세히 알아보세요.
Disk 서비스
config/storage.yml
에 Disk 서비스를 선언합니다:
local: service: Disk root: <%= Rails.root.join("storage") %>
S3 서비스(Amazon S3 및 S3 호환 API)
Amazon S3에 연결하려면 config/storage.yml
에 S3 서비스를 선언합니다:
amazon: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: ""
선택적으로 클라이언트 및 업로드 옵션을 제공할 수 있습니다:
amazon: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" http_open_timeout: 0 http_read_timeout: 0 retry_limit: 0 upload: server_side_encryption: "" # 'aws:kms' 또는 'AES256' cache_control: "private, max-age=<%= 1.day.to_i %>"
팁: 애플리케이션에 적절한 클라이언트 HTTP 타임아웃 및 재시도 제한을 설정하세요. 특정 오류 시나리오에서 기본 AWS 클라이언트 구성은 몇 분 동안 연결을 유지할 수 있어 요청 대기열이 발생할 수 있습니다.
Gemfile
에 aws-sdk-s3
젬을 추가합니다:
gem "aws-sdk-s3", require: false
참고: 액티브 스토리지의 핵심 기능에는 다음과 같은 권한이 필요합니다: s3:ListBucket
, s3:PutObject
, s3:GetObject
및 s3:DeleteObject
. 공개 액세스에는 추가로 s3:PutObjectAcl
이 필요합니다. ACL 설정과 같은 추가 업로드 옵션이 있는 경우 추가 권한이 필요할 수 있습니다.
참고: 환경 변수, 표준 SDK 구성 파일, 프로필, IAM 인스턴스 프로필 또는 작업 역할을 사용하려면 위 예제에서 access_key_id
, secret_access_key
및 region
키를 생략할 수 있습니다. S3 서비스는 AWS SDK 문서에 설명된 모든 인증 옵션을 지원합니다.
DigitalOcean Spaces와 같은 S3 호환 오브젝트 스토리지 API에 연결하려면 endpoint
를 제공합니다:
digitalocean: service: S3 endpoint: https://nyc3.digitaloceanspaces.com access_key_id: ... secret_access_key: ... # ...및 기타 옵션
사용할 수 있는 다른 옵션이 많습니다. AWS S3 Client 문서를 확인하세요.
Microsoft Azure Storage 서비스
config/storage.yml
에 Azure Storage 서비스를 선언합니다:
azure: service: AzureStorage storage_account_name: "" storage_access_key: "" container: ""
Gemfile
에 azure-storage-blob
젬을 추가합니다:
gem "azure-storage-blob", "~> 2.0", require: false
Google Cloud Storage 서비스
config/storage.yml
에 Google Cloud Storage 서비스를 선언합니다:
google: service: GCS credentials: <%= Rails.root.join("path/to/keyfile.json") %> project: "" bucket: ""
선택적으로 키파일 경로 대신 자격 증명 해시를 제공할 수 있습니다:
google: service: GCS credentials: type: "service_account" project_id: "" private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %> private_key: <%= Rails.application.credentials.dig(:gcs, :private_key).dump %> client_email: "" client_id: "" auth_uri: "https://accounts.google.com/o/oauth2/auth" token_uri: "https://accounts.google.com/o/oauth2/token" auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs" client_x509_cert_url: "" project: "" bucket: ""
선택적으로 업로드된 자산에 대한 Cache-Control 메타데이터를 제공할 수 있습니다:
google: service: GCS ... cache_control: "public, max-age=3600"
선택적으로 credentials
대신 IAM을 사용하여 URL에 서명할 수 있습니다. 이는 GKE 애플리케이션을 Workload Identity로 인증하는 경우 유용합니다. [이 Google Cloud 블로그 게시물](https://cloud.google.com/blog/products/containers-kubernetes/introducing-계속해서 번역문:
미러 서비스
여러 서비스를 동기화하려면 미러 서비스를 정의할 수 있습니다. 미러 서비스는 두 개 이상의 하위 서비스 간에 업로드 및 삭제를 복제합니다.
미러 서비스는 프로덕션 환경에서 서비스 간 마이그레이션 중에 일시적으로 사용하는 것이 목적입니다. 새 서비스로 미러링을 시작하고 이전 서비스에서 기존 파일을 복사한 다음 새 서비스로 완전히 전환할 수 있습니다.
참고: 미러링은 원자성이 없습니다. 기본 서비스에 업로드가 성공하고 하위 서비스 중 하나에 실패할 수 있습니다. 새 서비스로 완전히 전환하기 전에 모든 파일이 복사되었는지 확인하세요.
앞서 설명한 대로 각 미러링할 서비스를 정의합니다. 미러 서비스를 정의할 때 이름으로 참조합니다:
s3_west_coast: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" s3_east_coast: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" production: service: Mirror primary: s3_east_coast mirrors: - s3_west_coast
모든 보조 서비스가 업로드를 받지만 다운로드는 항상 기본 서비스에서 처리됩니다.
미러 서비스는 직접 업로드와 호환됩니다. 새 파일은 기본 서비스에 직접 업로드됩니다. 직접 업로드된 파일이 레코드에 첨부되면 보조 서비스에 복사하는 백그라운드 작업이 예약됩니다.
공개 액세스
기본적으로 액티브 스토리지는 서비스에 대한 비공개 액세스를 가정합니다. 즉, blob에 대한 서명된 단일 사용 URL을 생성합니다. 대신 blob을 공개적으로 액세스 가능하게 하려면 앱의 config/storage.yml
에 public: true
를 지정합니다:
gcs: &gcs service: GCS project: "" private_gcs: <<: *gcs credentials: <%= Rails.root.join("path/to/private_key.json") %> bucket: "" public_gcs: <<: *gcs credentials: <%= Rails.root.join("path/to/public_key.json") %> bucket: "" public: true
버킷이 공개 액세스에 적절하게 구성되어 있는지 확인하세요. Amazon S3, Google Cloud Storage 및 Microsoft Azure 스토리지 서비스에 대한 공개 읽기 권한 활성화 방법을 참조하세요. Amazon S3에는 추가로 s3:PutObjectAcl
권한이 필요합니다.
기존 애플리케이션을 public: true
로 변환할 때는 전환하기 전에 버킷의 개별 파일을 모두 공개적으로 읽을 수 있도록 업데이트해야 합니다.
레코드에 파일 첨부하기
has_one_attached
has_one_attached
매크로는 레코드와 파일 간의 일대일 매핑을 설정합니다. 각 레코드에 하나의 파일을 첨부할 수 있습니다.
예를 들어 애플리케이션에 User
모델이 있다고 가정합니다. 각 사용자에게 아바타를 지정하려면 다음과 같이 User
모델을 정의합니다:
class User < ApplicationRecord has_one_attached :avatar end
또는 Rails 6.0+ 사용 시 다음과 같은 모델 생성기 명령을 실행할 수 있습니다:
$ bin/rails generate model User avatar:attachment
아바타가 있는 사용자를 생성할 수 있습니다:
<%= form.file_field :avatar %>
class SignupController < ApplicationController def create user = User.create!(user_params) session[:user_id] = user.id redirect_to root_path end private def user_params params.require(:user).permit(:email_address, :password, :avatar) end end
avatar.attach
를 호출하여 기존 사용자에게 아바타를 첨부할 수 있습니다:
user.avatar.attach(params[:avatar])
avatar.attached?
를 호출하여 특정 사용자에게 아바타가 첨부되어 있는지 확인할 수 있습니다:
user.avatar.attached?
특정 첨부 파일에 대해 기본 서비스를 재정의해야 하는 경우가 있습니다. service
옵션을 사용하여 첨부 파일별로 특정 서비스를 구성할 수 있습니다:
class User < ApplicationRecord has_one_attached :avatar, service: :google end
첨부 가능한 객체에서 variant
메서드를 호출하여 첨부 파일별로 특정 변형을 구성할 수 있습니다:
class User < ApplicationRecord has_one_attached :avatar do |attachable| attachable.variant :thumb, resize_to_limit: [100, 100] end end
avatar.variant(:thumb)
를 호출하여 아바타의 썸네일 변형을 가져올 수 있습니다:
<%= image_tag user.avatar.variant(:thumb) %>
미리보기에도 특정 변형을 사용할 수 있습니다:
class User < ApplicationRecord has_one_attached :video do |attachable| attachable.variant :thumb, resize_to_limit: [100, 100] end end
<%= image_tag user.video.preview(:thumb) %>
변형에 미리 액세스할 것을 알고 있다면 Rails가 미리 생성하도록 지정할 수 있습니다:
class User < ApplicationRecord has_one_attached :video do |attachable| attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true end end
Rails는 첨부 파일이 레코드에 첨부된 후 변형을 생성하는 작업을 예약합니다.
참고: 액티브 스토리지는 다형성 연관에 의존하며, 다형성 연관은 데이터베이스에 클래스 이름을 저장합니다. 따라서 Ruby 코드에서 사용되는 클래스 이름과 동기화된 상태를 유지해야 합니다. has_one_attached
를 사용하는 클래스 이름을 변경할 때는 active_storage_attachments.record_type
다형성 유형 열의 해당 행에서 클래스 이름도 업데이트해야 합니다.
has_many_attached
has_many_attached
매크로는 레코드와 파일 간의 일대다 관계를 설정합니다. 각 레코드에 여러 파일을 첨부할 수 있습니다.
예를 들어 애플리케이션에 Message
모델이 있다고 가정합니다. 각 메시지에 여러 이미지를 첨부하려면 다음과 같이 Message
모델을 정의합니다:
class Message < ApplicationRecord has_many_attached :images end
또는 Rails 6.0+ 사용 시 다음과 같은 모델 생성기 명령을 실행할 수 있습니다:
$ bin/rails generate model Message images:attachments
이미지가 있는 메시지를 생성할 수 있습니다:
class MessagesController < ApplicationController def create message = Message.create!(message_params) redirect_to message end private def message_params params.require(:message).permit(:title, :content, images: []) end end
images.attach
를 호출하여 기존 메시지에 새 이미지를 추가할 수 있습니다:
@message.images.attach(params[:images])
images.attached?
를 호출하여 특정 메시지에 이미지가 첨부되어 있는지 확인할 수 있습니다:
@message.images.attached?
기본 서비스를 재정의하는 것은 has_one_attached
와 동일한 방식으로 service
옵션을 사용하여 수행합니다:
class Message < ApplicationRecord has_many_attached :images, service: :s3 end
특정 변형을 구성하는 것도 has_one_attached
와 동일한 방식으로 첨부 가능한 객체에서 variant
메서드를 호출하여 수행합니다:
class Message < ApplicationRecord has_many_attached :images do |attachable| attachable.variant :thumb, resize_to_limit: [100, 100] end end
참고: 액티브 스토리지는 다형성 연관에 의존하며, 다형성 연관은 데이터베이스에 클래스 이름을 저장합니다. 따라서 Ruby 코드에서 사용되는 클래스 이름과 동기화된 상태를 유지해야 합니다. has_many_attached
를 사용하는 클래스 이름을 변경할 때는 active_storage_attachments.record_type
다형성 유형 열의 해당 행에서 클래스 이름도 업데이트해야 합니다.
파일/IO 객체 첨부하기
때로는 HTTP 요청을 통해 도착하지 않는 파일을 첨부해야 합니다. 예를 들어 디스크에서 생성하거나 사용자가 제출한 URL에서 다운로드한 파일을 첨부하고 싶을 수 있습니다. 또한 모델 테스트에서 픽스처 파일을 첨부하고 싶을 수 있습니다. 이를 위해 열린 IO 객체와 파일 이름이 포함된 해시를 제공합니다:
@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf')
가능하면 콘텐츠 유형도 제공하세요. 액티브 스토리지는 데이터에서 파일의 콘텐츠 유형을 결정하려고 합니다. 자동으로 결정할 수 없는 경우 제공한 콘텐츠 유형으로 대체합니다.
@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')
데이터에서 콘텐츠 유형 추론을 건너뛰려면 identify: false
와 함께 content_type
을 전달합니다.
@message.images.attach( io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf', identify: false )
콘텐츠 유형을 제공하지 않고 액티브 스토리지가 자동으로 파일의 콘텐츠 유형을 결정할 수 없는 경우 기본값 application/octet-stream이 사용됩니다.
key
매개변수를 추계속해서 번역문:
key
매개변수를 추가로 사용할 수 있습니다. 이를 통해 S3 버킷에 폴더/하위 폴더를 지정할 수 있습니다. 그렇지 않으면 AWS S3가 임의의 키를 사용하여 파일 이름을 지정합니다. 이 접근 방식은 S3 버킷 파일을 더 잘 구성하는 데 도움이 됩니다.
@message.images.attach( io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf', key: "#{Rails.env}/blog_content/intuitive_filename.pdf", identify: false )
이렇게 하면 개발 환경에서 테스트할 때 파일이 [S3_BUCKET]/development/blog_content/
폴더에 저장됩니다. 키 매개변수를 사용하는 경우 업로드가 성공하려면 키가 고유해야 합니다. 파일 이름에 고유한 랜덤 키를 추가하는 것이 좋습니다:
def s3_file_key "#{Rails.env}/blog_content/intuitive_filename-#{SecureRandom.uuid}.pdf" end
@message.images.attach( io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf', key: s3_file_key, identify: false )
첨부 파일 교체 vs 추가
기본적으로 Rails에서는 has_many_attached
연관에 파일을 첨부하면 기존 첨부 파일을 교체합니다.
기존 첨부 파일을 유지하려면 각 첨부 파일의 signed_id
가 포함된 숨겨진 폼 필드를 사용할 수 있습니다:
<% @message.images.each do |image| %> <%= form.hidden_field :images, multiple: true, value: image.signed_id %> <% end %> <%= form.file_field :images, multiple: true %>
이렇게 하면 JavaScript를 사용하여 개별 숨겨진 필드를 제거하는 등 기존 첨부 파일을 선택적으로 제거할 수 있습니다.
폼 유효성 검사
첨부 파일은 관련 레코드의 성공적인 save
가 이루어질 때까지 저장 서비스로 전송되지 않습니다. 따라서 폼 제출이 유효성 검사에 실패하면 새로운 첨부 파일이 손실되고 다시 업로드해야 합니다. 직접 업로드를 사용하면 유효성 검사 실패 시에도 업로드를 유지할 수 있습니다:
<%= form.hidden_field :avatar, value: @user.avatar.signed_id if @user.avatar.attached? %> <%= form.file_field :avatar, direct_upload: true %>
파일 제거
모델에서 첨부 파일을 제거하려면 첨부 파일의 purge
를 호출합니다. 애플리케이션이 Active Job을 사용하도록 설정된 경우 purge_later
를 호출하여 백그라운드에서 제거할 수 있습니다. Purge는 blob과 저장 서비스의 파일을 삭제합니다.
# 동기적으로 아바타와 실제 리소스 파일을 파괴합니다. user.avatar.purge # 관련 모델과 실제 리소스 파일을 비동기적으로 파괴합니다(Active Job 통해). user.avatar.purge_later
파일 제공
액티브 스토리지는 두 가지 방식으로 파일을 제공합니다: 리디렉션과 프록시.
경고: 모든 액티브 스토리지 컨트롤러는 기본적으로 공개적으로 액세스할 수 있습니다. 생성된 URL은 추측하기 어렵지만 영구적으로 설계되어 있습니다. 파일에 더 높은 수준의 보호가 필요한 경우 인증된 컨트롤러를 구현하는 것을 고려해 보세요.
리디렉션 모드
blob에 대한 영구 URL을 생성하려면 url_for
뷰 헬퍼에 blob을 전달할 수 있습니다. 이렇게 하면 blob의 signed_id
가 포함된 URL이 생성되며, 이 URL은 blob의 RedirectController
로 라우팅됩니다.
url_for(user.avatar) # => https://www.example.com/rails/active_storage/blobs/redirect/:signed_id/my-avatar.png
RedirectController
는 실제 서비스 엔드포인트로 리디렉션합니다. 이 간접 방식은 서비스 URL과 실제 URL을 분리하여 고가용성을 위해 첨부 파일을 다른 서비스에 미러링할 수 있습니다. 리디렉션에는 5분의 HTTP 만료 시간이 있습니다.
다운로드 링크를 만들려면 rails_blob_{path|url}
헬퍼를 사용하세요. 이 헬퍼를 사용하면 처리 방식을 설정할 수 있습니다.
rails_blob_path(user.avatar, disposition: "attachment")
경고: XSS 공격을 방지하기 위해 액티브 스토리지는 일부 파일 유형에 대해 Content-Disposition 헤더를 “attachment"로 강제합니다. 이 동작을 변경하려면 Rails 애플리케이션 구성에 있는 사용 가능한 구성 옵션을 참조하세요.
컨트롤러/뷰 컨텍스트 외부(백그라운드 작업, Cronjob 등)에서 링크를 만들어야 하는 경우 다음과 같이 rails_blob_path
에 액세스할 수 있습니다:
Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true)
프록시 모드
선택적으로 파일을 프록시할 수 있습니다. 이는 애플리케이션 서버가 요청에 응답하여 저장 서비스에서 파일 데이터를 다운로드한다는 의미입니다. CDN에서 파일을 제공하는 데 유용할 수 있습니다.
기본적으로 프록시를 사용하도록 액티브 스토리지를 구성할 수 있습니다:
# config/initializers/active_storage.rb Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy
또는 특정 첨부 파일에 대해 명시적으로 프록시하려면 rails_storage_proxy_path
및 rails_storage_proxy_url
URL 헬퍼를 사용할 수 있습니다.
<%= image_tag rails_storage_proxy_path(@user.avatar) %>
CDN 앞에 액티브 스토리지 배치하기
또한 액티브 스토리지 첨부 파일에 CDN을 사용하려면 프록시 모드로 URL을 생성해야 합니다. 그래야 애플리케이션을 통해 제공되고 CDN이 추가 구성 없이 첨부 파일을 캐시할 수 있습니다. 이는 기본 액티브 스토리지 프록시 컨트롤러가 CDN이 응답을 캐시하도록 하는 HTTP 헤더를 설정하기 때문에 작동합니다.
생성된 URL에 앱 호스트 대신 CDN 호스트를 사용해야 합니다. 이를 달성하는 방법은 여러 가지가 있지만, 일반적으로 config/routes.rb
파일을 조정하여 첨부 파일과 해당 변형에 대한 적절한 URL을 생성할 수 있습니다. 예를 들면 다음과 같습니다:
# config/routes.rb direct :cdn_image do |model, options| expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in } if model.respond_to?(:signed_id) route_for( :rails_service_blob_proxy, model.signed_id(expires_in: expires_in), model.filename, options.merge(host: ENV['CDN_HOST']) ) else signed_blob_id = model.blob.signed_id(expires_in: expires_in) variation_key = model.variation.key filename = model.blob.filename route_for( :rails_blob_representation_proxy, signed_blob_id, variation_key, filename, options.merge(host: ENV['CDN_HOST']) ) end end
그리고 다음과 같이 경로를 생성할 수 있습니다:
<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>
인증된 컨트롤러
모든 액티브 스토리지 컨트롤러는 기본적으로 공개적으로 액세스할 수 있습니다. 생성된 URL은 signed_id
를 사용하므로 추측하기 어렵지만 영구적입니다. ApplicationController
의 before_action
이 로그인을 요구하더라도 blob URL을 알고 있는 사람은 누구나 액세스할 수 있습니다. 파일에 더 높은 수준의 보호가 필요한 경우 자체 인증된 컨트롤러를 구현할 수 있습니다. 이는 ActiveStorage::Blobs::RedirectController
,
ActiveStorage::Blobs::ProxyController
,
ActiveStorage::Representations::RedirectController
및
ActiveStorage::Representations::ProxyController
를 기반으로 합니다.
계정 자신의 로고에만 액세스할 수 있도록 하려면 다음과 같이 할 수 있습니다:
# config/routes.rb resource :account do resource :logo end
# app/controllers/logos_controller.rb class LogosController < ApplicationController # ApplicationController를 통해: # include Authenticate, SetCurrentAccount def show redirect_to Current.account.logo.url end end
<%= image_tag account_logo_path %>
그리고 나서 액티브 스토리지 기본 경로를 비활성화해야 합니다:
config.active_storage.draw_routes = false
이렇게 하면 공개적으로 액세스할 수 있는 URL로 파일에 액세스할 수 없습니다.
파일 다운로드
때로는 업로드된 blob을 처리해야 합니다. 예를 들어 다른 형식으로 변환하려는 경우입니다. 첨부 파일의 download
메서드를 사용하여 blob의 바이너리 데이터를 메모리로 읽을 수 있습니다:
binary = user.avatar.download
외부 프로그램(예: 바이러스 스캐너 또는 미디어 트랜스코더)이 작동할 수 있도록 blob을 디스크 파일로 다운로드하려면 첨부계속해서 번역문:
외부 프로그램(예: 바이러스 스캐너 또는 미디어 트랜스코더)이 작동할 수 있도록 blob을 디스크 파일로 다운로드하려면 첨부 파일의 open
메서드를 사용합니다:
message.video.open do |file| system '/path/to/virus/scanner', file.path # ... end
after_create
콜백에서는 파일을 사용할 수 없지만 after_create_commit
에서는 사용할 수 있다는 점에 유의해야 합니다.
파일 분석
액티브 스토리지는 파일이 업로드되면 Active Job에 작업을 예약하여 파일을 분석합니다. 분석된 파일은 메타데이터 해시에 추가 정보를 저장하며, 여기에는 analyzed: true
가 포함됩니다. analyzed?
를 호출하여 blob이 분석되었는지 확인할 수 있습니다.
이미지 분석에서는 width
와 height
속성을 제공합니다. 비디오 분석에서는 이 외에도 duration
, angle
, display_aspect_ratio
, video
및 audio
부울 값을 제공합니다. 오디오 분석에서는 duration
과 bit_rate
속성을 제공합니다.
이미지, 비디오 및 PDF 표시
액티브 스토리지는 다양한 파일 유형을 표현할 수 있습니다. 첨부 파일에서 representation
을 호출하여 이미지 변형이나 비디오 또는 PDF의 미리보기를 표시할 수 있습니다. representation
을 호출하기 전에 representable?
를 호출하여 첨부 파일을 표현할 수 있는지 확인하세요. 일부 파일 형식은 액티브 스토리지로 미리볼 수 없습니다(예: Word 문서). representable?
이 false를 반환하는 경우 대신 파일에 링크할 수 있습니다.
<ul> <% @message.files.each do |file| %> <li> <% if file.representable? %> <%= image_tag file.representation(resize_to_limit: [100, 100]) %> <% else %> <%= link_to rails_blob_path(file, disposition: "attachment") do %> <%= image_tag "placeholder.png", alt: "Download file" %> <% end %> <% end %> </li> <% end %> </ul>
내부적으로 representation
은 이미지의 경우 variant
를, 미리볼 수 있는 파일의 경우 preview
를 호출합니다. 이 메서드들도 직접 호출할 수 있습니다.
지연 vs 즉시 로드
기본적으로 액티브 스토리지는 표현을 지연 처리합니다. 이 코드:
image_tag file.representation(resize_to_limit: [100, 100])
는 ActiveStorage::Representations::RedirectController
를 가리키는 src
가 있는 <img>
태그를 생성합니다. 브라우저는 다음과 같은 작업을 수행하는 해당 컨트롤러에 요청을 보냅니다:
- 파일을 처리하고 필요한 경우 처리된 파일을 업로드합니다.
- 파일에 대한
302
리디렉션을 반환합니다.- 원격 서비스(예: S3)로.
- 또는 프록시 모드가 활성화된 경우
ActiveStorage::Blobs::ProxyController
가 파일 내용을 반환합니다.
이미지를 지연 로드하면 단일 사용 URL과 같은 기능이 초기 페이지 로드를 지연시키지 않고 작동할 수 있습니다.
대부분의 경우 이 방식이 잘 작동합니다.
이미지에 대한 URL을 즉시 생성하려면 .processed.url
을 호출할 수 있습니다:
image_tag file.representation(resize_to_limit: [100, 100]).processed.url
액티브 스토리지 변형 추적기는 이 성능을 개선합니다. 요청된 변형이 이전에 처리된 경우 데이터베이스에 레코드를 저장하므로 위의 코드는 원격 서비스(예: S3)에 대한 API 호출을 한 번만 수행합니다. 변형 추적기는 자동으로 실행되지만 config.active_storage.track_variants
를 통해 비활성화할 수 있습니다.
페이지에 많은 이미지를 렌더링하는 경우 위의 예제에서는 모든 변형 레코드를 로드하는 N+1 쿼리가 발생할 수 있습니다. 이러한 N+1 쿼리를 방지하려면 ActiveStorage::Attachment
의 명명된 범위를 사용하세요.
message.images.with_all_variant_records.each do |file| image_tag file.representation(resize_to_limit: [100, 100]).processed.url end
이미지 변환
이미지 변환을 통해 원하는 크기로 이미지를 표시할 수 있습니다. 첨부 파일에서 variant
를 호출하여 변형을 만들 수 있습니다. 변형 프로세서가 지원하는 모든 변형을 메서드에 전달할 수 있습니다. 브라우저가 변형 URL을 요청하면 액티브 스토리지가 지연 처리하여 지정된 형식으로 원본 blob을 변환하고 새 서비스 위치로 리디렉션합니다.
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
변형이 요청되면 액티브 스토리지가 자동으로 이미지 형식에 따라 변환을 적용합니다:
config.active_storage.variable_content_types
에 따라 가변적이고config.active_storage.web_image_content_types
에 따라 웹 이미지로 간주되지 않는 콘텐츠 유형은 PNG로 변환됩니다.quality
가 지정되지 않은 경우 변형 프로세서의 형식별 기본 품질이 사용됩니다.
액티브 스토리지는 Vips 또는 MiniMagick을 변형 프로세서로 사용할 수 있습니다. 기본값은 config.load_defaults
대상 버전에 따라 달라지며, config.active_storage.variant_processor
를 설정하여 프로세서를 변경할 수 있습니다.
두 프로세서는 완전히 호환되지 않으므로 MiniMagick과 Vips 간에 기존 애플리케이션을 마이그레이션할 때는 형식별 옵션을 사용하는 경우 일부 변경이 필요합니다:
<!-- MiniMagick --> <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %> <!-- Vips --> <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
사용 가능한 매개변수는 image_processing
젬에 정의되어 있으며 사용 중인 변형 프로세서에 따라 다릅니다. 하지만 두 프로세서 모두 다음과 같은 매개변수를 지원합니다:
매개변수 | 예시 | 설명 |
---|---|---|
resize_to_limit |
resize_to_limit: [100, 100] |
원본 종횡비를 유지하면서 지정된 치수에 맞게 이미지 크기를 줄입니다. 이미지가 지정된 치수보다 크면 크기를 줄입니다. |
resize_to_fit |
resize_to_fit: [100, 100] |
원본 종횡비를 유지하면서 지정된 치수에 맞게 이미지 크기를 조정합니다. 이미지가 지정된 치수보다 크면 크기를 줄이고, 작으면 크기를 늘립니다. |
resize_to_fill |
resize_to_fill: [100, 100] |
원본 종횡비를 유지하면서 지정된 치수를 채우도록 이미지 크기를 조정합니다. 필요한 경우 더 큰 치수를 기준으로 이미지를 자릅니다. |
resize_and_pad |
resize_and_pad: [100, 100] |
원본 종횡비를 유지하면서 지정된 치수에 맞게 이미지 크기를 조정합니다. 필요한 경우 투명 색상으로 나머지 영역을 채웁니다(소스 이미지에 알파 채널이 있는 경우). 그렇지 않으면 검정색으로 채웁니다. |
crop |
crop: [20, 50, 300, 300] |
이미지에서 영역을 추출합니다. 첫 두 인수는 추출할 영역의 왼쪽 및 상단 가장자리이고, 마지막 두 인수는 추출할 영역의 너비와 높이입니다. |
rotate |
rotate: 90 |
지정된 각도로 이미지를 회전합니다. |
image_processing
에는 Vips 및 MiniMagick 프로세서에 대한 추가 옵션(예: 이미지 압축을 구성할 수 있는 saver
)이 있습니다.
파일 미리보기
일부 비이미지 파일은 미리볼 수 있습니다. 즉, 이미지로 표현할 수 있습니다. 예를 들어 비디오 파일의 첫 번째 프레임을 추출하여 미리볼 수 있습니다. 기본적으로 액티브 스토리지는 비디오와 PDF 문서의 미리보기를 지원합니다. 지연 생성된 미리보기에 대한 링크를 만들려면 첨부 파일의 [preview
][] 메서드를 사용합니다:
<%= image_tag message.video.preview(resize_to_limit: [100, 100]) %>
다른 형식에 대한 지원을 추가하려면 사용자 고유의 미리보기 생성기를 추가하세요. 자세한 내용은 [ActiveStorage::Preview
][] 문서를 참조하세요.
[preview
]: https://api.rubyonrails.org/classes/ActiveStorage/Blob/Representable.html#method-계속해서 번역문:
직접 업로드
액티브 스토리지는 포함된 JavaScript 라이브러리를 통해 클라이언트에서 클라우드로 직접 업로드를 지원합니다.
사용법
애플리케이션의 JavaScript 번들에
activestorage.js
를 포함합니다.애셋 파이프라인 사용:
//= require activestorage
npm 패키지 사용:
import * as ActiveStorage from "@rails/activestorage" ActiveStorage.start()
파일 필드에
direct_upload: true
를 추가합니다:<%= form.file_field :attachments, multiple: true, direct_upload: true %>
또는
FormBuilder
를 사용하지 않는 경우 데이터 속성을 직접 추가합니다:<input type="file" data-direct-upload-url="<%= rails_direct_uploads_url %>" />
직접 업로드 요청을 허용하도록 타사 저장 서비스의 CORS를 구성합니다.
끝! 폼 제출 시 업로드가 시작됩니다.
교차 출처 리소스 공유(CORS) 구성
직접 업로드를 타사 서비스에 작동하려면 서비스에서 애플리케이션의 교차 출처 요청을 허용하도록 CORS를 구성해야 합니다. 사용 중인 서비스의 CORS 문서를 참조하세요:
다음을 허용하도록 구성해야 합니다:
- 애플리케이션에 액세스하는 모든 출처
PUT
요청 메서드- 다음 헤더:
Content-Type
Content-MD5
Content-Disposition
(Azure Storage 제외)x-ms-blob-content-disposition
(Azure Storage만 해당)x-ms-blob-type
(Azure Storage만 해당)Cache-Control
(GCS의 경우cache_control
이 설정된 경우에만)
Disk 서비스의 경우 앱의 출처와 동일하므로 CORS 구성이 필요하지 않습니다.
예: S3 CORS 구성
[ { "AllowedHeaders": [ "Content-Type", "Content-MD5", "Content-Disposition" ], "AllowedMethods": [ "PUT" ], "AllowedOrigins": [ "https://www.example.com" ], "MaxAgeSeconds": 3600 } ]
예: Google Cloud Storage CORS 구성
[ { "origin": ["https://www.example.com"], "method": ["PUT"], "responseHeader": ["Content-Type", "Content-MD5", "Content-Disposition"], "maxAgeSeconds": 3600 } ]
예: Azure Storage CORS 구성
<Cors> <CorsRule> <AllowedOrigins>https://www.example.com</AllowedOrigins> <AllowedMethods>PUT</AllowedMethods> <AllowedHeaders>Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-type</AllowedHeaders> <MaxAgeInSeconds>3600</MaxAgeInSeconds> </CorsRule> </Cors>
직접 업로드 JavaScript 이벤트
이벤트 이름 | 이벤트 대상 | 이벤트 데이터(event.detail ) |
설명 |
---|---|---|---|
direct-uploads:start |
<form> |
없음 | 직접 업로드 필드가 포함된 폼이 제출되었습니다. |
direct-upload:initialize |
<input> |
{id, file} |
폼 제출 후 각 파일에 대해 발생합니다. |
direct-upload:start |
<input> |
{id, file} |
직접 업로드가 시작되고 있습니다. |
direct-upload:before-blob-request |
<input> |
{id, file, xhr} |
애플리케이션에 직접 업로드 메타데이터를 요청하기 전입니다. |
direct-upload:before-storage-request |
<input> |
{id, file, xhr} |
파일을 저장하기 위한 요청을 보내기 전입니다. |
direct-upload:progress |
<input> |
{id, file, progress} |
파일 저장 요청이 진행되는 동안 발생합니다. |
direct-upload:error |
<input> |
{id, file, error} |
오류가 발생했습니다. 이 이벤트가 취소되지 않으면 alert 이 표시됩니다. |
direct-upload:end |
<input> |
{id, file} |
직접 업로드가 완료되었습니다. |
direct-uploads:end |
<form> |
없음 | 모든 직접 업로드가 완료되었습니다. |
예시
이러한 이벤트를 사용하여 업로드 진행 상황을 표시할 수 있습니다.
폼에 업로드된 파일을 표시하려면:
// direct_uploads.js addEventListener("direct-upload:initialize", event => { const { target, detail } = event const { id, file } = detail target.insertAdjacentHTML("beforebegin", ` <div id="direct-upload-${id}" class="direct-upload direct-upload--pending"> <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div> <span class="direct-upload__filename"></span> </div> `) target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name }) addEventListener("direct-upload:start", event => { const { id } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.remove("direct-upload--pending") }) addEventListener("direct-upload:progress", event => { const { id, progress } = event.detail const progressElement = document.getElementById(`direct-upload-progress-${id}`) progressElement.style.width = `${progress}%` }) addEventListener("direct-upload:error", event => { event.preventDefault() const { id, error } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.add("direct-upload--error") element.setAttribute("title", error) }) addEventListener("direct-upload:end", event => { const { id } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.add("direct-upload--complete") })
스타일 추가:
/* direct_uploads.css */ .direct-upload { display: inline-block; position: relative; padding: 2px 4px; margin: 0 3px 3px 0; border: 1px solid rgba(0, 0, 0, 0.3); border-radius: 3px; font-size: 11px; line-height: 13px; } .direct-upload--pending { opacity: 0.6; } .direct-upload__progress { position: absolute; top: 0; left: 0; bottom: 0; opacity: 0.2; background: #0076ff; transition: width 120ms ease-out, opacity 60ms 60ms ease-in; transform: translate3d(0, 0, 0); } .direct-upload--complete .direct-upload__progress { opacity: 0.4; } .direct-upload--error { border-color: red; } input[type=file][data-direct-upload-url][disabled] { display: none; }
사용자 정의 드래그 앤 드롭 솔루션
DirectUpload
클래스를 사용할 수 있습니다. 선택한 라이브러리에서 파일을 받으면 DirectUpload를 인스턴스화하고 create 메서드를 호출하세요. create는 업로드가 완료되면 호출되는 콜백을 받습니다.
import { DirectUpload } from "@rails/activestorage" const input = document.querySelector('input[type=file]') // 파일 드롭에 바인딩 - 부모 요소의 ondrop 또는 Dropzone과 같은 라이브러리 사용 const onDrop = (event) => { event.preventDefault() const files = event.dataTransfer.files; Array.from(files).forEach(file => uploadFile(file)) } // 일반 파일 선택에 바인딩 input.addEventListener('change', (event) => { Array.from(input.files).forEach(file => uploadFile(file)) // 선택된 파일을 입력에서 지울 수 있습니다 input.value = null }) const uploadFile = (file) => { // 폼에 file_field direct_upload: true가 있어야 하며, 이를 통해 data-direct-upload-url 데이터를 제공합니다 const url = input.dataset.directUploadUrl const upload = new DirectUpload(file, url) upload.create((error, blob) => { if (error) { // 오류 처리 } else { // 폼에 blob.signed_id가 포함된 적절한 이름의 숨겨진 입력을 추가하여 일반 업로드 흐름에서 blob id가 전송되도록 합니다 const hiddenField = document.createElement('input') hiddenField.setAttribute("type", "hidden"); hiddenField.setAttribute("value", blob.signed_id); hiddenField.name = input.name document.querySelector('form').appendChild(hiddenField) } }) }
파일 업로드 진행 상황 추적
DirectUpload
생성자를 사용할 때 세 번째 매개변수를 포함할 수 있습니다. 이렇게 하면 DirectUpload
객체가 업로드 프로세스 중에 directUploadWillStoreFileWithXHR
메서드를 호출할 수 있습니다.
그런 다음 XHR에 자신의 진행 핸들러를 연결할 수 있습니다.
import { DirectUpload } from "@rails/activestorage" class Uploader { constructor(file, url) { this.upload = new DirectUpload(this.file, this.url, this) } upload(file) { this.upload.create((error, blob) => { if (error) { // 오류 처리 } else { // 폼에 blob.signed_id가 포함된 적절한 이름의 숨겨진 입력을 추가 } }) } directUploadWillStoreFileWithXHR(request) { request.upload.addEventListener("progress", event => this.directUploadDidProgress(event)) } directUploadDidProgress(event) { // event.loaded와 event.total을 사용하여 진행률 표시줄 업데이트 } }
라이브러리 또는 프레임워크와 통합
라이브러리에서 파일을 받으면 DirectUpload
인스턴스를 만들고 필요한 추가 헤더와 함께 "create” 메서드를 사용하여 업로드 프로세스를 시작해야 합니다. “create” 메서드에는 업로드가 완료되면 트리거되는 콜백 함수도 제공해야 합니다.
import { DirectUpload } from "@rails/activestorage" class Uploader { constructor(file, url, token) { const headers = { 'Authentication': `Bearer ${token}` } // 정보: 헤더 전송은 선택적 매개변수입니다. 헤더를 보내지 않으면 쿠키 또는 세션 데이터를 사용하여 인증이 수행됩니다. this.upload = new DirectUpload(this.file, this.url, this, headers) } upload(file) { this.upload.create((error, blob) => { if (error) { // 오류 처리 } else { // blob.signed_id를 다음 요청의 파일 참조로 사용 } }) } directUploadWillStoreFileWithXHR(request) { request.upload.addEventListener("progress", event => this.directUploadDidProgress(event)) } directUploadDidProgress(event) { // event.loaded와 event.total을 사용하여 진행률 표시줄 업데이트 } }
사용자 정의 인증을 구현하려면 다음과 유사한 새 컨트롤러를 Rails 애플리케이션에 만들어야 합니다:
class DirectUploadsController < ActiveStorage::DirectUploadsController skip_forg계속해서 번역문: 사용자 정의 인증을 구현하려면 다음과 유사한 새 컨트롤러를 Rails 애플리케이션에 만들어야 합니다: ```ruby class DirectUploadsController < ActiveStorage::DirectUploadsController skip_forgery_protection before_action :authenticate! def authenticate! @token = request.headers['Authorization']&.split&.last head :unauthorized unless valid_token?(@token) end end
참고: 직접 업로드를 사용하면 파일이 업로드되지만 레코드에 첨부되지 않는 경우가 있습니다. 첨부되지 않은 업로드 정리를 고려해 보세요.
테스트
file_fixture_upload
를 사용하여 통합 또는 컨트롤러 테스트에서 파일 업로드를 테스트할 수 있습니다. Rails는 파일을 다른 매개변수와 마찬가지로 처리합니다.
class SignupController < ActionDispatch::IntegrationTest test "can sign up" do post signup_path, params: { name: "David", avatar: file_fixture_upload("david.png", "image/png") } user = User.order(:created_at).last assert user.avatar.attached? end end
테스트 중 생성된 파일 폐기
시스템 테스트
시스템 테스트는 트랜잭션 롤백을 통해 테스트 데이터를 정리합니다. destroy
가 호출되지 않으므로 첨부 파일이 정리되지 않습니다. 파일을 정리하려면 after_teardown
콜백에서 수행할 수 있습니다. 여기서 수행하면 테스트 중에 생성된 모든 연결이 완료되었고 Active Storage에서 파일을 찾을 수 없다는 오류가 발생하지 않습니다.
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase # ... def after_teardown super FileUtils.rm_rf(ActiveStorage::Blob.service.root) end # ... end
병렬 테스트를 사용하고 DiskService
를 사용하는 경우 각 프로세스가 Active Storage에 대해 자체 폴더를 사용하도록 구성해야 합니다. 그러면 teardown
콜백이 관련 프로세스의 테스트 파일만 삭제합니다.
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase # ... parallelize_setup do |i| ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}" end # ... end
시스템 테스트에서 첨부 파일이 포함된 모델 삭제를 확인하고 Active Job을 사용하는 경우 테스트 환경에서 인라인 큐 어댑터를 사용하도록 설정하여 purge 작업이 즉시 실행되도록 하세요.
# 즉시 작업 처리를 사용하여 즉시 발생하도록 합니다 config.active_job.queue_adapter = :inline
통합 테스트
시스템 테스트와 마찬가지로 통합 테스트 중에 업로드된 파일이 자동으로 정리되지 않습니다. 파일을 정리하려면 teardown
콜백에서 수행할 수 있습니다.
class ActionDispatch::IntegrationTest def after_teardown super FileUtils.rm_rf(ActiveStorage::Blob.service.root) end end
병렬 테스트를 사용하고 Disk 서비스를 사용하는 경우 각 프로세스가 Active Storage에 대해 자체 폴더를 사용하도록 구성해야 합니다. 그러면 teardown
콜백이 관련 프로세스의 테스트 파일만 삭제합니다.
class ActionDispatch::IntegrationTest parallelize_setup do |i| ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}" end end
픽스처에 첨부 파일 추가
기존 픽스처에 첨부 파일을 추가할 수 있습니다. 먼저 별도의 저장 서비스를 만들어야 합니다:
# config/storage.yml test_fixtures: service: Disk root: <%= Rails.root.join("tmp/storage_fixtures") %>
이렇게 하면 Active Storage가 “업로드"할 수 있는 임시 디렉토리를 지정합니다. 이를 정규 test
서비스와 다른 디렉토리로 만들면 테스트 중에 업로드된 파일과 픽스처 파일을 구분할 수 있습니다.
다음으로 Active Storage 클래스에 대한 픽스처 파일을 만듭니다:
# active_storage/attachments.yml david_avatar: name: avatar record: david (User) blob: david_avatar_blob
# active_storage/blobs.yml david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>
그런 다음 픽스처 디렉토리(기본 경로는 test/fixtures/files
)에 해당 파일 이름으로 파일을 넣습니다.
ActiveStorage::FixtureSet
문서에서 자세한 내용을 확인하세요.
모든 것이 설정되면 테스트에서 첨부 파일에 액세스할 수 있습니다:
class UserTest < ActiveSupport::TestCase def test_avatar avatar = users(:david).avatar assert avatar.attached? assert_not_nil avatar.download assert_equal 1000, avatar.byte_size end end
픽스처 정리
테스트에서 업로드된 파일은 각 테스트 종료 시 정리되지만, 픽스처 파일은 한 번만 정리하면 됩니다: 모든 테스트가 완료될 때.
병렬 테스트를 사용하는 경우 parallelize_teardown
을 호출합니다:
class ActiveSupport::TestCase # ... parallelize_teardown do |i| FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root) end # ... end
병렬 테스트를 실행하지 않는 경우 Minitest.after_run
또는 사용 중인 테스트 프레임워크의 해당 기능(예: RSpec의 after(:suite)
)을 사용합니다:
# test_helper.rb Minitest.after_run do FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root) end
서비스 구성
config/storage/test.yml
을 추가하여 테스트 환경에서 사용할 서비스를 구성할 수 있습니다.
service
옵션을 사용할 때 유용합니다.
class User < ApplicationRecord has_one_attached :avatar, service: :s3 end
config/storage/test.yml
이 없으면 config/storage.yml
에 구성된 s3
서비스가 테스트 실행 시에도 사용됩니다.
기본 구성이 사용되어 서비스 공급자에 파일이 업로드됩니다.
이 경우 config/storage/test.yml
을 추가하고 테스트에 Disk 서비스를 사용하도록 s3
서비스를 구성할 수 있습니다.
test: service: Disk root: <%= Rails.root.join("tmp/storage") %> s3: service: Disk root: <%= Rails.root.join("tmp/storage") %>
첨부되지 않은 업로드 정리
파일이 업로드되었지만 레코드에 첨부되지 않은 경우가 있습니다. 직접 업로드를 사용할 때 이런 일이 발생할 수 있습니다. unattached 범위를 사용하여 첨부되지 않은 레코드를 쿼리할 수 있습니다. 아래는 사용자 정의 rake 작업을 사용하는 예입니다.
namespace :active_storage do desc "Purges unattached Active Storage blobs. Run regularly." task purge_unattached: :environment do ActiveStorage::Blob.unattached.where(created_at: ..2.days.ago).find_each(&:purge_later) end end
경고: ActiveStorage::Blob.unattached
가 생성하는 쿼리는 느리고 데이터베이스가 큰 애플리케이션에서 방해가 될 수 있습니다.