**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** Active Storage Overview ======================= This guide covers how to attach files to your Active Record models. After reading this guide, you will know: * How to attach one or many files to a record. * How to delete an attached file. * How to link to an attached file. * How to use variants to transform images. * How to generate an image representation of a non-image file, such as a PDF or a video. * How to send file uploads directly from browsers to a storage service, bypassing your application servers. * How to clean up files stored during testing. * How to implement support for additional storage services. -------------------------------------------------------------------------------- What is Active Storage? ----------------------- Active Storage facilitates uploading files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects. It comes with a local disk-based service for development and testing and supports mirroring files to subordinate services for backups and migrations. Using Active Storage, an application can transform image uploads with [ImageMagick](https://www.imagemagick.org), generate image representations of non-image uploads like PDFs and videos, and extract metadata from arbitrary files. ## Setup Active Storage uses two tables in your application’s database named `active_storage_blobs` and `active_storage_attachments`. After creating a new application (or upgrading your application to Rails 5.2), run `rails active_storage:install` to generate a migration that creates these tables. Use `rails db:migrate` to run the migration. Declare Active Storage services in `config/storage.yml`. For each service your application uses, provide a name and the requisite configuration. The example below declares three services named `local`, `test`, and `amazon`: ```yaml 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: "" ``` Tell Active Storage which service to use by setting `Rails.application.config.active_storage.service`. Because each environment will likely use a different service, it is recommended to do this on a per-environment basis. To use the disk service from the previous example in the development environment, you would add the following to `config/environments/development.rb`: ```ruby # Store files locally. config.active_storage.service = :local ``` To use the Amazon S3 service in production, you add the following to `config/environments/production.rb`: ```ruby # Store files on Amazon S3. config.active_storage.service = :amazon ``` Continue reading for more information on the built-in service adapters (e.g. `Disk` and `S3`) and the configuration they require. ### Disk Service Declare a Disk service in `config/storage.yml`: ```yaml local: service: Disk root: <%= Rails.root.join("storage") %> ``` ### Amazon S3 Service Declare an S3 service in `config/storage.yml`: ```yaml amazon: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" ``` Add the [`aws-sdk-s3`](https://github.com/aws/aws-sdk-ruby) gem to your `Gemfile`: ```ruby gem "aws-sdk-s3", require: false ``` NOTE: The core features of Active Storage require the following permissions: `s3:ListBucket`, `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject`. If you have additional upload options configured such as setting ACLs then additional permissions may be required. NOTE: If you want to use environment variables, standard SDK configuration files, profiles, IAM instance profiles or task roles, you can omit the `access_key_id`, `secret_access_key`, and `region` keys in the example above. The Amazon S3 Service supports all of the authentication options described in the [AWS SDK documentation] (https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html). ### Microsoft Azure Storage Service Declare an Azure Storage service in `config/storage.yml`: ```yaml azure: service: AzureStorage storage_account_name: "" storage_access_key: "" container: "" ``` Add the [`azure-storage`](https://github.com/Azure/azure-storage-ruby) gem to your `Gemfile`: ```ruby gem "azure-storage", require: false ``` ### Google Cloud Storage Service Declare a Google Cloud Storage service in `config/storage.yml`: ```yaml google: service: GCS credentials: <%= Rails.root.join("path/to/keyfile.json") %> project: "" bucket: "" ``` Optionally provide a Hash of credentials instead of a keyfile path: ```yaml 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: "" ``` Add the [`google-cloud-storage`](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-storage) gem to your `Gemfile`: ```ruby gem "google-cloud-storage", "~> 1.8", require: false ``` ### Mirror Service You can keep multiple services in sync by defining a mirror service. When a file is uploaded or deleted, it's done across all the mirrored services. Mirrored services can be used to facilitate a migration between services in production. You can start mirroring to the new service, copy existing files from the old service to the new, then go all-in on the new service. Define each of the services you'd like to use as described above and reference them from a mirrored service. ```yaml 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 ``` NOTE: Files are served from the primary service. NOTE: This is not compatible with the [direct uploads](#direct-uploads) feature. Attaching Files to Records -------------------------- ### `has_one_attached` The `has_one_attached` macro sets up a one-to-one mapping between records and files. Each record can have one file attached to it. For example, suppose your application has a `User` model. If you want each user to have an avatar, define the `User` model like this: ```ruby class User < ApplicationRecord has_one_attached :avatar end ``` You can create a user with an avatar: ```erb <%= form.file_field :avatar %> ``` ```ruby 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 ``` Call `avatar.attach` to attach an avatar to an existing user: ```ruby user.avatar.attach(params[:avatar]) ``` Call `avatar.attached?` to determine whether a particular user has an avatar: ```ruby user.avatar.attached? ``` ### `has_many_attached` The `has_many_attached` macro sets up a one-to-many relationship between records and files. Each record can have many files attached to it. For example, suppose your application has a `Message` model. If you want each message to have many images, define the `Message` model like this: ```ruby class Message < ApplicationRecord has_many_attached :images end ``` You can create a message with images: ```ruby 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 ``` Call `images.attach` to add new images to an existing message: ```ruby @message.images.attach(params[:images]) ``` Call `images.attached?` to determine whether a particular message has any images: ```ruby @message.images.attached? ``` ### Attaching File/IO Objects Sometimes you need to attach a file that doesn’t arrive via an HTTP request. For example, you may want to attach a file you generated on disk or downloaded from a user-submitted URL. You may also want to attach a fixture file in a model test. To do that, provide a Hash containing at least an open IO object and a filename: ```ruby @message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf') ``` When possible, provide a content type as well. Active Storage attempts to determine a file’s content type from its data. It falls back to the content type you provide if it can’t do that. ```ruby @message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf') ``` You can bypass the content type inference from the data by passing in `identify: false` along with the `content_type`. ```ruby @message.image.attach( io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf', identify: false ) ``` If you don’t provide a content type and Active Storage can’t determine the file’s content type automatically, it defaults to application/octet-stream. Removing Files -------------- To remove an attachment from a model, call `purge` on the attachment. Removal can be done in the background if your application is setup to use Active Job. Purging deletes the blob and the file from the storage service. ```ruby # Synchronously destroy the avatar and actual resource files. user.avatar.purge # Destroy the associated models and actual resource files async, via Active Job. user.avatar.purge_later ``` Linking to Files ---------------- Generate a permanent URL for the blob that points to the application. Upon access, a redirect to the actual service endpoint is returned. This indirection decouples the public URL from the actual one, and allows, for example, mirroring attachments in different services for high-availability. The redirection has an HTTP expiration of 5 min. ```ruby url_for(user.avatar) ``` To create a download link, use the `rails_blob_{path|url}` helper. Using this helper allows you to set the disposition. ```ruby rails_blob_path(user.avatar, disposition: "attachment") ``` If you need to create a link from outside of controller/view context (Background jobs, Cronjobs, etc.), you can access the rails_blob_path like this: ``` Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true) ``` Downloading Files ----------------- Sometimes you need to process a blob after it’s uploaded—for example, to convert it to a different format. Use `ActiveStorage::Blob#download` to read a blob’s binary data into memory: ```ruby binary = user.avatar.download ``` You might want to download a blob to a file on disk so an external program (e.g. a virus scanner or media transcoder) can operate on it. Use `ActiveStorage::Blob#open` to download a blob to a tempfile on disk: ```ruby message.video.open do |file| system '/path/to/virus/scanner', file.path # ... end ``` Transforming Images ------------------- To create a variation of the image, call `variant` on the `Blob`. You can pass any transformation to the method supported by the processor. The default processor is [MiniMagick](https://github.com/minimagick/minimagick), but you can also use [Vips](http://www.rubydoc.info/gems/ruby-vips/Vips/Image). To enable variants, add the `image_processing` gem to your `Gemfile`: ```ruby gem 'image_processing', '~> 1.2' ``` When the browser hits the variant URL, Active Storage will lazily transform the original blob into the specified format and redirect to its new service location. ```erb <%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %> ``` To switch to the Vips processor, you would add the following to `config/application.rb`: ```ruby # Use Vips for processing variants. config.active_storage.variant_processor = :vips ``` Previewing Files ---------------- Some non-image files can be previewed: that is, they can be presented as images. For example, a video file can be previewed by extracting its first frame. Out of the box, Active Storage supports previewing videos and PDF documents. ```erb