aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activestorage/app/models/active_storage/blob.rb149
-rw-r--r--activestorage/app/models/active_storage/blob/analyzable.rb55
-rw-r--r--activestorage/app/models/active_storage/blob/representable.rb91
-rw-r--r--activestorage/lib/active_storage.rb2
-rw-r--r--activestorage/lib/active_storage/errors.rb5
-rw-r--r--activestorage/test/models/preview_test.rb2
-rw-r--r--activestorage/test/models/representation_test.rb2
-rw-r--r--activestorage/test/models/variant_test.rb2
8 files changed, 158 insertions, 150 deletions
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index df4f6f6075..2ac09d3062 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_storage/analyzer/null_analyzer"
-
# A blob is a record that contains the metadata about a file and a key for where that file resides on the service.
# Blobs can be created in two ways:
#
@@ -16,9 +14,7 @@ require "active_storage/analyzer/null_analyzer"
# update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
# If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
class ActiveStorage::Blob < ActiveRecord::Base
- class InvariableError < StandardError; end
- class UnpreviewableError < StandardError; end
- class UnrepresentableError < StandardError; end
+ include Analyzable, Representable
self.table_name = "active_storage_blobs"
@@ -29,8 +25,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
has_many :attachments
- has_one_attached :preview_image
-
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
@@ -69,7 +63,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
end
-
# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
# It uses the framework-wide verifier on <tt>ActiveStorage.verifier</tt>, but with a dedicated purpose.
def signed_id
@@ -112,91 +105,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
- # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
- # files, and it allows any image to be transformed for size, colors, and the like. Example:
- #
- # avatar.variant(resize: "100x100").processed.service_url
- #
- # This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
- # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
- #
- # Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
- # specific variant that can be created by a controller on-demand. Like so:
- #
- # <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
- #
- # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController
- # can then produce on-demand.
- #
- # Raises ActiveStorage::Blob::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is
- # variable, call ActiveStorage::Blob#previewable?.
- def variant(transformations)
- if variable?
- ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations))
- else
- raise InvariableError
- end
- end
-
- # Returns true if ImageMagick can transform the blob (its content type is in +ActiveStorage.variable_content_types+).
- def variable?
- ActiveStorage.variable_content_types.include?(content_type)
- end
-
-
- # Returns an ActiveStorage::Preview instance with the set of +transformations+ provided. A preview is an image generated
- # from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
- # extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
- #
- # blob.preview(resize: "100x100").processed.service_url
- #
- # Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
- # Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
- # how to use the built-in version:
- #
- # <%= image_tag video.preview(resize: "100x100") %>
- #
- # This method raises ActiveStorage::Blob::UnpreviewableError if no previewer accepts the receiving blob. To determine
- # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
- def preview(transformations)
- if previewable?
- ActiveStorage::Preview.new(self, ActiveStorage::Variation.wrap(transformations))
- else
- raise UnpreviewableError
- end
- end
-
- # Returns true if any registered previewer accepts the blob. By default, this will return true for videos and PDF documents.
- def previewable?
- ActiveStorage.previewers.any? { |klass| klass.accept?(self) }
- end
-
-
- # Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
- #
- # blob.representation(resize: "100x100").processed.service_url
- #
- # Raises ActiveStorage::Blob::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
- # ActiveStorage::Blob#representable? to determine whether a blob is representable.
- #
- # See ActiveStorage::Blob#preview and ActiveStorage::Blob#variant for more information.
- def representation(transformations)
- case
- when previewable?
- preview transformations
- when variable?
- variant transformations
- else
- raise UnrepresentableError
- end
- end
-
- # Returns true if the blob is variable or previewable.
- def representable?
- variable? || previewable?
- end
-
-
# Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly
# with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
# Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
@@ -216,6 +124,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum
end
+
# Uploads the +io+ to the service on the +key+ for this blob. Blobs are intended to be immutable, so you shouldn't be
# using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob,
# you should instead simply create a new blob based on the old one.
@@ -240,46 +149,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
- # Extracts and stores metadata from the file associated with this blob using a relevant analyzer. Active Storage comes
- # with built-in analyzers for images and videos. See ActiveStorage::Analyzer::ImageAnalyzer and
- # ActiveStorage::Analyzer::VideoAnalyzer for information about the specific attributes they extract and the third-party
- # libraries they require.
- #
- # To choose the analyzer for a blob, Active Storage calls +accept?+ on each registered analyzer in order. It uses the
- # first analyzer for which +accept?+ returns true when given the blob. If no registered analyzer accepts the blob, no
- # metadata is extracted from it.
- #
- # In a Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+
- # in an initializer:
- #
- # # Add a custom analyzer for Microsoft Office documents:
- # Rails.application.config.active_storage.analyzers.append DOCXAnalyzer
- #
- # # Remove the built-in video analyzer:
- # Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
- #
- # Outside of a Rails application, manipulate +ActiveStorage.analyzers+ instead.
- #
- # You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously
- # analyzed via #analyze_later when they're attached for the first time.
- def analyze
- update! metadata: metadata.merge(extract_metadata_via_analyzer)
- end
-
- # Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
- #
- # This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob
- # again (e.g. if you add a new analyzer or modify an existing one).
- def analyze_later
- ActiveStorage::AnalyzeJob.perform_later(self)
- end
-
- # Returns true if the blob has been analyzed.
- def analyzed?
- analyzed
- end
-
-
# Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be
# deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+
# methods in most circumstances.
@@ -313,20 +182,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
end.base64digest
end
-
- def extract_metadata_via_analyzer
- analyzer.metadata.merge(analyzed: true)
- end
-
- def analyzer
- analyzer_class.new(self)
- end
-
- def analyzer_class
- ActiveStorage.analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
- end
-
-
def forcibly_serve_as_binary?
ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
end
diff --git a/activestorage/app/models/active_storage/blob/analyzable.rb b/activestorage/app/models/active_storage/blob/analyzable.rb
new file mode 100644
index 0000000000..9667990033
--- /dev/null
+++ b/activestorage/app/models/active_storage/blob/analyzable.rb
@@ -0,0 +1,55 @@
+require "active_storage/analyzer/null_analyzer"
+
+module ActiveStorage::Blob::Analyzable
+ # Extracts and stores metadata from the file associated with this blob using a relevant analyzer. Active Storage comes
+ # with built-in analyzers for images and videos. See ActiveStorage::Analyzer::ImageAnalyzer and
+ # ActiveStorage::Analyzer::VideoAnalyzer for information about the specific attributes they extract and the third-party
+ # libraries they require.
+ #
+ # To choose the analyzer for a blob, Active Storage calls +accept?+ on each registered analyzer in order. It uses the
+ # first analyzer for which +accept?+ returns true when given the blob. If no registered analyzer accepts the blob, no
+ # metadata is extracted from it.
+ #
+ # In a Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+
+ # in an initializer:
+ #
+ # # Add a custom analyzer for Microsoft Office documents:
+ # Rails.application.config.active_storage.analyzers.append DOCXAnalyzer
+ #
+ # # Remove the built-in video analyzer:
+ # Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
+ #
+ # Outside of a Rails application, manipulate +ActiveStorage.analyzers+ instead.
+ #
+ # You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously
+ # analyzed via #analyze_later when they're attached for the first time.
+ def analyze
+ update! metadata: metadata.merge(extract_metadata_via_analyzer)
+ end
+
+ # Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
+ #
+ # This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob
+ # again (e.g. if you add a new analyzer or modify an existing one).
+ def analyze_later
+ ActiveStorage::AnalyzeJob.perform_later(self)
+ end
+
+ # Returns true if the blob has been analyzed.
+ def analyzed?
+ analyzed
+ end
+
+ private
+ def extract_metadata_via_analyzer
+ analyzer.metadata.merge(analyzed: true)
+ end
+
+ def analyzer
+ analyzer_class.new(self)
+ end
+
+ def analyzer_class
+ ActiveStorage.analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
+ end
+end
diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb
new file mode 100644
index 0000000000..86f39fdf0c
--- /dev/null
+++ b/activestorage/app/models/active_storage/blob/representable.rb
@@ -0,0 +1,91 @@
+module ActiveStorage::Blob::Representable
+ extend ActiveSupport::Concern
+
+ included do
+ has_one_attached :preview_image
+ end
+
+ # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
+ # files, and it allows any image to be transformed for size, colors, and the like. Example:
+ #
+ # avatar.variant(resize: "100x100").processed.service_url
+ #
+ # This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
+ # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
+ #
+ # Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
+ # specific variant that can be created by a controller on-demand. Like so:
+ #
+ # <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
+ #
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController
+ # can then produce on-demand.
+ #
+ # Raises ActiveStorage::Blob::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is
+ # variable, call ActiveStorage::Blob#previewable?.
+ def variant(transformations)
+ if variable?
+ ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations))
+ else
+ raise ActiveStorage::InvariableError
+ end
+ end
+
+ # Returns true if ImageMagick can transform the blob (its content type is in +ActiveStorage.variable_content_types+).
+ def variable?
+ ActiveStorage.variable_content_types.include?(content_type)
+ end
+
+
+ # Returns an ActiveStorage::Preview instance with the set of +transformations+ provided. A preview is an image generated
+ # from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
+ # extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
+ #
+ # blob.preview(resize: "100x100").processed.service_url
+ #
+ # Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
+ # Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
+ # how to use the built-in version:
+ #
+ # <%= image_tag video.preview(resize: "100x100") %>
+ #
+ # This method raises ActiveStorage::Blob::UnpreviewableError if no previewer accepts the receiving blob. To determine
+ # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
+ def preview(transformations)
+ if previewable?
+ ActiveStorage::Preview.new(self, ActiveStorage::Variation.wrap(transformations))
+ else
+ raise ActiveStorage::UnpreviewableError
+ end
+ end
+
+ # Returns true if any registered previewer accepts the blob. By default, this will return true for videos and PDF documents.
+ def previewable?
+ ActiveStorage.previewers.any? { |klass| klass.accept?(self) }
+ end
+
+
+ # Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
+ #
+ # blob.representation(resize: "100x100").processed.service_url
+ #
+ # Raises ActiveStorage::Blob::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
+ # ActiveStorage::Blob#representable? to determine whether a blob is representable.
+ #
+ # See ActiveStorage::Blob#preview and ActiveStorage::Blob#variant for more information.
+ def representation(transformations)
+ case
+ when previewable?
+ preview transformations
+ when variable?
+ variant transformations
+ else
+ raise ActiveStorage::UnrepresentableError
+ end
+ end
+
+ # Returns true if the blob is variable or previewable.
+ def representable?
+ variable? || previewable?
+ end
+end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index 3bd8ef664f..02442cfbe1 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -26,7 +26,9 @@
require "active_record"
require "active_support"
require "active_support/rails"
+
require "active_storage/version"
+require "active_storage/errors"
module ActiveStorage
extend ActiveSupport::Autoload
diff --git a/activestorage/lib/active_storage/errors.rb b/activestorage/lib/active_storage/errors.rb
new file mode 100644
index 0000000000..db2c7f6bf5
--- /dev/null
+++ b/activestorage/lib/active_storage/errors.rb
@@ -0,0 +1,5 @@
+module ActiveStorage
+ class InvariableError < StandardError; end
+ class UnpreviewableError < StandardError; end
+ class UnrepresentableError < StandardError; end
+end
diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb
index bcd8442f4b..b0c6d2c45b 100644
--- a/activestorage/test/models/preview_test.rb
+++ b/activestorage/test/models/preview_test.rb
@@ -33,7 +33,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase
test "previewing an unpreviewable blob" do
blob = create_file_blob
- assert_raises ActiveStorage::Blob::UnpreviewableError do
+ assert_raises ActiveStorage::UnpreviewableError do
blob.preview resize: "640x280"
end
end
diff --git a/activestorage/test/models/representation_test.rb b/activestorage/test/models/representation_test.rb
index 29fe61aee4..2a06b31c77 100644
--- a/activestorage/test/models/representation_test.rb
+++ b/activestorage/test/models/representation_test.rb
@@ -34,7 +34,7 @@ class ActiveStorage::RepresentationTest < ActiveSupport::TestCase
test "representing an unrepresentable blob" do
blob = create_blob
- assert_raises ActiveStorage::Blob::UnrepresentableError do
+ assert_raises ActiveStorage::UnrepresentableError do
blob.representation resize: "100x100"
end
end
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index 7157bfac3a..49660e880a 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -59,7 +59,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
end
test "variation of invariable blob" do
- assert_raises ActiveStorage::Blob::InvariableError do
+ assert_raises ActiveStorage::InvariableError do
create_file_blob(filename: "report.pdf", content_type: "application/pdf").variant(resize: "100x100")
end
end