banner



Activestorage Direct Upload Without Javascript?

Active Storage Overview

This guide covers how to attach files to your Active Tape models.

Afterwards 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 ship 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.

Chapters

  1. What is Active Storage?
    • Requirements
  2. Setup
    • Disk Service
    • S3 Service (Amazon S3 and S3-uniform APIs)
    • Microsoft Azure Storage Service
    • Google Cloud Storage Service
    • Mirror Service
    • Public admission
  3. Attaching Files to Records
    • has_one_attached
    • has_many_attached
    • Attaching File/IO Objects
  4. Removing Files
  5. Serving Files
    • Redirect mode
    • Proxy mode
    • Authenticated Controllers
  6. Downloading Files
  7. Analyzing Files
  8. Displaying Images, Videos, and PDFs
    • Lazy vs Immediate Loading
    • Transforming Images
    • Previewing Files
  9. Direct Uploads
    • Usage
    • Cross-Origin Resource Sharing (CORS) configuration
    • Directly upload JavaScript events
    • Example
    • Integrating with Libraries or Frameworks
  10. Testing
    • Discarding files created during tests
    • Adding attachments to fixtures
  11. Implementing Support for Other Cloud Services
  12. Purging Unattached Uploads

1 What is Agile Storage?

Agile 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 deejay-based service for evolution and testing and supports mirroring files to subordinate services for backups and migrations.

Using Active Storage, an application tin can transform image uploads or generate image representations of non-prototype uploads similar PDFs and videos, and extract metadata from arbitrary files.

1.ane Requirements

Diverse features of Agile Storage depend on third-party software which Rail will non install, and must be installed separately:

  • libvips v8.6+ or ImageMagick for prototype analysis and transformations
  • ffmpeg v3.4+ for video previews and ffprobe for video/sound analysis
  • poppler or muPDF for PDF previews

Image analysis and transformations besides require the image_processing gem. Uncomment information technology in your Gemfile, or add it if necessary:

                          gem              "image_processing"              ,              ">= 1.2"                      

Compared to libvips, ImageMagick is better known and more widely bachelor. All the same, libvips can be up to 10x faster and consume 1/10 the retentiveness. For JPEG files, this can be farther improved past replacing libjpeg-dev with libjpeg-turbo-dev, which is ii-7x faster.

Earlier you lot install and use third-party software, brand certain you understand the licensing implications of doing so. MuPDF, in particular, is licensed nether AGPL and requires a commercial license for some use.

ii Setup

Active Storage uses iii tables in your application's database named active_storage_blobs, active_storage_variant_records and active_storage_attachments. After 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. Utilize bin/rails db:migrate to run the migration.

active_storage_attachments is a polymorphic join tabular array that stores your model's class proper noun. If your model'south course name changes, yous volition need to run a migration on this table to update the underlying record_type to your model'due south new class proper name.

If you are using UUIDs instead of integers as the master key on your models you lot will demand to change the column type of active_storage_attachments.record_id and active_storage_variant_records.id in the generated migration accordingly.

Declare Active Storage services in config/storage.yml. For each service your awarding uses, provide a proper name and the requisite configuration. The example below declares 3 services named local, exam, and amazon:

                          local              :              service              :              Disk              root              :              <%= Runway.root.bring together("storage") %>              test              :              service              :              Disk              root              :              <%= Runway.root.bring together("tmp/storage") %>              amazon              :              service              :              S3              access_key_id              :              "              "              secret_access_key              :              "              "              bucket              :              "              "              region              :              "              "              # e.g. 'usa-east-1'                      

Tell Agile Storage which service to employ by setting Track.application.config.active_storage.service. Considering each environs will likely use a unlike service, it is recommended to practise this on a per-surroundings footing. To employ the disk service from the previous example in the development environment, you lot would add the following to config/environments/development.rb:

                          # Store files locally.              config              .              active_storage              .              service              =              :local                      

To use the S3 service in production, y'all add the following to config/environments/production.rb:

                          # Store files on Amazon S3.              config              .              active_storage              .              service              =              :amazon                      

To employ the test service when testing, you add the following to config/environments/test.rb:

                          # Store uploaded files on the local file system in a temporary directory.              config              .              active_storage              .              service              =              :test                      

Continue reading for more information on the built-in service adapters (e.yard. Deejay and S3) and the configuration they crave.

Configuration files that are environment-specific will take precedence: in production, for instance, the config/storage/production.yml file (if real) volition take precedence over the config/storage.yml file.

It is recommended to apply Rail.env in the bucket names to further reduce the gamble of accidentally destroying product data.

                          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-<%= Runway.env %>                      

two.1 Disk Service

Declare a Disk service in config/storage.yml:

                          local              :              service              :              Disk              root              :              <%= Rails.root.join("storage") %>                      

