aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage')
-rw-r--r--activestorage/CHANGELOG.md38
-rw-r--r--activestorage/app/assets/javascripts/activestorage.js11
-rw-r--r--activestorage/app/controllers/active_storage/base_controller.rb8
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb2
-rw-r--r--activestorage/app/controllers/concerns/active_storage/set_current.rb15
-rw-r--r--activestorage/app/javascript/activestorage/ujs.js13
-rw-r--r--activestorage/app/jobs/active_storage/analyze_job.rb2
-rw-r--r--activestorage/app/models/active_storage/blob.rb4
-rw-r--r--activestorage/app/models/active_storage/blob/identifiable.rb6
-rw-r--r--activestorage/app/models/active_storage/filename.rb6
-rw-r--r--activestorage/app/models/active_storage/filename/parameters.rb36
-rw-r--r--activestorage/app/models/active_storage/variant.rb48
-rw-r--r--activestorage/app/models/active_storage/variation.rb79
-rw-r--r--activestorage/config/routes.rb25
-rw-r--r--activestorage/lib/active_storage.rb9
-rw-r--r--activestorage/lib/active_storage/engine.rb1
-rw-r--r--activestorage/lib/active_storage/errors.rb23
-rw-r--r--activestorage/lib/active_storage/service.rb5
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb34
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb34
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb14
-rw-r--r--activestorage/lib/active_storage/service/s3_service.rb14
-rw-r--r--activestorage/lib/active_storage/transformers/image_processing_transformer.rb39
-rw-r--r--activestorage/lib/active_storage/transformers/mini_magick_transformer.rb38
-rw-r--r--activestorage/lib/active_storage/transformers/transformer.rb42
-rw-r--r--activestorage/test/controllers/disk_controller_test.rb8
-rw-r--r--activestorage/test/dummy/app/assets/config/manifest.js2
-rw-r--r--activestorage/test/dummy/config/environments/production.rb3
-rw-r--r--activestorage/test/dummy/config/webpacker.yml72
-rw-r--r--activestorage/test/fixtures/files/empty_file.txt0
-rw-r--r--activestorage/test/jobs/purge_job_test.rb10
-rw-r--r--activestorage/test/models/blob_test.rb2
-rw-r--r--activestorage/test/models/filename/parameters_test.rb32
-rw-r--r--activestorage/test/models/filename_test.rb4
-rw-r--r--activestorage/test/service/s3_service_test.rb7
-rw-r--r--activestorage/test/service/shared_service_tests.rb23
-rw-r--r--activestorage/test/test_helper.rb3
37 files changed, 485 insertions, 227 deletions
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 0e9c2b5619..f4e2826dc6 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,41 @@
+* Add `ActiveStorage.routes_prefix` for configuring generated routes.
+
+ *Chris Bisnett*
+
+* `ActiveStorage::Service::AzureStorageService` only handles specifically
+ relevant types of `Azure::Core::Http::HTTPError`. It previously obscured
+ other types of `HTTPError`, which is the azure-storage gem’s catch-all
+ exception class.
+
+ *Cameron Bothner*
+
+* `ActiveStorage::DiskController#show` generates a 404 Not Found response when
+ the requested file is missing from the disk service. It previously raised
+ `Errno::ENOENT`.
+
+ *Cameron Bothner*
+
+* `ActiveStorage::Blob#download` and `ActiveStorage::Blob#open` raise
+ `ActiveStorage::FileNotFoundError` when the corresponding file is missing
+ from the storage service. Services translate service-specific missing object
+ exceptions (e.g. `Google::Cloud::NotFoundError` for the GCS service and
+ `Errno::ENOENT` for the disk service) into
+ `ActiveStorage::FileNotFoundError`.
+
+ *Cameron Bothner*
+
+* Added the `ActiveStorage::SetCurrent` concern for custom Active Storage
+ controllers that can't inherit from `ActiveStorage::BaseController`.
+
+ *George Claghorn*
+
+* Active Storage error classes like `ActiveStorage::IntegrityError` and
+ `ActiveStorage::UnrepresentableError` now inherit from `ActiveStorage::Error`
+ instead of `StandardError`. This permits rescuing `ActiveStorage::Error` to
+ handle all Active Storage errors.
+
+ *Andrei Makarov*, *George Claghorn*
+
* Uploaded files assigned to a record are persisted to storage when the record
is saved instead of immediately.
diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js
index d3fd795a3a..375eb6b533 100644
--- a/activestorage/app/assets/javascripts/activestorage.js
+++ b/activestorage/app/assets/javascripts/activestorage.js
@@ -855,14 +855,22 @@
return DirectUploadsController;
}();
var processingAttribute = "data-direct-uploads-processing";
+ var submitButtonsByForm = new WeakMap();
var started = false;
function start() {
if (!started) {
started = true;
+ document.addEventListener("click", didClick, true);
document.addEventListener("submit", didSubmitForm);
document.addEventListener("ajax:before", didSubmitRemoteElement);
}
}
+ function didClick(event) {
+ var target = event.target;
+ if (target.tagName == "INPUT" && target.type == "submit" && target.form) {
+ submitButtonsByForm.set(target.form, target);
+ }
+ }
function didSubmitForm(event) {
handleFormSubmissionEvent(event);
}
@@ -894,7 +902,7 @@
}
}
function submitForm(form) {
- var button = findElement(form, "input[type=submit]");
+ var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]");
if (button) {
var _button = button, disabled = _button.disabled;
button.disabled = false;
@@ -909,6 +917,7 @@
button.click();
form.removeChild(button);
}
+ submitButtonsByForm.delete(form);
}
function disable(input) {
input.disabled = true;
diff --git a/activestorage/app/controllers/active_storage/base_controller.rb b/activestorage/app/controllers/active_storage/base_controller.rb
index 59312ac8df..b27d2bd8aa 100644
--- a/activestorage/app/controllers/active_storage/base_controller.rb
+++ b/activestorage/app/controllers/active_storage/base_controller.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
-# The base controller for all ActiveStorage controllers.
+# The base class for all Active Storage controllers.
class ActiveStorage::BaseController < ActionController::Base
- protect_from_forgery with: :exception
+ include ActiveStorage::SetCurrent
- before_action do
- ActiveStorage::Current.host = request.base_url
- end
+ protect_from_forgery with: :exception
end
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index 75cc11d6ff..7bd641ab9a 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -13,6 +13,8 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
else
head :not_found
end
+ rescue Errno::ENOENT
+ head :not_found
end
def update
diff --git a/activestorage/app/controllers/concerns/active_storage/set_current.rb b/activestorage/app/controllers/concerns/active_storage/set_current.rb
new file mode 100644
index 0000000000..597afe7064
--- /dev/null
+++ b/activestorage/app/controllers/concerns/active_storage/set_current.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# Sets the <tt>ActiveStorage::Current.host</tt> attribute, which the disk service uses to generate URLs.
+# Include this concern in custom controllers that call ActiveStorage::Blob#service_url,
+# ActiveStorage::Variant#service_url, or ActiveStorage::Preview#service_url so the disk service can
+# generate URLs using the same host, protocol, and base path as the current request.
+module ActiveStorage::SetCurrent
+ extend ActiveSupport::Concern
+
+ included do
+ before_action do
+ ActiveStorage::Current.host = request.base_url
+ end
+ end
+end
diff --git a/activestorage/app/javascript/activestorage/ujs.js b/activestorage/app/javascript/activestorage/ujs.js
index 08c535470d..f5353389ef 100644
--- a/activestorage/app/javascript/activestorage/ujs.js
+++ b/activestorage/app/javascript/activestorage/ujs.js
@@ -2,16 +2,25 @@ import { DirectUploadsController } from "./direct_uploads_controller"
import { findElement } from "./helpers"
const processingAttribute = "data-direct-uploads-processing"
+const submitButtonsByForm = new WeakMap
let started = false
export function start() {
if (!started) {
started = true
+ document.addEventListener("click", didClick, true)
document.addEventListener("submit", didSubmitForm)
document.addEventListener("ajax:before", didSubmitRemoteElement)
}
}
+function didClick(event) {
+ const { target } = event
+ if (target.tagName == "INPUT" && target.type == "submit" && target.form) {
+ submitButtonsByForm.set(target.form, target)
+ }
+}
+
function didSubmitForm(event) {
handleFormSubmissionEvent(event)
}
@@ -49,7 +58,8 @@ function handleFormSubmissionEvent(event) {
}
function submitForm(form) {
- let button = findElement(form, "input[type=submit]")
+ let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]")
+
if (button) {
const { disabled } = button
button.disabled = false
@@ -64,6 +74,7 @@ function submitForm(form) {
button.click()
form.removeChild(button)
}
+ submitButtonsByForm.delete(form)
}
function disable(input) {
diff --git a/activestorage/app/jobs/active_storage/analyze_job.rb b/activestorage/app/jobs/active_storage/analyze_job.rb
index 2a952f9f74..804ee4557a 100644
--- a/activestorage/app/jobs/active_storage/analyze_job.rb
+++ b/activestorage/app/jobs/active_storage/analyze_job.rb
@@ -2,6 +2,8 @@
# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later.
class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob
+ retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
+
def perform(blob)
blob.analyze
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index e7f2615b0f..53aa9f0237 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -35,6 +35,10 @@ class ActiveStorage::Blob < ActiveRecord::Base
scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) }
+ before_destroy(prepend: true) do
+ raise ActiveRecord::InvalidForeignKey if attachments.exists?
+ end
+
class << self
# You can used the signed ID of a blob to refer to it on the client side without fear of tampering.
# This is particularly helpful for direct uploads where the client-side needs to refer to the blob
diff --git a/activestorage/app/models/active_storage/blob/identifiable.rb b/activestorage/app/models/active_storage/blob/identifiable.rb
index 049e45dc3e..2c17ddc25f 100644
--- a/activestorage/app/models/active_storage/blob/identifiable.rb
+++ b/activestorage/app/models/active_storage/blob/identifiable.rb
@@ -15,6 +15,10 @@ module ActiveStorage::Blob::Identifiable
end
def download_identifiable_chunk
- service.download_chunk key, 0...4.kilobytes
+ if byte_size.positive?
+ service.download_chunk key, 0...4.kilobytes
+ else
+ ""
+ end
end
end
diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb
index bebb5e61b3..2a03e0173d 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -3,8 +3,6 @@
# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
class ActiveStorage::Filename
- require_dependency "active_storage/filename/parameters"
-
include Comparable
class << self
@@ -60,10 +58,6 @@ class ActiveStorage::Filename
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
end
- def parameters #:nodoc:
- Parameters.new self
- end
-
# Returns the sanitized version of the filename.
def to_s
sanitized.to_s
diff --git a/activestorage/app/models/active_storage/filename/parameters.rb b/activestorage/app/models/active_storage/filename/parameters.rb
deleted file mode 100644
index fb9ea10e49..0000000000
--- a/activestorage/app/models/active_storage/filename/parameters.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-class ActiveStorage::Filename::Parameters #:nodoc:
- attr_reader :filename
-
- def initialize(filename)
- @filename = filename
- end
-
- def combined
- "#{ascii}; #{utf8}"
- end
-
- TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
-
- def ascii
- 'filename="' + percent_escape(I18n.transliterate(filename.sanitized), TRADITIONAL_ESCAPED_CHAR) + '"'
- end
-
- RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
-
- def utf8
- "filename*=UTF-8''" + percent_escape(filename.sanitized, RFC_5987_ESCAPED_CHAR)
- end
-
- def to_s
- combined
- end
-
- private
- def percent_escape(string, pattern)
- string.gsub(pattern) do |char|
- char.bytes.map { |byte| "%%%02X" % byte }.join
- end
- end
-end
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index 056fd2be99..ea57fa5f78 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "ostruct"
+
# Image blobs can have variants that are the result of a set of transformations applied to the original.
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
# original.
@@ -51,7 +53,7 @@
# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
class ActiveStorage::Variant
- WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
+ WEB_IMAGE_CONTENT_TYPES = %w[ image/png image/jpeg image/jpg image/gif ]
attr_reader :blob, :variation
delegate :service, to: :blob
@@ -95,37 +97,35 @@ class ActiveStorage::Variant
def process
blob.open do |image|
- transform image do |output|
- upload output
- end
+ transform(image) { |output| upload(output) }
end
end
-
- def filename
- if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
- blob.filename
- else
- ActiveStorage::Filename.new("#{blob.filename.base}.png")
- end
+ def transform(image, &block)
+ variation.transform(image, format: format, &block)
end
- def content_type
- blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
+ def upload(file)
+ service.upload(key, file)
end
- def transform(image)
- format = "png" unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
- result = variation.transform(image, format: format)
- begin
- yield result
- ensure
- result.close!
- end
+ def specification
+ @specification ||=
+ if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ Specification.new \
+ filename: blob.filename,
+ content_type: blob.content_type,
+ format: nil
+ else
+ Specification.new \
+ filename: ActiveStorage::Filename.new("#{blob.filename.base}.png"),
+ content_type: "image/png",
+ format: "png"
+ end
end
- def upload(file)
- service.upload(key, file)
- end
+ delegate :filename, :content_type, :format, to: :specification
+
+ class Specification < OpenStruct; end
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index ae1376a6cf..3adc2407e5 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -47,13 +47,9 @@ class ActiveStorage::Variation
# saves the transformed image into a temporary file. If +format+ is specified
# it will be the format of the result image, otherwise the result image
# retains the source format.
- def transform(file, format: nil)
+ def transform(file, format: nil, &block)
ActiveSupport::Notifications.instrument("transform.active_storage") do
- if processor
- image_processing_transform(file, format)
- else
- mini_magick_transform(file, format)
- end
+ transformer.transform(file, format: format, &block)
end
end
@@ -63,63 +59,22 @@ class ActiveStorage::Variation
end
private
- # Applies image transformations using the ImageProcessing gem.
- def image_processing_transform(file, format)
- operations = transformations.each_with_object([]) do |(name, argument), list|
- if name.to_s == "combine_options"
- ActiveSupport::Deprecation.warn("The ImageProcessing ActiveStorage variant backend doesn't need :combine_options, as it already generates a single MiniMagick command. In Rails 6.1 :combine_options will not be supported anymore.")
- list.concat argument.keep_if { |key, value| value.present? }.to_a
- elsif argument.present?
- list << [name, argument]
+ def transformer
+ if ActiveStorage.variant_processor
+ begin
+ require "image_processing"
+ rescue LoadError
+ ActiveSupport::Deprecation.warn <<~WARNING
+ Generating image variants will require the image_processing gem in Rails 6.1.
+ Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
+ WARNING
+
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
+ else
+ ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
end
- end
-
- processor
- .source(file)
- .loader(page: 0)
- .convert(format)
- .apply(operations)
- .call
- end
-
- # Applies image transformations using the MiniMagick gem.
- def mini_magick_transform(file, format)
- image = MiniMagick::Image.new(file.path, file)
-
- transformations.each do |name, argument_or_subtransformations|
- image.mogrify do |command|
- if name.to_s == "combine_options"
- argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
- pass_transform_argument(command, subtransformation_name, subtransformation_argument)
- end
- else
- pass_transform_argument(command, name, argument_or_subtransformations)
- end
- end
- end
-
- image.format(format) if format
-
- image.tempfile.tap(&:open)
- end
-
- # Returns the ImageProcessing processor class specified by `ActiveStorage.variant_processor`.
- def processor
- begin
- require "image_processing"
- rescue LoadError
- ActiveSupport::Deprecation.warn("Using mini_magick gem directly is deprecated and will be removed in Rails 6.1. Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.")
- return nil
- end
-
- ImageProcessing.const_get(ActiveStorage.variant_processor.to_s.camelize) if ActiveStorage.variant_processor
- end
-
- def pass_transform_argument(command, method, argument)
- if argument == true
- command.public_send(method)
- elsif argument.present?
- command.public_send(method, argument)
+ else
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
end
end
end
diff --git a/activestorage/config/routes.rb b/activestorage/config/routes.rb
index 20d19f334a..3af7361cff 100644
--- a/activestorage/config/routes.rb
+++ b/activestorage/config/routes.rb
@@ -1,17 +1,15 @@
# frozen_string_literal: true
Rails.application.routes.draw do
- get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
-
- direct :rails_blob do |blob, options|
- route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
- end
-
- resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
- resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
+ scope ActiveStorage.routes_prefix do
+ get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
+ get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
- get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
+ get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
+ put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
+ post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
+ end
direct :rails_representation do |representation, options|
signed_blob_id = representation.blob.signed_id
@@ -25,7 +23,10 @@ Rails.application.routes.draw do
resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
- get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
- put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
- post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
+ direct :rails_blob do |blob, options|
+ route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
+ end
+
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index 928db32691..a94ef626f2 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -50,4 +50,13 @@ module ActiveStorage
mattr_accessor :variable_content_types, default: []
mattr_accessor :content_types_to_serve_as_binary, default: []
mattr_accessor :service_urls_expire_in, default: 5.minutes
+ mattr_accessor :routes_prefix, default: "/rails/active_storage"
+
+ module Transformers
+ extend ActiveSupport::Autoload
+
+ autoload :Transformer
+ autoload :ImageProcessingTransformer
+ autoload :MiniMagickTransformer
+ end
end
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 9d6a27eabe..7eb93b5e16 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -51,6 +51,7 @@ module ActiveStorage
ActiveStorage.previewers = app.config.active_storage.previewers || []
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
ActiveStorage.paths = app.config.active_storage.paths || {}
+ ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage"
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
diff --git a/activestorage/lib/active_storage/errors.rb b/activestorage/lib/active_storage/errors.rb
index bedcd080c4..6475c1d076 100644
--- a/activestorage/lib/active_storage/errors.rb
+++ b/activestorage/lib/active_storage/errors.rb
@@ -1,11 +1,26 @@
# frozen_string_literal: true
module ActiveStorage
- class InvariableError < StandardError; end
- class UnpreviewableError < StandardError; end
- class UnrepresentableError < StandardError; end
+ # Generic base class for all Active Storage exceptions.
+ class Error < StandardError; end
+
+ # Raised when ActiveStorage::Blob#variant is called on a blob that isn't variable.
+ # Use ActiveStorage::Blob#variable? to determine whether a blob is variable.
+ class InvariableError < Error; end
+
+ # Raised when ActiveStorage::Blob#preview is called on a blob that isn't previewable.
+ # Use ActiveStorage::Blob#previewable? to determine whether a blob is previewable.
+ class UnpreviewableError < Error; end
+
+ # Raised when ActiveStorage::Blob#representation is called on a blob that isn't representable.
+ # Use ActiveStorage::Blob#representable? to determine whether a blob is representable.
+ class UnrepresentableError < Error; end
# Raised when uploaded or downloaded data does not match a precomputed checksum.
# Indicates that a network error or a software bug caused data corruption.
- class IntegrityError < StandardError; end
+ class IntegrityError < Error; end
+
+ # Raised when ActiveStorage::Blob#download is called on a blob where the
+ # backing file is no longer present in its service.
+ class FileNotFoundError < Error; end
end
diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb
index f915518f52..54ba08fb87 100644
--- a/activestorage/lib/active_storage/service.rb
+++ b/activestorage/lib/active_storage/service.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "active_storage/log_subscriber"
+require "action_dispatch"
+require "action_dispatch/http/content_disposition"
module ActiveStorage
# Abstract class serving as an interface for concrete services.
@@ -122,7 +124,8 @@ module ActiveStorage
end
def content_disposition_with(type: "inline", filename:)
- (type.to_s.presence_in(%w( attachment inline )) || "inline") + "; #{filename.parameters}"
+ disposition = (type.to_s.presence_in(%w( attachment inline )) || "inline")
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename.sanitized)
end
end
end
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index b26234c722..8de3889cb5 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -19,10 +19,8 @@ module ActiveStorage
def upload(key, io, checksum: nil)
instrument :upload, key: key, checksum: checksum do
- begin
+ handle_errors do
blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum)
- rescue Azure::Core::Http::HTTPError
- raise ActiveStorage::IntegrityError
end
end
end
@@ -34,16 +32,20 @@ module ActiveStorage
end
else
instrument :download, key: key do
- _, io = blobs.get_blob(container, key)
- io.force_encoding(Encoding::BINARY)
+ handle_errors do
+ _, io = blobs.get_blob(container, key)
+ io.force_encoding(Encoding::BINARY)
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
- io.force_encoding(Encoding::BINARY)
+ handle_errors do
+ _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
+ io.force_encoding(Encoding::BINARY)
+ end
end
end
@@ -51,7 +53,8 @@ module ActiveStorage
instrument :delete, key: key do
begin
blobs.delete_blob(container, key)
- rescue Azure::Core::Http::HTTPError
+ rescue Azure::Core::Http::HTTPError => e
+ raise unless e.type == "BlobNotFound"
# Ignore files already deleted
end
end
@@ -139,11 +142,26 @@ module ActiveStorage
chunk_size = 5.megabytes
offset = 0
+ raise ActiveStorage::FileNotFoundError unless blob.present?
+
while offset < blob.properties[:content_length]
_, chunk = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
yield chunk.force_encoding(Encoding::BINARY)
offset += chunk_size
end
end
+
+ def handle_errors
+ yield
+ rescue Azure::Core::Http::HTTPError => e
+ case e.type
+ when "BlobNotFound"
+ raise ActiveStorage::FileNotFoundError
+ when "Md5Mismatch"
+ raise ActiveStorage::IntegrityError
+ else
+ raise
+ end
+ end
end
end
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index 9f304b7e01..52f3a3df16 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -22,27 +22,31 @@ module ActiveStorage
end
end
- def download(key)
+ def download(key, &block)
if block_given?
instrument :streaming_download, key: key do
- File.open(path_for(key), "rb") do |file|
- while data = file.read(5.megabytes)
- yield data
- end
- end
+ stream key, &block
end
else
instrument :download, key: key do
- File.binread path_for(key)
+ begin
+ File.binread path_for(key)
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- File.open(path_for(key), "rb") do |file|
- file.seek range.begin
- file.read range.size
+ begin
+ File.open(path_for(key), "rb") do |file|
+ file.seek range.begin
+ file.read range.size
+ end
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
end
end
end
@@ -122,6 +126,16 @@ module ActiveStorage
end
private
+ def stream(key)
+ File.open(path_for(key), "rb") do |file|
+ while data = file.read(5.megabytes)
+ yield data
+ end
+ end
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
+ end
+
def folder_for(key)
[ key[0..1], key[2..3] ].join("/")
end
diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index eb46973509..18c0f14cfc 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -34,14 +34,22 @@ module ActiveStorage
end
else
instrument :download, key: key do
- file_for(key).download.string
+ begin
+ file_for(key).download.string
+ rescue Google::Cloud::NotFoundError
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- file_for(key).download(range: range).string
+ begin
+ file_for(key).download(range: range).string
+ rescue Google::Cloud::NotFoundError
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
@@ -116,6 +124,8 @@ module ActiveStorage
chunk_size = 5.megabytes
offset = 0
+ raise ActiveStorage::FileNotFoundError unless file.present?
+
while offset < file.size
yield file.download(range: offset..(offset + chunk_size - 1)).string
offset += chunk_size
diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb
index 0286e7ff21..89a9e54158 100644
--- a/activestorage/lib/active_storage/service/s3_service.rb
+++ b/activestorage/lib/active_storage/service/s3_service.rb
@@ -33,14 +33,22 @@ module ActiveStorage
end
else
instrument :download, key: key do
- object_for(key).get.body.string.force_encoding(Encoding::BINARY)
+ begin
+ object_for(key).get.body.string.force_encoding(Encoding::BINARY)
+ rescue Aws::S3::Errors::NoSuchKey
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY)
+ begin
+ object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY)
+ rescue Aws::S3::Errors::NoSuchKey
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
@@ -103,6 +111,8 @@ module ActiveStorage
chunk_size = 5.megabytes
offset = 0
+ raise ActiveStorage::FileNotFoundError unless object.exists?
+
while offset < object.content_length
yield object.get(range: "bytes=#{offset}-#{offset + chunk_size - 1}").body.read.force_encoding(Encoding::BINARY)
offset += chunk_size
diff --git a/activestorage/lib/active_storage/transformers/image_processing_transformer.rb b/activestorage/lib/active_storage/transformers/image_processing_transformer.rb
new file mode 100644
index 0000000000..7f8685b72d
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/image_processing_transformer.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require "image_processing"
+
+module ActiveStorage
+ module Transformers
+ class ImageProcessingTransformer < Transformer
+ private
+ def process(file, format:)
+ processor.
+ source(file).
+ loader(page: 0).
+ convert(format).
+ apply(operations).
+ call
+ end
+
+ def processor
+ ImageProcessing.const_get(ActiveStorage.variant_processor.to_s.camelize)
+ end
+
+ def operations
+ transformations.each_with_object([]) do |(name, argument), list|
+ if name.to_s == "combine_options"
+ ActiveSupport::Deprecation.warn <<~WARNING
+ Active Storage's ImageProcessing transformer doesn't support :combine_options,
+ as it always generates a single ImageMagick command. Passing :combine_options will
+ not be supported in Rails 6.1.
+ WARNING
+
+ list.concat argument.keep_if { |key, value| value.present? }.to_a
+ elsif argument.present?
+ list << [ name, argument ]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb b/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb
new file mode 100644
index 0000000000..e8e99cea9e
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "mini_magick"
+
+module ActiveStorage
+ module Transformers
+ class MiniMagickTransformer < Transformer
+ private
+ def process(file, format:)
+ image = MiniMagick::Image.new(file.path, file)
+
+ transformations.each do |name, argument_or_subtransformations|
+ image.mogrify do |command|
+ if name.to_s == "combine_options"
+ argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
+ pass_transform_argument(command, subtransformation_name, subtransformation_argument)
+ end
+ else
+ pass_transform_argument(command, name, argument_or_subtransformations)
+ end
+ end
+ end
+
+ image.format(format) if format
+
+ image.tempfile.tap(&:open)
+ end
+
+ def pass_transform_argument(command, method, argument)
+ if argument == true
+ command.public_send(method)
+ elsif argument.present?
+ command.public_send(method, argument)
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/transformers/transformer.rb b/activestorage/lib/active_storage/transformers/transformer.rb
new file mode 100644
index 0000000000..2e21201004
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/transformer.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Transformers
+ # A Transformer applies a set of transformations to an image.
+ #
+ # The following concrete subclasses are included in Active Storage:
+ #
+ # * ActiveStorage::Transformers::ImageProcessingTransformer:
+ # backed by ImageProcessing, a common interface for MiniMagick and ruby-vips
+ #
+ # * ActiveStorage::Transformers::MiniMagickTransformer:
+ # backed by MiniMagick, a wrapper around the ImageMagick CLI
+ class Transformer
+ attr_reader :transformations
+
+ def initialize(transformations)
+ @transformations = transformations
+ end
+
+ # Applies the transformations to the source image in +file+, producing a target image in the
+ # specified +format+. Yields an open Tempfile containing the target image. Closes and unlinks
+ # the output tempfile after yielding to the given block. Returns the result of the block.
+ def transform(file, format:)
+ output = process(file, format: format)
+
+ begin
+ yield output
+ ensure
+ output.close!
+ end
+ end
+
+ private
+ # Returns an open Tempfile containing a transformed image in the given +format+.
+ # All subclasses implement this method.
+ def process(file, format:) #:doc:
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb
index c053052f6f..4bc61d13f3 100644
--- a/activestorage/test/controllers/disk_controller_test.rb
+++ b/activestorage/test/controllers/disk_controller_test.rb
@@ -31,6 +31,14 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
assert_equal " worl", response.body
end
+ test "showing blob that does not exist" do
+ blob = create_blob
+ blob.delete
+
+ get blob.service_url
+ assert_response :not_found
+ end
+
test "directly uploading blob with integrity" do
data = "Something else entirely!"
diff --git a/activestorage/test/dummy/app/assets/config/manifest.js b/activestorage/test/dummy/app/assets/config/manifest.js
index a8adebe722..bb109908b2 100644
--- a/activestorage/test/dummy/app/assets/config/manifest.js
+++ b/activestorage/test/dummy/app/assets/config/manifest.js
@@ -1,5 +1,3 @@
//= link_tree ../images
-//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
-//= link active_storage_manifest.js
diff --git a/activestorage/test/dummy/config/environments/production.rb b/activestorage/test/dummy/config/environments/production.rb
index 34ac3bc561..be7f5b80d4 100644
--- a/activestorage/test/dummy/config/environments/production.rb
+++ b/activestorage/test/dummy/config/environments/production.rb
@@ -25,8 +25,7 @@ Rails.application.configure do
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
- # Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
+ # Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
diff --git a/activestorage/test/dummy/config/webpacker.yml b/activestorage/test/dummy/config/webpacker.yml
new file mode 100644
index 0000000000..c1515a2e95
--- /dev/null
+++ b/activestorage/test/dummy/config/webpacker.yml
@@ -0,0 +1,72 @@
+# Note: You must restart bin/webpack-dev-server for changes to take effect
+
+default: &default
+ source_path: app/javascript
+ source_entry_path: packs
+ public_output_path: packs
+ cache_path: tmp/cache/webpacker
+ check_yarn_integrity: false
+
+ # Additional paths webpack should lookup modules
+ # ['app/assets', 'engine/foo/app/assets']
+ resolved_paths: []
+
+ # Reload manifest.json on all requests so we reload latest compiled packs
+ cache_manifest: false
+
+ extensions:
+ - .js
+ - .sass
+ - .scss
+ - .css
+ - .module.sass
+ - .module.scss
+ - .module.css
+ - .png
+ - .svg
+ - .gif
+ - .jpeg
+ - .jpg
+
+development:
+ <<: *default
+ compile: true
+
+ # Verifies that versions and hashed value of the package contents in the project's package.json
+ check_yarn_integrity: true
+
+ # Reference: https://webpack.js.org/configuration/dev-server/
+ dev_server:
+ https: false
+ host: localhost
+ port: 3035
+ public: localhost:3035
+ hmr: false
+ # Inline should be set to true if using HMR
+ inline: true
+ overlay: true
+ compress: true
+ disable_host_check: true
+ use_local_ip: false
+ quiet: false
+ headers:
+ 'Access-Control-Allow-Origin': '*'
+ watch_options:
+ ignored: /node_modules/
+
+
+test:
+ <<: *default
+ compile: true
+
+ # Compile test packs to a separate directory
+ public_output_path: packs-test
+
+production:
+ <<: *default
+
+ # Production depends on precompilation of packs prior to booting for performance.
+ compile: false
+
+ # Cache manifest.json for performance
+ cache_manifest: true
diff --git a/activestorage/test/fixtures/files/empty_file.txt b/activestorage/test/fixtures/files/empty_file.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activestorage/test/fixtures/files/empty_file.txt
diff --git a/activestorage/test/jobs/purge_job_test.rb b/activestorage/test/jobs/purge_job_test.rb
index ed4100b78d..251022a96f 100644
--- a/activestorage/test/jobs/purge_job_test.rb
+++ b/activestorage/test/jobs/purge_job_test.rb
@@ -24,14 +24,4 @@ class ActiveStorage::PurgeJobTest < ActiveJob::TestCase
end
end
end
-
- test "ignores attached blob" do
- User.create! name: "DHH", avatar: @blob
-
- perform_enqueued_jobs do
- assert_nothing_raised do
- ActiveStorage::PurgeJob.perform_later @blob
- end
- end
- end
end
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index 88c106a08b..1a6a89de56 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -185,7 +185,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
private
def expected_url_for(blob, disposition: :inline, filename: nil)
filename ||= blob.filename
- query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{filename.parameters}" }.to_param
+ query_string = { content_type: blob.content_type, disposition: ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename.sanitized) }.to_param
"https://example.com/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{filename}?#{query_string}"
end
end
diff --git a/activestorage/test/models/filename/parameters_test.rb b/activestorage/test/models/filename/parameters_test.rb
deleted file mode 100644
index 431be00639..0000000000
--- a/activestorage/test/models/filename/parameters_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-
-class ActiveStorage::Filename::ParametersTest < ActiveSupport::TestCase
- test "parameterizing a Latin filename" do
- filename = ActiveStorage::Filename.new("racecar.jpg")
-
- assert_equal %(filename="racecar.jpg"), filename.parameters.ascii
- assert_equal "filename*=UTF-8''racecar.jpg", filename.parameters.utf8
- assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined
- assert_equal filename.parameters.combined, filename.parameters.to_s
- end
-
- test "parameterizing a Latin filename with accented characters" do
- filename = ActiveStorage::Filename.new("råcëçâr.jpg")
-
- assert_equal %(filename="racecar.jpg"), filename.parameters.ascii
- assert_equal "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg", filename.parameters.utf8
- assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined
- assert_equal filename.parameters.combined, filename.parameters.to_s
- end
-
- test "parameterizing a non-Latin filename" do
- filename = ActiveStorage::Filename.new("автомобиль.jpg")
-
- assert_equal %(filename="%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F.jpg"), filename.parameters.ascii
- assert_equal "filename*=UTF-8''%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg", filename.parameters.utf8
- assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined
- assert_equal filename.parameters.combined, filename.parameters.to_s
- end
-end
diff --git a/activestorage/test/models/filename_test.rb b/activestorage/test/models/filename_test.rb
index 88405e41c0..715116309f 100644
--- a/activestorage/test/models/filename_test.rb
+++ b/activestorage/test/models/filename_test.rb
@@ -30,8 +30,8 @@ class ActiveStorage::FilenameTest < ActiveSupport::TestCase
end
test "sanitize transcodes to valid UTF-8" do
- { "\xF6".dup.force_encoding(Encoding::ISO8859_1) => "ö",
- "\xC3".dup.force_encoding(Encoding::ISO8859_1) => "Ã",
+ { (+"\xF6").force_encoding(Encoding::ISO8859_1) => "ö",
+ (+"\xC3").force_encoding(Encoding::ISO8859_1) => "Ã",
"\xAD" => "�",
"\xCF" => "�",
"\x00" => "",
diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb
index 4bfcda017f..559aa028f2 100644
--- a/activestorage/test/service/s3_service_test.rb
+++ b/activestorage/test/service/s3_service_test.rb
@@ -31,6 +31,13 @@ if SERVICE_CONFIGURATIONS[:s3]
end
end
+ test "upload a zero byte file" do
+ blob = directly_upload_file_blob filename: "empty_file.txt", content_type: nil
+ user = User.create! name: "DHH", avatar: blob
+
+ assert_equal user.avatar.blob, blob
+ end
+
test "signed URL generation" do
url = @service.url(@key, expires_in: 5.minutes,
disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb
index 30cfca4e36..ca2490f2bc 100644
--- a/activestorage/test/service/shared_service_tests.rb
+++ b/activestorage/test/service/shared_service_tests.rb
@@ -6,7 +6,7 @@ require "active_support/core_ext/securerandom"
module ActiveStorage::Service::SharedServiceTests
extend ActiveSupport::Concern
- FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".dup.force_encoding(Encoding::BINARY)
+ FIXTURE_DATA = (+"\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202").force_encoding(Encoding::BINARY)
included do
setup do
@@ -50,6 +50,13 @@ module ActiveStorage::Service::SharedServiceTests
assert_equal FIXTURE_DATA, @service.download(@key)
end
+ test "downloading a nonexistent file" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download(SecureRandom.base58(24))
+ end
+ end
+
+
test "downloading in chunks" do
key = SecureRandom.base58(24)
expected_chunks = [ "a" * 5.megabytes, "b" ]
@@ -68,11 +75,25 @@ module ActiveStorage::Service::SharedServiceTests
end
end
+ test "downloading a nonexistent file in chunks" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download(SecureRandom.base58(24)) { }
+ end
+ end
+
+
test "downloading partially" do
assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19..21)
assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19...22)
end
+ test "partially downloading a nonexistent file" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download_chunk(SecureRandom.base58(24), 19..21)
+ end
+ end
+
+
test "existing" do
assert @service.exist?(@key)
assert_not @service.exist?(@key + "nonsense")
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 7b7926ac79..144c224421 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -18,8 +18,7 @@ require "active_job"
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.logger = ActiveSupport::Logger.new(nil)
-# Filter out Minitest backtrace while allowing backtrace from other libraries
-# to be shown.
+# Filter out the backtrace from minitest while preserving the one from other libraries.
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
require "yaml"