Rails Upload Images to S3 and Retrive
Active Storage Overview
This guide covers how to attach files to your Active Tape models.
After reading this guide, y'all 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 employ 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 awarding servers.
- How to clean up files stored during testing.
- How to implement back up for additional storage services.
Chapters
- What is Active Storage?
- Requirements
- Setup
- Deejay Service
- S3 Service (Amazon S3 and S3-compatible APIs)
- Microsoft Azure Storage Service
- Google Deject Storage Service
- Mirror Service
- Public admission
- Attaching Files to Records
-
has_one_attached -
has_many_attached - Attaching File/IO Objects
-
- Removing Files
- Serving Files
- Redirect fashion
- Proxy mode
- Authenticated Controllers
- Downloading Files
- Analyzing Files
- Displaying Images, Videos, and PDFs
- Lazy vs Immediate Loading
- Transforming Images
- Previewing Files
- Direct Uploads
- Usage
- Cross-Origin Resource Sharing (CORS) configuration
- Direct upload JavaScript events
- Example
- Integrating with Libraries or Frameworks
- Testing
- Discarding files created during tests
- Adding attachments to fixtures
- Implementing Support for Other Deject Services
- Purging Unattached Uploads
ane What is Active Storage?
Active Storage facilitates uploading files to a deject 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 epitome uploads or generate image representations of non-prototype uploads like PDFs and videos, and extract metadata from capricious files.
i.i Requirements
Diverse features of Active Storage depend on 3rd-party software which Rails will not install, and must be installed separately:
- libvips v8.vi+ or ImageMagick for image analysis and transformations
- ffmpeg v3.four+ for video previews and ffprobe for video/audio analysis
- poppler or muPDF for PDF previews
Image assay and transformations likewise require the image_processing jewel. Uncomment it in your Gemfile, or add it if necessary:
gem "image_processing" , ">= 1.2" Compared to libvips, ImageMagick is better known and more widely available. However, libvips can exist up to 10x faster and consume 1/ten the retentivity. For JPEG files, this tin be further improved by replacing libjpeg-dev with libjpeg-turbo-dev, which is 2-7x faster.
Earlier yous install and use third-party software, make sure you understand the licensing implications of doing so. MuPDF, in particular, is licensed under AGPL and requires a commercial license for some employ.
2 Setup
Active Storage uses 3 tables in your awarding's database named active_storage_blobs, active_storage_variant_records and active_storage_attachments. Afterward creating a new application (or upgrading your application to Rails 5.2), run bin/rails active_storage:install to generate a migration that creates these tables. Use bin/runway db:migrate to run the migration.
active_storage_attachments is a polymorphic bring together table that stores your model'south course proper name. If your model's form name changes, you will need to run a migration on this tabular array to update the underlying record_type to your model'south new class name.
If you are using UUIDs instead of integers as the primary key on your models you will need to modify the column type of active_storage_attachments.record_id and active_storage_variant_records.id in the generated migration appropriately.
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 3 services named local, test, and amazon:
local : service : Disk root : <%= Rail.root.bring together("storage") %> test : service : Disk root : <%= Rails.root.bring together("tmp/storage") %> amazon : service : S3 access_key_id : " " secret_access_key : " " bucket : " " region : " " # east.g. 'u.s.a.-east-i' Tell Active Storage which service to utilise by setting Runway.application.config.active_storage.service. Because each surround will probable use a dissimilar service, it is recommended to do this on a per-environment basis. To apply the disk service from the previous example in the development surroundings, you would add the following to config/environments/development.rb:
# Store files locally. config . active_storage . service = :local To apply the S3 service in production, you add together the following to config/environments/production.rb:
# Shop files on Amazon S3. config . active_storage . service = :amazon To employ the examination service when testing, you lot add together the post-obit to config/environments/examination.rb:
# Store uploaded files on the local file system in a temporary directory. config . active_storage . service = :exam Continue reading for more information on the congenital-in service adapters (eastward.m. Deejay and S3) and the configuration they require.
Configuration files that are environs-specific will have precedence: in production, for instance, the config/storage/production.yml file (if existent) will take precedence over the config/storage.yml file.
Information technology is recommended to employ Rails.env in the bucket names to further reduce the risk of accidentally destroying product data.
amazon : service : S3 # ... bucket : your_own_bucket-<%= Rail.env %> google : service : GCS # ... bucket : your_own_bucket-<%= Rails.env %> azure : service : AzureStorage # ... container : your_container_name-<%= Runway.env %> 2.1 Disk Service
Declare a Deejay service in config/storage.yml:
local : service : Disk root : <%= Track.root.join("storage") %> ii.2 S3 Service (Amazon S3 and S3-uniform APIs)
To connect to Amazon S3, declare an S3 service in config/storage.yml:
amazon : service : S3 access_key_id : " " secret_access_key : " " region : " " bucket : " " Optionally provide client and upload options:
amazon : service : S3 access_key_id : " " secret_access_key : " " region : " " saucepan : " " http_open_timeout : 0 http_read_timeout : 0 retry_limit : 0 upload : server_side_encryption : " " # 'aws:kms' or 'AES256' Prepare sensible customer HTTP timeouts and retry limits for your application. In sure failure scenarios, the default AWS customer configuration may crusade connections to exist held for upwardly to several minutes and lead to asking queuing.
Add the aws-sdk-s3 jewel to your Gemfile:
gem "aws-sdk-s3" , require: false The core features of Active Storage crave the following permissions: s3:ListBucket, s3:PutObject, s3:GetObject, and s3:DeleteObject. Public admission additionally requires s3:PutObjectAcl. If you have boosted upload options configured such every bit setting ACLs then boosted permissions may be required.
If you desire to use environment variables, standard SDK configuration files, profiles, IAM instance profiles or task roles, yous can omit the access_key_id, secret_access_key, and region keys in the example above. The S3 Service supports all of the authentication options described in the AWS SDK documentation.
To connect to an S3-compatible object storage API such as DigitalOcean Spaces, provide the endpoint:
digitalocean : service : S3 endpoint : https://nyc3.digitaloceanspaces.com access_key_id : ... secret_access_key : ... # ...and other options At that place are many other options available. Yous can cheque them in AWS S3 Customer documentation.
two.iii Microsoft Azure Storage Service
Declare an Azure Storage service in config/storage.yml:
azure : service : AzureStorage storage_account_name : " " storage_access_key : " " container : " " Add the azure-storage-blob gem to your Gemfile:
gem "azure-storage-blob" , require: simulated 2.4 Google Cloud Storage Service
Declare a Google Cloud Storage service in config/storage.yml:
google : service : GCS credentials : <%= Rails.root.bring together("path/to/keyfile.json") %> project : " " bucket : " " Optionally provide a Hash of credentials instead of a keyfile path:
google : service : GCS credentials : type : " service_account" project_id : " " private_key_id : <%= Rails.awarding.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 : " " projection : " " bucket : " " Optionally provide a Cache-Control metadata to gear up on uploaded assets:
google : service : GCS ... cache_control : " public, max-historic period=3600" Optionally employ IAM instead of the credentials when signing URLs. This is useful if you are authenticating your GKE applications with Workload Identity, see this Google Cloud blog mail for more than data.
google : service : GCS ... iam : true Optionally utilize a specific GSA when signing URLs. When using IAM, the metadata server will be contacted to go the GSA email, but this metadata server is not always present (e.chiliad. local tests) and you may wish to apply a non-default GSA.
google : service : GCS ... iam : true gsa_email : " foobar@baz.iam.gserviceaccount.com" Add the google-cloud-storage gem to your Gemfile:
gem "google-cloud-storage" , "~> 1.xi" , require: faux 2.5 Mirror Service
You lot can keep multiple services in sync by defining a mirror service. A mirror service replicates uploads and deletes across ii or more subordinate services.
A mirror service is intended to be used temporarily during a migration between services in product. You tin can outset mirroring to a new service, copy pre-existing files from the old service to the new, so go all-in on the new service.
Mirroring is non atomic. Information technology is possible for an upload to succeed on the master service and fail on any of the subordinate services. Before going all-in on a new service, verify that all files take been copied.
Define each of the services you'd like to mirror as described above. Reference them by name when defining a mirror service:
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 : " " saucepan : " " product : service : Mirror primary : s3_east_coast mirrors : - s3_west_coast Although all secondary services receive uploads, downloads are always handled past the master service.
Mirror services are compatible with direct uploads. New files are directly uploaded to the principal service. When a directly-uploaded file is attached to a record, a groundwork job is enqueued to re-create it to the secondary services.
two.6 Public access
By default, Active Storage assumes private access to services. This means generating signed, single-employ URLs for blobs. If you'd rather make blobs publicly accessible, specify public: true in your app's config/storage.yml:
gcs : &gcs service : GCS project : " " private_gcs : << : *gcs credentials : <%= Rails.root.join("path/to/private_keyfile.json") %> saucepan : " " public_gcs : << : *gcs credentials : <%= Track.root.join("path/to/public_keyfile.json") %> bucket : " " public : true Make sure your buckets are properly configured for public access. See docs on how to enable public read permissions for Amazon S3, Google Cloud Storage, and Microsoft Azure storage services. Amazon S3 additionally requires that y'all have the s3:PutObjectAcl permission.
When converting an existing awarding to use public: true, make sure to update every private file in the saucepan to be publicly-readable before switching over.
iii Attaching Files to Records
3.1 has_one_attached
The has_one_attached macro sets upward a 1-to-one mapping between records and files. Each tape tin have one file attached to it.
For example, suppose your application has a User model. If you want each user to accept an avatar, define the User model as follows:
grade User < ApplicationRecord has_one_attached :avatar cease or if you are using Rails half-dozen.0+, you can run a model generator command like this:
bin / track generate model User avatar :zipper Y'all can create a user with an avatar:
<%= form . file_field :avatar %> form SignupController < ApplicationController def create user = User . create! ( user_params ) session [ :user_id ] = user . id redirect_to root_path end private def user_params params . crave ( :user ). permit ( :email_address , :countersign , :avatar ) finish end Phone call avatar.attach to attach an avatar to an existing user:
user . avatar . attach ( params [ :avatar ]) Call avatar.attached? to determine whether a detail user has an avatar:
In some cases you lot might want to override a default service for a specific attachment. You can configure specific services per attachment using the service choice:
class User < ApplicationRecord has_one_attached :avatar , service: :s3 end You can configure specific variants per attachment by calling the variant method on yielded attachable object:
class User < ApplicationRecord has_one_attached :avatar do | attachable | attachable . variant :thumb , resize_to_limit: [ 100 , 100 ] end end Call avatar.variant(:thumb) to get a thumb variant of an avatar:
<%= image_tag user . avatar . variant ( :thumb ) %> three.ii has_many_attached
The has_many_attached macro sets upwards a i-to-many relationship between records and files. Each record can have many files attached to it.
For instance, suppose your application has a Message model. If yous desire each message to take many images, ascertain the Message model as follows:
grade Message < ApplicationRecord has_many_attached :images stop or if you are using Rails six.0+, you can run a model generator command like this:
bin / rails generate model Message images :attachments You can create a message with images:
class MessagesController < ApplicationController def create message = Message . create! ( message_params ) redirect_to message cease private def message_params params . require ( :message ). permit ( :title , :content , images: []) terminate terminate Call images.adhere to add new images to an existing bulletin:
@bulletin . images . attach ( params [ :images ]) Telephone call images.attached? to decide whether a detail bulletin has any images:
@bulletin . images . attached? Overriding the default service is done the same way as has_one_attached, by using the service selection:
class Message < ApplicationRecord has_many_attached :images , service: :s3 end Configuring specific variants is done the same way as has_one_attached, past calling the variant method on the yielded attachable object:
class Message < ApplicationRecord has_many_attached :images do | attachable | attachable . variant :pollex , resize_to_limit: [ 100 , 100 ] end end 3.3 Attaching File/IO Objects
Sometimes you need to adhere a file that doesn't arrive via an HTTP request. For instance, you may want to attach a file you lot generated on disk or downloaded from a user-submitted URL. You may likewise want to attach a fixture file in a model test. To do that, provide a Hash containing at least an open up IO object and a filename:
@message . images . attach ( io: File . open up ( '/path/to/file' ), filename: 'file.pdf' ) When possible, provide a content type as well. Active Storage attempts to make up one's mind a file's content blazon from its information. Information technology falls dorsum to the content type you provide if it tin can't practise that.
@message . images . attach ( io: File . open ( '/path/to/file' ), filename: 'file.pdf' , content_type: 'application/pdf' ) Yous tin featherbed the content type inference from the data by passing in identify: false forth with the content_type.
@message . images . attach ( io: File . open up ( '/path/to/file' ), filename: 'file.pdf' , content_type: 'application/pdf' , identify: false ) If you lot don't provide a content type and Active Storage can't determine the file's content blazon automatically, it defaults to application/octet-stream.
4 Removing Files
To remove an zipper from a model, call purge on the attachment. If your application is set up to use Active Job, removal can be done in the background instead by calling purge_later. Purging deletes the blob and the file from the storage service.
# Synchronously destroy the avatar and actual resources files. user . avatar . purge # Destroy the associated models and actual resource files async, via Active Job. user . avatar . purge_later 5 Serving Files
Agile Storage supports ii ways to serve files: redirecting and proxying.
All Active Storage controllers are publicly accessible by default. The generated URLs are hard to judge, but permanent by design. If your files require a higher level of protection consider implementing Authenticated Controllers.
v.1 Redirect way
To generate a permanent URL for a blob, you can pass the blob to the url_for view helper. This generates a URL with the blob's signed_id that is routed to the blob's RedirectController
url_for ( user . avatar ) # => /rails/active_storage/blobs/:signed_id/my-avatar.png The RedirectController redirects to the actual service endpoint. This indirection decouples the service URL from the actual one, and allows, for example, mirroring attachments in different services for loftier-availability. The redirection has an HTTP expiration of v minutes.
To create a download link, use the rails_blob_{path|url} helper. Using this helper allows you lot to prepare the disposition.
rails_blob_path ( user . avatar , disposition: "attachment" ) To prevent XSS attacks, Active Storage forces the Content-Disposition header to "attachment" for some kind of files. To alter this behaviour see the available configuration options in Configuring Track Applications.
If y'all need to create a link from outside of controller/view context (Groundwork jobs, Cronjobs, etc.), you can access the rails_blob_path similar this:
Track . awarding . routes . url_helpers . rails_blob_path ( user . avatar , only_path: truthful ) five.ii Proxy fashion
Optionally, files tin be proxied instead. This means that your application servers will download file data from the storage service in response to requests. This can be useful for serving files from a CDN.
You lot can configure Active Storage to use proxying by default:
# config/initializers/active_storage.rb Rails . awarding . config . active_storage . resolve_model_to_route = :rails_storage_proxy Or if you lot want to explicitly proxy specific attachments there are URL helpers you tin use in the class of rails_storage_proxy_path and rails_storage_proxy_url.
<%= image_tag rails_storage_proxy_path ( @user . avatar ) %> v.2.1 Putting a CDN in forepart of Agile Storage
Additionally, in order to apply a CDN for Agile Storage attachments, yous will need to generate URLs with proxy fashion so that they are served by your app and the CDN will cache the attachment without any extra configuration. This works out of the box because the default Active Storage proxy controller sets an HTTP header indicating to the CDN to cache the response.
You should besides make sure that the generated URLs utilize the CDN host instead of your app host. There are multiple ways to attain this, only in general it involves tweaking your config/routes.rb file so that you can generate the proper URLs for the attachments and their variations. As an example, you could add this:
# config/routes.rb straight :cdn_image practise | model , options | if model . respond_to? ( :signed_id ) route_for ( :rails_service_blob_proxy , model . signed_id , model . filename , options . merge ( host: ENV [ 'CDN_HOST' ]) ) else signed_blob_id = model . hulk . signed_id 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 cease and and then generate routes like this:
<%= cdn_image_url ( user . avatar . variant ( resize_to_limit: [ 128 , 128 ])) %> five.3 Authenticated Controllers
All Agile Storage controllers are publicly accessible by default. The generated URLs utilise a evidently signed_id, making them difficult to guess but permanent. Anyone that knows the blob URL will be able to access it, even if a before_action in your ApplicationController would otherwise require a login. If your files crave a college level of protection, yous can implement your own authenticated controllers, based on the ActiveStorage::Blobs::RedirectController, ActiveStorage::Blobs::ProxyController, ActiveStorage::Representations::RedirectController and ActiveStorage::Representations::ProxyController
To merely allow an account to admission their ain logo yous could do the post-obit:
# config/routes.rb resource :business relationship exercise resource :logo end # app/controllers/logos_controller.rb class LogosController < ApplicationController # Through ApplicationController: # include Cosign, SetCurrentAccount def show redirect_to Electric current . business relationship . logo . url end end <%= image_tag account_logo_path %> And and so you might want to disable the Agile Storage default routes with:
config . active_storage . draw_routes = fake to forestall files being accessed with the publicly attainable URLs.
half-dozen Downloading Files
Sometimes you demand to process a blob after it's uploaded—for example, to convert it to a unlike format. Use the attachment's download method to read a blob's binary data into memory:
binary = user . avatar . download You might want to download a blob to a file on disk then an external program (eastward.1000. a virus scanner or media transcoder) can operate on it. Utilise the attachment'southward open method to download a blob to a tempfile on disk:
message . video . open practice | file | system '/path/to/virus/scanner' , file . path # ... end It's of import to know that the file is non however bachelor in the after_create callback but in the after_create_commit only.
vii Analyzing Files
Active Storage analyzes files in one case they've been uploaded by queuing a task in Active Job. Analyzed files volition store boosted information in the metadata hash, including analyzed: true. You can check whether a hulk has been analyzed by calling analyzed? on it.
Prototype analysis provides width and height attributes. Video assay provides these, likewise as duration, angle, display_aspect_ratio, and video and audio booleans to indicate the presence of those channels. Audio assay provides duration and bit_rate attributes.
8 Displaying Images, Videos, and PDFs
Active Storage supports representing a variety of files. Y'all can telephone call representation on an zipper to display an prototype variant, or a preview of a video or PDF. Before calling representation, check if the zipper tin be represented by calling representable?. Some file formats can't be previewed past Agile Storage out of the box (e.g. Word documents); if representable? returns false y'all may want to link to the file instead.
<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: "zipper" ) practise %> <%= image_tag "placeholder.png" , alt: "Download file" %> <% terminate %> <% end %> </li> <% end %> </ul> Internally, representation calls variant for images, and preview for previewable files. Y'all can too phone call these methods direct.
8.1 Lazy vs Immediate Loading
By default, Agile Storage will process representations lazily. This code:
image_tag file . representation ( resize_to_limit: [ 100 , 100 ]) Will generate an <img> tag with the src pointing to the ActiveStorage::Representations::RedirectController. The browser will brand a asking to that controller, which will return a 302 redirect to the file on the remote service (or in proxy style, return the file contents). Loading the file lazily allows features like single use URLs to piece of work without slowing down your initial page loads.
This works fine for most cases.
If you want to generate URLs for images immediately, you can call .processed.url:
image_tag file . representation ( resize_to_limit: [ 100 , 100 ]). candy . url The Active Storage variant tracker improves performance of this, by storing a tape in the database if the requested representation has been candy before. Thus, the above lawmaking will only make an API call to the remote service (e.g. S3) once, and in one case a variant is stored, will use that. The variant tracker runs automatically, just can be disabled through config.active_storage.track_variants.
If you're rendering lots of images on a page, the above example could result in North+one queries loading all the variant records. To avoid these Due north+1 queries, utilise the named scopes on ActiveStorage::Attachment.
message . images . with_all_variant_records . each practise | file | image_tag file . representation ( resize_to_limit: [ 100 , 100 ]). processed . url end viii.2 Transforming Images
Transforming images allows you to display the paradigm at your pick of dimensions. To create a variation of an image, call variant on the zipper. You can pass whatever transformation supported by the variant processor to the method. When the browser hits the variant URL, Agile Storage will lazily transform the original blob into the specified format and redirect to its new service location.
<%= image_tag user . avatar . variant ( resize_to_limit: [ 100 , 100 ]) %> If a variant is requested, Agile Storage volition automatically apply transformations depending on the prototype'south format:
-
Content types that are variable (equally dictated by
config.active_storage.variable_content_types) and not considered spider web images (as dictated pastconfig.active_storage.web_image_content_types), will be converted to PNG. -
If
qualityis non specified, the variant processor's default quality for the format volition exist used.
Active Storage can apply either Vips or MiniMagick every bit the variant processor. The default depends on your config.load_defaults target version, and the processor can be changed by setting config.active_storage.variant_processor.
The two processors are not fully uniform, so when migrating an existing awarding betwixt MiniMagick and Vips, some changes have to be made if using options that are format specific:
<!-- 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: truthful , quality: 80 }) %> 8.3 Previewing Files
Some not-epitome files can be previewed: that is, they can exist presented as images. For case, a video file tin exist previewed past extracting its beginning frame. Out of the box, Active Storage supports previewing videos and PDF documents. To create a link to a lazily-generated preview, use the attachment'due south preview method:
<%= image_tag message . video . preview ( resize_to_limit: [ 100 , 100 ]) %> To add together support for another format, add your own previewer. See the ActiveStorage::Preview documentation for more information.
9 Direct Uploads
Active Storage, with its included JavaScript library, supports uploading directly from the client to the deject.
nine.1 Usage
-
Include
activestorage.jsin your awarding's JavaScript bundle.Using the nugget pipeline:
//= require activestorageUsing the npm package:
import * as ActiveStorage from " @runway/activestorage " ActiveStorage . start () -
Add
direct_upload: trueto your file field:<%= form . file_field :attachments , multiple: truthful , direct_upload: true %>Or, if you aren't using a
FormBuilder, add the data attribute directly:<input blazon= file data-direct-upload-url= " <%= rails_direct_uploads_url %> " /> -
Configure CORS on third-party storage services to allow direct upload requests.
-
That's information technology! Uploads begin upon grade submission.
9.2 Cross-Origin Resource Sharing (CORS) configuration
To brand direct uploads to a third-party service piece of work, you'll need to configure the service to allow cross-origin requests from your app. Consult the CORS documentation for your service:
- S3
- Google Cloud Storage
- Azure Storage
Take care to permit:
- All origins from which your app is accessed
- The
PUTrequest method - The following headers:
-
Origin -
Content-Blazon -
Content-MD5 -
Content-Disposition(except for Azure Storage) -
x-ms-blob-content-disposition(for Azure Storage simply) -
x-ms-blob-type(for Azure Storage simply) -
Cache-Control(for GCS, only ifcache_controlis fix)
-
No CORS configuration is required for the Disk service since information technology shares your app's origin.
nine.2.1 Example: S3 CORS configuration
[ { "AllowedHeaders" : [ "*" ], "AllowedMethods" : [ "PUT" ], "AllowedOrigins" : [ "https://world wide web.example.com" ], "ExposeHeaders" : [ "Origin" , "Content-Type" , "Content-MD5" , "Content-Disposition" ], "MaxAgeSeconds" : 3600 } ] 9.2.2 Example: Google Cloud Storage CORS configuration
[ { "origin" : [ "https://www.example.com" ], "method" : [ "PUT" ], "responseHeader" : [ "Origin" , "Content-Type" , "Content-MD5" , "Content-Disposition" ], "maxAgeSeconds" : 3600 } ] 9.2.3 Case: Azure Storage CORS configuration
<Cors> <CorsRule> <AllowedOrigins>https://www.example.com</AllowedOrigins> <AllowedMethods>PUT</AllowedMethods> <AllowedHeaders>Origin, Content-Type, Content-MD5, 10-ms-blob-content-disposition, 10-ms-blob-type</AllowedHeaders> <MaxAgeInSeconds>3600</MaxAgeInSeconds> </CorsRule> </Cors> 9.3 Direct upload JavaScript events
| Consequence name | Event target | Event data (event.particular) | Description |
|---|---|---|---|
directly-uploads:start | <form> | None | A grade containing files for direct upload fields was submitted. |
straight-upload:initialize | <input> | {id, file} | Dispatched for every file afterward form submission. |
direct-upload:start | <input> | {id, file} | A direct upload is starting. |
directly-upload:before-blob-asking | <input> | {id, file, xhr} | Before making a request to your application for straight upload metadata. |
straight-upload:before-storage-request | <input> | {id, file, xhr} | Before making a request to store a file. |
directly-upload:progress | <input> | {id, file, progress} | Equally requests to store files progress. |
direct-upload:error | <input> | {id, file, error} | An error occurred. An alert will brandish unless this event is canceled. |
direct-upload:end | <input> | {id, file} | A direct upload has ended. |
directly-uploads:end | <grade> | None | All direct uploads have concluded. |
nine.iv Example
You lot tin can apply these events to bear witness the progress of an upload.
To testify the uploaded files in a form:
// direct_uploads.js addEventListener ( " straight-upload:initialize " , event => { const { target , detail } = upshot const { id , file } = detail target . insertAdjacentHTML ( " beforebegin " , ` <div id="direct-upload- ${ id } " form="direct-upload directly-upload--awaiting"> <div id="direct-upload-progress- ${ id } " form="directly-upload__progress" way="width: 0%"></div> <span class="direct-upload__filename"></span> </div> ` ) target . previousElementSibling . querySelector ( `.directly-upload__filename` ). textContent = file . name }) addEventListener ( " straight-upload:showtime " , event => { const { id } = event . particular const element = document . getElementById ( `direct-upload- ${ id } ` ) element . classList . remove ( " direct-upload--awaiting " ) }) addEventListener ( " directly-upload:progress " , event => { const { id , progress } = event . detail const progressElement = document . getElementById ( `direct-upload-progress- ${ id } ` ) progressElement . fashion . width = ` ${ progress } %` }) addEventListener ( " straight-upload:fault " , outcome => { issue . preventDefault () const { id , error } = event . detail const element = document . getElementById ( `direct-upload- ${ id } ` ) chemical element . classList . add ( " straight-upload--error " ) element . setAttribute ( " title " , error ) }) addEventListener ( " direct-upload:stop " , event => { const { id } = event . item const element = certificate . getElementById ( `directly-upload- ${ id } ` ) element . classList . add together ( " direct-upload--complete " ) }) Add styles:
/* 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.iii ); border-radius : 3px ; font-size : 11px ; line-pinnacle : 13px ; } .direct-upload--pending { opacity : 0.6 ; } .straight-upload__progress { position : absolute ; meridian : 0 ; left : 0 ; bottom : 0 ; opacity : 0.two ; groundwork : #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 ; } .straight-upload--error { border-color : red ; } input [ type = file ][ data-direct-upload-url ][ disabled ] { display : none ; } 9.5 Integrating with Libraries or Frameworks
If yous want to use the Direct Upload feature from a JavaScript framework, or yous want to integrate custom drag and driblet solutions, you tin use the DirectUpload class for this purpose. Upon receiving a file from your library of pick, instantiate a DirectUpload and telephone call its create method. Create takes a callback to invoke when the upload completes.
import { DirectUpload } from " @runway/activestorage " const input = document . querySelector ( ' input[type=file] ' ) // Bind to file drop - use the ondrop on a parent element or use a // library like Dropzone const onDrop = ( event ) => { consequence . preventDefault () const files = event . dataTransfer . files ; Assortment . from ( files ). forEach ( file => uploadFile ( file )) } // Bind to normal file selection input . addEventListener ( ' modify ' , ( event ) => { Array . from ( input . files ). forEach ( file => uploadFile ( file )) // you might articulate the selected files from the input input . value = null }) const uploadFile = ( file ) => { // your class needs the file_field direct_upload: true, which // provides data-direct-upload-url const url = input . dataset . directUploadUrl const upload = new DirectUpload ( file , url ) upload . create (( fault , blob ) => { if ( error ) { // Handle the error } else { // Add together an appropriately-named subconscious input to the course with a // value of blob.signed_id so that the blob ids will exist // transmitted in the normal upload flow const hiddenField = document . createElement ( ' input ' ) hiddenField . setAttribute ( " type " , " hidden " ); hiddenField . setAttribute ( " value " , blob . signed_id ); hiddenField . proper name = input . name document . querySelector ( ' form ' ). appendChild ( hiddenField ) } }) } If you demand to track the progress of the file upload, you can pass a third parameter to the DirectUpload constructor. During the upload, DirectUpload volition call the object's directUploadWillStoreFileWithXHR method. Y'all can so bind your own progress handler on the 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 (( fault , hulk ) => { if ( error ) { // Handle the mistake } else { // Add together an appropriately-named hidden input to the class // with a value of blob.signed_id } }) } directUploadWillStoreFileWithXHR ( request ) { request . upload . addEventListener ( " progress " , consequence => this . directUploadDidProgress ( event )) } directUploadDidProgress ( upshot ) { // Use event.loaded and event.total to update the progress bar } } Using Direct Uploads tin sometimes consequence in a file that uploads, simply never attaches to a record. Consider purging unattached uploads.
10 Testing
Utilise fixture_file_upload to examination uploading a file in an integration or controller examination. Rails handles files similar any other parameter.
class SignupController < ActionDispatch :: IntegrationTest test "can sign upward" do mail service signup_path , params: { proper name: "David" , avatar: fixture_file_upload ( "david.png" , "image/png" ) } user = User . gild ( :created_at ). last assert user . avatar . attached? cease cease 10.1 Discarding files created during tests
10.i.one System tests
System tests clean up test information by rolling back a transaction. Considering destroy is never called on an object, the attached files are never cleaned up. If you want to clear the files, you lot can do it in an after_teardown callback. Doing it here ensures that all connections created during the test are complete and yous won't receive an mistake from Active Storage saying information technology tin can't observe a file.
class ApplicationSystemTestCase < ActionDispatch :: SystemTestCase # ... def after_teardown super FileUtils . rm_rf ( ActiveStorage :: Hulk . service . root ) end # ... end If you're using parallel tests and the DiskService, yous should configure each process to use its own folder for Active Storage. This way, the teardown callback will merely delete files from the relevant process' tests.
class ApplicationSystemTestCase < ActionDispatch :: SystemTestCase # ... parallelize_setup do | i | ActiveStorage :: Blob . service . root = " #{ ActiveStorage :: Blob . service . root } - #{ i } " terminate # ... stop If your system tests verify the deletion of a model with attachments and y'all're using Active Job, set your test environment to use the inline queue adapter so the purge job is executed immediately rather at an unknown time in the future.
# Use inline job processing to make things happen immediately config . active_job . queue_adapter = :inline ten.1.two Integration tests
Similarly to Organization Tests, files uploaded during Integration Tests will not be automatically cleaned up. If you want to clear the files, you can practise it in an teardown callback.
class ActionDispatch::IntegrationTest def after_teardown super FileUtils . rm_rf ( ActiveStorage :: Blob . service . root ) end stop If you're using parallel tests and the Disk service, you should configure each process to use its own folder for Agile Storage. This way, the teardown callback will only delete files from the relevant process' tests.
grade ActionDispatch::IntegrationTest parallelize_setup do | i | ActiveStorage :: Blob . service . root = " #{ ActiveStorage :: Blob . service . root } - #{ i } " end finish 10.2 Adding attachments to fixtures
You can add attachments to your existing fixtures. Commencement, you'll want to create a dissever storage service:
# config/storage.yml test_fixtures : service : Disk root : <%= Track.root.join("tmp/storage_fixtures") %> This tells Active Storage where to "upload" fixture files to, so information technology should be a temporary directory. By making it a different directory to your regular test service, you tin separate fixture files from files uploaded during a test.
Next, create fixture files for the Agile Storage classes:
# 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" % > Then put a file in your fixtures directory (the default path is examination/fixtures/files) with the corresponding filename. See the ActiveStorage::FixtureSet docs for more data.
One time everything is set up, you'll be able to access attachments in your tests:
class UserTest < ActiveSupport :: TestCase def test_avatar avatar = users ( :david ). avatar assert avatar . attached? assert_not_nil avatar . download assert_equal g , avatar . byte_size stop end 10.2.1 Cleaning upwards fixtures
While files uploaded in tests are cleaned up at the end of each test, you lot only need to make clean up fixture files in one case: when all your tests complete.
If you're using parallel tests, call parallelize_teardown:
grade ActiveSupport::TestCase # ... parallelize_teardown do | i | FileUtils . rm_rf ( ActiveStorage :: Blob . services . fetch ( :test_fixtures ). root ) end # ... end If you're not running parallel tests, apply Minitest.after_run or the equivalent for your test framework (due east.g. afterwards(:suite) for RSpec):
# test_helper.rb Minitest . after_run do FileUtils . rm_rf ( ActiveStorage :: Blob . services . fetch ( :test_fixtures ). root ) end eleven Implementing Support for Other Cloud Services
If you need to support a deject service other than these, you will need to implement the Service. Each service extends ActiveStorage::Service by implementing the methods necessary to upload and download files to the cloud.
12 Purging Unattached Uploads
There are cases where a file is uploaded but never attached to a record. This tin can happen when using Straight Uploads. You tin query for unattached records using the unattached scope. Below is an instance using a custom rake task.
namespace :active_storage exercise desc "Purges unattached Active Storage blobs. Run regularly." task purge_unattached: :environment exercise ActiveStorage :: Blob . unattached . where ( "active_storage_blobs.created_at <= ?" , 2 . days . ago ). find_each ( & :purge_later ) cease finish The query generated by ActiveStorage::Hulk.unattached tin be slow and potentially disruptive on applications with larger databases.
Source: https://guides.rubyonrails.org/kindle/active_storage_overview.html
Post a Comment for "Rails Upload Images to S3 and Retrive"