2.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'                      

Set sensible customer HTTP timeouts and retry limits for your application. In sure failure scenarios, the default AWS customer configuration may crusade connections to be held for up to several minutes and atomic number 82 to request queuing.

Add together the aws-sdk-s3 precious stone to your Gemfile:

                          jewel              "aws-sdk-s3"              ,              require:                            simulated                      

The cadre features of Agile Storage require the post-obit permissions: s3:ListBucket, s3:PutObject, s3:GetObject, and s3:DeleteObject. Public access additionally requires s3:PutObjectAcl. If you take additional upload options configured such as setting ACLs then boosted permissions may be required.

If you want to utilise environs variables, standard SDK configuration files, profiles, IAM instance profiles or job roles, you can omit the access_key_id, secret_access_key, and region keys in the case above. The S3 Service supports all of the hallmark options described in the AWS SDK documentation.

To connect to an S3-uniform 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 check them in AWS S3 Client documentation.

2.3 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-hulk"              ,              require:                            simulated                      

2.4 Google Deject Storage Service

Declare a Google Cloud Storage service in config/storage.yml:

                          google              :              service              :              GCS              credentials              :              <%= Track.root.join("path/to/keyfile.json") %>              projection              :              "              "              bucket              :              "              "                      

Optionally provide a Hash of credentials instead of a keyfile path:

                          google              :              service              :              GCS              credentials              :              type              :              "              service_account"              project_id              :              "              "              private_key_id              :              <%= Track.application.credentials.dig(:gcs, :private_key_id) %>              private_key              :              <%= Rails.awarding.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://world wide web.googleapis.com/oauth2/v1/certs"              client_x509_cert_url              :              "              "              projection              :              "              "              saucepan              :              "              "                      

Optionally provide a Cache-Control metadata to set on uploaded assets:

                          google              :              service              :              GCS              ...              cache_control              :              "              public,                                          max-age=3600"                      

Optionally use IAM instead of the credentials when signing URLs. This is useful if you are authenticating your GKE applications with Workload Identity, come across this Google Cloud blog post for more data.

                          google              :              service              :              GCS              ...              iam              :              truthful                      

Optionally employ a specific GSA when signing URLs. When using IAM, the metadata server will be contacted to get the GSA email, only this metadata server is not ever present (e.yard. local tests) and you may wish to utilise 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:

                          precious stone              "google-cloud-storage"              ,              "~> ane.11"              ,              require:                            fake                      

ii.5 Mirror Service

You can keep multiple services in sync by defining a mirror service. A mirror service replicates uploads and deletes across two or more subordinate services.

A mirror service is intended to exist used temporarily during a migration betwixt services in product. Yous can beginning mirroring to a new service, re-create pre-existing files from the sometime service to the new, then go all-in on the new service.

Mirroring is not atomic. It is possible for an upload to succeed on the primary service and neglect on any of the subordinate services. Earlier going all-in on a new service, verify that all files have been copied.

Define each of the services you'd like to mirror as described above. Reference them by proper name when defining a mirror service:

                          s3_west_coast              :              service              :              S3              access_key_id              :              "              "              secret_access_key              :              "              "              region              :              "              "              saucepan              :              "              "              s3_east_coast              :              service              :              S3              access_key_id              :              "              "              secret_access_key              :              "              "              region              :              "              "              bucket              :              "              "              product              :              service              :              Mirror              chief              :              s3_east_coast              mirrors              :              -              s3_west_coast                      

Although all secondary services receive uploads, downloads are always handled by the primary service.

Mirror services are compatible with straight uploads. New files are directly uploaded to the primary service. When a direct-uploaded file is attached to a record, a groundwork job is enqueued to copy information technology to the secondary services.

ii.vi Public access

By default, Active Storage assumes individual access to services. This ways generating signed, single-employ URLs for blobs. If you'd rather make blobs publicly accessible, specify public: truthful in your app's config/storage.yml:

                          gcs              :              &gcs              service              :              GCS              project              :              "              "              private_gcs              :              <<              :              *gcs              credentials              :              <%= Rail.root.bring together("path/to/private_keyfile.json") %>              saucepan              :              "              "              public_gcs              :              <<              :              *gcs              credentials              :              <%= Rails.root.join("path/to/public_keyfile.json") %>              saucepan              :              "              "              public              :              truthful                      

Make sure your buckets are properly configured for public admission. Run across docs on how to enable public read permissions for Amazon S3, Google Deject Storage, and Microsoft Azure storage services. Amazon S3 additionally requires that you have the s3:PutObjectAcl permission.

When converting an existing application to use public: true, make sure to update every individual file in the bucket to be publicly-readable before switching over.

3 Attaching Files to Records

3.1 has_one_attached

The has_one_attached macro sets up a one-to-one mapping between records and files. Each record can take one file attached to it.

For example, suppose your application has a User model. If you desire each user to accept an avatar, define the User model as follows:

                          class              User              <              ApplicationRecord              has_one_attached              :avatar              finish                      

or if y'all are using Track 6.0+, you tin can run a model generator command like this:

                          bin              /              rail              generate              model              User              avatar              :attachment                      

Yous can create a user with an avatar:

                          <%=              class              .              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              )              stop              stop                      

Call avatar.attach to adhere an avatar to an existing user:

                          user              .              avatar              .              adhere              (              params              [              :avatar              ])                      

Phone call avatar.attached? to determine whether a particular user has an avatar:

In some cases you lot might want to override a default service for a specific attachment. You tin can configure specific services per attachment using the service option:

                          form              User              <              ApplicationRecord              has_one_attached              :avatar              ,              service: :s3              cease                      

You tin can configure specific variants per attachment past calling the variant method on yielded attachable object:

                          class              User              <              ApplicationRecord              has_one_attached              :avatar              exercise              |              attachable              |              attachable              .              variant              :thumb              ,              resize_to_limit:                            [              100              ,              100              ]              cease              terminate                      

Call avatar.variant(:thumb) to become a thumb variant of an avatar:

                          <%=              image_tag              user              .              avatar              .              variant              (              :thumb              )              %>                      

3.2 has_many_attached

The has_many_attached macro sets upward a i-to-many human relationship betwixt records and files. Each record can have many files attached to it.

For example, suppose your application has a Bulletin model. If you desire each message to take many images, define the Message model as follows:

                          class              Message              <              ApplicationRecord              has_many_attached              :images              end                      

or if y'all are using Rails 6.0+, you can run a model generator command like this:

                          bin              /              rails              generate              model              Bulletin              images              :attachments                      

You tin can create a message with images:

                          grade              MessagesController              <              ApplicationController              def              create              message              =              Message              .              create!              (              message_params              )              redirect_to              message              end              individual              def              message_params              params              .              require              (              :message              ).              permit              (              :title              ,              :content              ,              images:                            [])              end              end                      

Phone call images.attach to add new images to an existing message:

                          @message              .              images              .              attach              (              params              [              :images              ])                      

Telephone call images.attached? to determine whether a particular message has any images:

                          @message              .              images              .              attached?                      

Overriding the default service is washed the same way every bit has_one_attached, by using the service option:

                          class              Message              <              ApplicationRecord              has_many_attached              :images              ,              service: :s3              finish                      

Configuring specific variants is done the same style as has_one_attached, by calling the variant method on the yielded attachable object:

                          course              Message              <              ApplicationRecord              has_many_attached              :images              do              |              attachable              |              attachable              .              variant              :thumb              ,              resize_to_limit:                            [              100              ,              100              ]              end              end                      

3.3 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 examination. To do that, provide a Hash containing at to the lowest degree an open IO object and a filename:

                          @bulletin              .              images              .              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'due south content type from its data. It falls back to the content type y'all provide if it can't do that.

                          @message              .              images              .              attach              (              io:                            File              .              open up              (              '/path/to/file'              ),              filename:                            'file.pdf'              ,              content_type:                            'application/pdf'              )                      

You can bypass the content blazon inference from the information by passing in identify: fake along with the content_type.

                          @message              .              images              .              adhere              (              io:                            File              .              open              (              '/path/to/file'              ),              filename:                            'file.pdf'              ,              content_type:                            'application/pdf'              ,              place:                            false              )                      

If you don't provide a content type and Active Storage tin't determine the file'southward content type automatically, it defaults to application/octet-stream.

4 Removing Files

To remove an attachment from a model, call purge on the zipper. If your awarding is fix to apply Active Job, removal tin exist washed 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 resource files.              user              .              avatar              .              purge              # Destroy the associated models and actual resource files async, via Active Job.              user              .              avatar              .              purge_later                      

5 Serving Files

Active Storage supports two ways to serve files: redirecting and proxying.

All Active Storage controllers are publicly accessible past default. The generated URLs are difficult to guess, but permanent past design. If your files require a higher level of protection consider implementing Authenticated Controllers.

v.i Redirect mode

To generate a permanent URL for a blob, you tin can laissez passer the hulk to the url_for view helper. This generates a URL with the blob'due south signed_id that is routed to the blob's RedirectController

                          url_for              (              user              .              avatar              )              # => /runway/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 high-availability. The redirection has an HTTP expiration of 5 minutes.

To create a download link, utilise the rails_blob_{path|url} helper. Using this helper allows yous to set the disposition.

                          rails_blob_path              (              user              .              avatar              ,              disposition:                            "zipper"              )                      

To foreclose XSS attacks, Active Storage forces the Content-Disposition header to "attachment" for some kind of files. To change this behaviour see the available configuration options in Configuring Runway Applications.

If yous need to create a link from exterior of controller/view context (Background jobs, Cronjobs, etc.), yous can access the rails_blob_path like this:

                          Rails              .              awarding              .              routes              .              url_helpers              .              rails_blob_path              (              user              .              avatar              ,              only_path:                            true              )                      

five.2 Proxy style

Optionally, files tin can exist proxied instead. This means that your application servers volition download file information from the storage service in response to requests. This tin be useful for serving files from a CDN.

You lot tin configure Active Storage to use proxying past default:

                          # config/initializers/active_storage.rb              Rails              .              application              .              config              .              active_storage              .              resolve_model_to_route              =              :rails_storage_proxy                      

Or if you want to explicitly proxy specific attachments there are URL helpers you lot can use in the class of rails_storage_proxy_path and rails_storage_proxy_url.

                          <%=              image_tag              rails_storage_proxy_path              (              @user              .              avatar              )              %>                      
5.2.1 Putting a CDN in front of Agile Storage

Additionally, in order to apply a CDN for Active Storage attachments, you volition need to generate URLs with proxy mode then that they are served by your app and the CDN volition 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 as well brand sure that the generated URLs use the CDN host instead of your app host. In that location are multiple means to achieve this, but 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              directly              :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              .              cardinal              filename              =              model              .              blob              .              filename              route_for              (              :rails_blob_representation_proxy              ,              signed_blob_id              ,              variation_key              ,              filename              ,              options              .              merge              (              host:                            ENV              [              'CDN_HOST'              ])              )              end              end                      

and so generate routes like this:

                          <%=              cdn_image_url              (              user              .              avatar              .              variant              (              resize_to_limit:                            [              128              ,              128              ]))              %>                      

v.iii Authenticated Controllers

All Agile Storage controllers are publicly accessible by default. The generated URLs use a plain 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 require a college level of protection, you can implement your ain authenticated controllers, based on the ActiveStorage::Blobs::RedirectController, ActiveStorage::Blobs::ProxyController, ActiveStorage::Representations::RedirectController and ActiveStorage::Representations::ProxyController

To only allow an account to admission their own logo you could exercise the following:

                          # config/routes.rb              resource              :account              do              resource              :logo              end                      
                          # app/controllers/logos_controller.rb              class              LogosController              <              ApplicationController              # Through ApplicationController:              # include Authenticate, SetCurrentAccount              def              testify              redirect_to              Current              .              account              .              logo              .              url              end              end                      
                          <%=              image_tag              account_logo_path              %>                      

And and then you might desire to disable the Active Storage default routes with:

                          config              .              active_storage              .              draw_routes              =              false                      

to prevent files existence accessed with the publicly attainable URLs.

6 Downloading Files

Sometimes y'all need to process a blob later information technology'southward uploaded—for instance, to catechumen it to a different format. Utilize the attachment's download method to read a blob'southward binary data into memory:

                          binary              =              user              .              avatar              .              download                      

You might desire to download a hulk to a file on disk then an external programme (e.g. a virus scanner or media transcoder) can operate on it. Use the zipper'southward open method to download a blob to a tempfile on disk:

                          bulletin              .              video              .              open              do              |              file              |              system              '/path/to/virus/scanner'              ,              file              .              path              # ...              cease                      

Information technology's important to know that the file is not yet available in the after_create callback but in the after_create_commit only.

7 Analyzing Files

Active Storage analyzes files in one case they've been uploaded by queuing a job in Active Job. Analyzed files will store additional information in the metadata hash, including analyzed: true. Y'all tin can check whether a blob has been analyzed by calling analyzed? on it.

Image analysis provides width and height attributes. Video analysis provides these, equally well every bit duration, bending, display_aspect_ratio, and video and audio booleans to point the presence of those channels. Audio analysis provides duration and bit_rate attributes.

8 Displaying Images, Videos, and PDFs

Active Storage supports representing a multifariousness of files. You lot tin call representation on an attachment to display an image variant, or a preview of a video or PDF. Before calling representation, check if the attachment can be represented past calling representable?. Some file formats can't be previewed by Active Storage out of the box (due east.m. Give-and-take documents); if representable? returns false y'all may desire to link to the file instead.

                          <ul>              <%              @message              .              files              .              each              practise              |              file              |              %>              <li>              <%              if              file              .              representable?              %>              <%=              image_tag              file              .              representation              (              resize_to_limit:                            [              100              ,              100              ])              %>              <%              else              %>              <%=              link_to              rails_blob_path              (              file              ,              disposition:                            "zipper"              )              exercise              %>              <%=              image_tag              "placeholder.png"              ,              alt:                            "Download file"              %>              <%              stop              %>              <%              end              %>              </li>              <%              end              %>              </ul>                      

Internally, representation calls variant for images, and preview for previewable files. Yous can also phone call these methods directly.

8.1 Lazy vs Immediate Loading

By default, Active Storage volition procedure representations lazily. This lawmaking:

                          image_tag              file              .              representation              (              resize_to_limit:                            [              100              ,              100              ])                      

Volition generate an <img> tag with the src pointing to the ActiveStorage::Representations::RedirectController. The browser will make a request to that controller, which will return a 302 redirect to the file on the remote service (or in proxy mode, return the file contents). Loading the file lazily allows features like single utilise URLs to work without slowing downwards your initial folio loads.

This works fine for most cases.

If you desire to generate URLs for images immediately, you can call .processed.url:

                          image_tag              file              .              representation              (              resize_to_limit:                            [              100              ,              100              ]).              processed              .              url                      

The Agile Storage variant tracker improves functioning of this, by storing a tape in the database if the requested representation has been candy before. Thus, the in a higher place code will simply make an API call to the remote service (e.thou. S3) once, and in one case a variant is stored, will apply that. The variant tracker runs automatically, but can exist disabled through config.active_storage.track_variants.

If you lot're rendering lots of images on a page, the above example could result in Northward+1 queries loading all the variant records. To avoid these N+1 queries, use the named scopes on ActiveStorage::Zipper.

                          message              .              images              .              with_all_variant_records              .              each              do              |              file              |              image_tag              file              .              representation              (              resize_to_limit:                            [              100              ,              100              ]).              candy              .              url              end                      

8.2 Transforming Images

Transforming images allows yous to display the prototype at your choice of dimensions. To create a variation of an image, phone call variant on the attachment. You lot can pass any transformation supported by the variant processor to the method. When the browser hits the variant URL, Agile Storage volition lazily transform the original hulk 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, Active Storage will automatically apply transformations depending on the paradigm's format:

  1. Content types that are variable (as dictated by config.active_storage.variable_content_types) and not considered spider web images (as dictated by config.active_storage.web_image_content_types), will be converted to PNG.

  2. If quality is non specified, the variant processor's default quality for the format will be used.

Active Storage tin use either Vips or MiniMagick as 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 compatible, so when migrating an existing awarding between 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:                            "four:2:0"              ,              strip:                            true              ,              interlace:                            "JPEG"              ,              colorspace:                            "sRGB"              ,              quality:                            lxxx              )              %>              <!-- Vips -->              <%=              image_tag              user              .              avatar              .              variant              (              resize_to_limit:                            [              100              ,              100              ],              format: :jpeg              ,              saver:                            {              subsample_mode:                            "on"              ,              strip:                            true              ,              interlace:                            true              ,              quality:                            lxxx              })              %>                      

8.3 Previewing Files

Some not-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. To create a link to a lazily-generated preview, use the attachment's preview method:

                          <%=              image_tag              message              .              video              .              preview              (              resize_to_limit:                            [              100              ,              100              ])              %>                      

To add support for some other format, add your own previewer. See the ActiveStorage::Preview documentation for more than information.

nine Direct Uploads

Active Storage, with its included JavaScript library, supports uploading directly from the client to the cloud.

9.ane Usage

  1. Include activestorage.js in your awarding's JavaScript bundle.

    Using the asset pipeline:

                                      //= require activestorage                              

    Using the npm package:

                                      import                  *                  every bit                  ActiveStorage                  from                  "                  @rails/activestorage                  "                  ActiveStorage                  .                  offset                  ()                              
  2. Add together direct_upload: truthful to your file field:

                                      <%=                  class                  .                  file_field                  :attachments                  ,                  multiple:                                    true                  ,                  direct_upload:                                    truthful                  %>                              

    Or, if yous aren't using a FormBuilder, add the data attribute direct:

                                      <input                  type=                  file                  data-direct-upload-url=                  "                  <%=                  rails_direct_uploads_url                  %>                  "                  />                              
  3. Configure CORS on third-party storage services to allow direct upload requests.

  4. That's it! Uploads begin upon form submission.

9.2 Cross-Origin Resource Sharing (CORS) configuration

To make direct uploads to a tertiary-party service 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 Deject Storage
  • Azure Storage

Take intendance to allow:

  • All origins from which your app is accessed
  • The PUT request method
  • The following headers:
    • Origin
    • Content-Type
    • Content-MD5
    • Content-Disposition (except for Azure Storage)
    • x-ms-blob-content-disposition (for Azure Storage merely)
    • x-ms-blob-type (for Azure Storage simply)
    • Enshroud-Control (for GCS, only if cache_control is prepare)

No CORS configuration is required for the Disk service since it shares your app'south origin.

nine.ii.1 Example: S3 CORS configuration
                          [                                          {                                          "AllowedHeaders"              :                                          [                                          "*"                                          ],                                          "AllowedMethods"              :                                          [                                          "PUT"                                          ],                                          "AllowedOrigins"              :                                          [                                          "https://world wide web.example.com"                                          ],                                          "ExposeHeaders"              :                                          [                                          "Origin"              ,                                          "Content-Blazon"              ,                                          "Content-MD5"              ,                                          "Content-Disposition"                                          ],                                          "MaxAgeSeconds"              :                                          3600                                          }                                          ]                                                  
9.2.2 Case: Google Deject Storage CORS configuration
                          [                                          {                                          "origin"              :                                          [              "https://www.example.com"              ],                                          "method"              :                                          [              "PUT"              ],                                          "responseHeader"              :                                          [              "Origin"              ,                                          "Content-Type"              ,                                          "Content-MD5"              ,                                          "Content-Disposition"              ],                                          "maxAgeSeconds"              :                                          3600                                          }                                          ]                                                  
9.2.three Case: Azure Storage CORS configuration
                          <Cors>              <CorsRule>              <AllowedOrigins>https://www.example.com</AllowedOrigins>              <AllowedMethods>PUT</AllowedMethods>              <AllowedHeaders>Origin, Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-blazon</AllowedHeaders>              <MaxAgeInSeconds>3600</MaxAgeInSeconds>              </CorsRule>              </Cors>                      

ix.3 Direct upload JavaScript events

Event name Event target Issue information (event.detail) Description
direct-uploads:outset <form> None A class containing files for directly upload fields was submitted.
directly-upload:initialize <input> {id, file} Dispatched for every file after class submission.
straight-upload:start <input> {id, file} A direct upload is starting.
direct-upload:before-blob-request <input> {id, file, xhr} Before making a request to your awarding for direct upload metadata.
straight-upload:earlier-storage-request <input> {id, file, xhr} Before making a asking to store a file.
direct-upload:progress <input> {id, file, progress} Every bit requests to shop files progress.
direct-upload:mistake <input> {id, file, mistake} An fault occurred. An alert will display unless this upshot is canceled.
direct-upload:finish <input> {id, file} A straight upload has ended.
directly-uploads:finish <form> None All direct uploads have ended.

9.four Example

You can use these events to prove the progress of an upload.

direct-uploads

To show the uploaded files in a form:

                          // direct_uploads.js              addEventListener              (              "              directly-upload:initialize              "              ,              event              =>              {              const              {              target              ,              detail              }              =              issue              const              {              id              ,              file              }              =              item              target              .              insertAdjacentHTML              (              "              beforebegin              "              ,              `     <div id="straight-upload-              ${              id              }              " class="straight-upload directly-upload--pending">       <div id="direct-upload-progress-              ${              id              }              " course="direct-upload__progress" way="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              .              particular              const              element              =              document              .              getElementById              (              `direct-upload-              ${              id              }              `              )              element              .              classList              .              remove              (              "              direct-upload--pending              "              )              })              addEventListener              (              "              direct-upload:progress              "              ,              result              =>              {              const              {              id              ,              progress              }              =              event              .              particular              const              progressElement              =              document              .              getElementById              (              `direct-upload-progress-              ${              id              }              `              )              progressElement              .              manner              .              width              =              `              ${              progress              }              %`              })              addEventListener              (              "              directly-upload:mistake              "              ,              event              =>              {              event              .              preventDefault              ()              const              {              id              ,              error              }              =              event              .              detail              const              element              =              document              .              getElementById              (              `direct-upload-              ${              id              }              `              )              element              .              classList              .              add together              (              "              direct-upload--error              "              )              element              .              setAttribute              (              "              championship              "              ,              error              )              })              addEventListener              (              "              direct-upload:end              "              ,              event              =>              {              const              {              id              }              =              event              .              detail              const              element              =              document              .              getElementById              (              `straight-upload-              ${              id              }              `              )              chemical element              .              classList              .              add              (              "              direct-upload--complete              "              )              })                      

Add styles:

                          /* direct_uploads.css */              .direct-upload              {              display              :              inline-block              ;              position              :              relative              ;              padding              :              2px              4px              ;              margin              :              0              3px              3px              0              ;              edge              :              1px              solid              rgba              (              0              ,              0              ,              0              ,              0.3              );              edge-radius              :              3px              ;              font-size              :              11px              ;              line-top              :              13px              ;              }              .direct-upload--awaiting              {              opacity              :              0.6              ;              }              .direct-upload__progress              {              position              :              absolute              ;              meridian              :              0              ;              left              :              0              ;              bottom              :              0              ;              opacity              :              0.ii              ;              background              :              #0076ff              ;              transition              :              width              120ms              ease-out              ,              opacity              60ms              60ms              ease-in              ;              transform              :              translate3d              (              0              ,              0              ,              0              );              }              .directly-upload--complete              .direct-upload__progress              {              opacity              :              0.4              ;              }              .direct-upload--error              {              border-color              :              red              ;              }              input              [              type              =              file              ][              data-direct-upload-url              ][              disabled              ]              {              display              :              none              ;              }                      

9.5 Integrating with Libraries or Frameworks

If you desire to employ the Direct Upload feature from a JavaScript framework, or you want to integrate custom drag and driblet solutions, you can apply the DirectUpload course for this purpose. Upon receiving a file from your library of choice, instantiate a DirectUpload and phone call its create method. Create takes a callback to invoke when the upload completes.

                          import              {              DirectUpload              }              from              "              @rails/activestorage              "              const              input              =              certificate              .              querySelector              (              '              input[type=file]              '              )              // Bind to file drop - apply the ondrop on a parent element or use a              //  library similar Dropzone              const              onDrop              =              (              event              )              =>              {              event              .              preventDefault              ()              const              files              =              effect              .              dataTransfer              .              files              ;              Array              .              from              (              files              ).              forEach              (              file              =>              uploadFile              (              file              ))              }              // Bind to normal file selection              input              .              addEventListener              (              '              change              '              ,              (              event              )              =>              {              Array              .              from              (              input              .              files              ).              forEach              (              file              =>              uploadFile              (              file              ))              // you might clear the selected files from the input              input              .              value              =              nothing              })              const              uploadFile              =              (              file              )              =>              {              // your grade needs the file_field direct_upload: true, which              //  provides data-straight-upload-url              const              url              =              input              .              dataset              .              directUploadUrl              const              upload              =              new              DirectUpload              (              file              ,              url              )              upload              .              create              ((              error              ,              blob              )              =>              {              if              (              error              )              {              // Handle the error              }              else              {              // Add an appropriately-named hidden input to the form with a              //  value of blob.signed_id and then that the blob ids will be              //  transmitted in the normal upload menstruum              const              hiddenField              =              document              .              createElement              (              '              input              '              )              hiddenField              .              setAttribute              (              "              type              "              ,              "              subconscious              "              );              hiddenField              .              setAttribute              (              "              value              "              ,              blob              .              signed_id              );              hiddenField              .              name              =              input              .              proper name              document              .              querySelector              (              '              course              '              ).              appendChild              (              hiddenField              )              }              })              }                      

If you demand to track the progress of the file upload, y'all can pass a 3rd parameter to the DirectUpload constructor. During the upload, DirectUpload will telephone call the object'due south directUploadWillStoreFileWithXHR method. Y'all can then 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              ((              error              ,              blob              )              =>              {              if              (              error              )              {              // Handle the error              }              else              {              // Add an appropriately-named hidden input to the class              // with a value of hulk.signed_id              }              })              }              directUploadWillStoreFileWithXHR              (              request              )              {              request              .              upload              .              addEventListener              (              "              progress              "              ,              issue              =>              this              .              directUploadDidProgress              (              consequence              ))              }              directUploadDidProgress              (              event              )              {              // Utilise issue.loaded and result.total to update the progress bar              }              }                      

Using Direct Uploads can sometimes outcome in a file that uploads, but never attaches to a tape. Consider purging unattached uploads.

10 Testing

Use fixture_file_upload to test uploading a file in an integration or controller exam. Rails handles files similar whatsoever other parameter.

                          class              SignupController              <              ActionDispatch              ::              IntegrationTest              test              "can sign up"              practice              post              signup_path              ,              params:                            {              name:                            "David"              ,              avatar:                            fixture_file_upload              (              "david.png"              ,              "image/png"              )              }              user              =              User              .              order              (              :created_at              ).              last              assert              user              .              avatar              .              attached?              end              end                      

10.ane Discarding files created during tests

10.1.1 Organization 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 upwards. If you want to clear the files, you tin can do it in an after_teardown callback. Doing it here ensures that all connections created during the test are complete and you won't receive an error from Active Storage saying information technology can't find a file.

                          course              ApplicationSystemTestCase              <              ActionDispatch              ::              SystemTestCase              # ...              def              after_teardown              super              FileUtils              .              rm_rf              (              ActiveStorage              ::              Blob              .              service              .              root              )              cease              # ...              end                      

If yous're using parallel tests and the DiskService, you should configure each process to use its own folder for Active Storage. This fashion, the teardown callback will only delete files from the relevant process' tests.

                          class              ApplicationSystemTestCase              <              ActionDispatch              ::              SystemTestCase              # ...              parallelize_setup              practise              |              i              |              ActiveStorage              ::              Blob              .              service              .              root              =              "              #{              ActiveStorage              ::              Blob              .              service              .              root              }              -              #{              i              }              "              end              # ...              stop                      

If your system tests verify the deletion of a model with attachments and you lot're using Active Chore, set your exam environment to employ the inline queue adapter so the purge chore is executed immediately rather at an unknown time in the future.

                          # Use inline task processing to make things happen immediately              config              .              active_job              .              queue_adapter              =              :inline                      
x.1.2 Integration tests

Similarly to Arrangement Tests, files uploaded during Integration Tests volition non exist automatically cleaned up. If you want to articulate the files, you can do it in an teardown callback.

                          grade              ActionDispatch::IntegrationTest              def              after_teardown              super              FileUtils              .              rm_rf              (              ActiveStorage              ::              Blob              .              service              .              root              )              finish              end                      

If you're using parallel tests and the Disk service, you should configure each procedure to use its own folder for Active Storage. This way, the teardown callback volition only delete files from the relevant procedure' tests.

                          class              ActionDispatch::IntegrationTest              parallelize_setup              do              |              i              |              ActiveStorage              ::              Hulk              .              service              .              root              =              "              #{              ActiveStorage              ::              Blob              .              service              .              root              }              -              #{              i              }              "              end              terminate                      

10.ii Adding attachments to fixtures

Yous tin add together attachments to your existing fixtures. Showtime, y'all'll want to create a separate storage service:

                          # config/storage.yml              test_fixtures              :              service              :              Disk              root              :              <%= Rails.root.join("tmp/storage_fixtures") %>                      

This tells Active Storage where to "upload" fixture files to, and then it should exist a temporary directory. By making it a different directory to your regular test service, you can separate fixture files from files uploaded during a examination.

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.hulk filename              :              "              david.png"              ,              service_name              :              "              test_fixtures"              %              >                      

And so put a file in your fixtures directory (the default path is test/fixtures/files) with the corresponding filename. Run across the ActiveStorage::FixtureSet docs for more information.

Once everything is ready, you lot'll exist able to access attachments in your tests:

                          grade              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              terminate                      
ten.2.1 Cleaning upwardly fixtures

While files uploaded in tests are cleaned up at the end of each test, you only demand to make clean up fixture files once: when all your tests complete.

If you're using parallel tests, call parallelize_teardown:

                          class              ActiveSupport::TestCase              # ...              parallelize_teardown              do              |              i              |              FileUtils              .              rm_rf              (              ActiveStorage              ::              Blob              .              services              .              fetch              (              :test_fixtures              ).              root              )              end              # ...              cease                      

If you're not running parallel tests, use Minitest.after_run or the equivalent for your test framework (e.g. subsequently(:suite) for RSpec):

                          # test_helper.rb              Minitest              .              after_run              do              FileUtils              .              rm_rf              (              ActiveStorage              ::              Blob              .              services              .              fetch              (              :test_fixtures              ).              root              )              cease                      

eleven Implementing Support for Other Cloud Services

If yous need to support a cloud 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

At that place are cases where a file is uploaded but never attached to a tape. This tin happen when using Straight Uploads. You can query for unattached records using the unattached scope. Below is an example using a custom rake task.

                          namespace              :active_storage              do              desc              "Purges unattached Active Storage blobs. Run regularly."              task              purge_unattached: :surround              do              ActiveStorage              ::              Blob              .              unattached              .              where              (              "active_storage_blobs.created_at <= ?"              ,              ii              .              days              .              ago              ).              find_each              (              &              :purge_later              )              end              stop                      

The query generated past ActiveStorage::Blob.unattached can be tedious and potentially disruptive on applications with larger databases.

Feedback

You're encouraged to help improve the quality of this guide.

Delight contribute if you see whatsoever typos or factual errors. To get started, you can read our documentation contributions section.

Yous may also find incomplete content or stuff that is non up to appointment. Please practice add any missing documentation for main. Make certain to bank check Edge Guides start to verify if the problems are already stock-still or not on the main branch. Check the Cherry-red on Track Guides Guidelines for way and conventions.

If for whatever reason you spot something to fix just cannot patch it yourself, delight open an issue.

And last but not least, any kind of give-and-take regarding Ruby on Rails documentation is very welcome on the rubyonrails-docs mailing listing.

Source: https://edgeguides.rubyonrails.org/active_storage_overview.html

Posted by: murdockbeinerculd.blogspot.com

0 Response to "Activestorage Direct Upload Without Javascript?"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel