diff options
author | George Claghorn <george@basecamp.com> | 2018-01-10 12:12:04 -0500 |
---|---|---|
committer | George Claghorn <george@basecamp.com> | 2018-01-10 12:12:04 -0500 |
commit | d4cdd4a05f9874127f5052eb7909c7d921ad046e (patch) | |
tree | 8e8ff3f111f37e770a9d6da4548dab14a0a32135 /activestorage/app/models | |
parent | 5a593486f5c8a73f2be200a409b0a14dff51790c (diff) | |
download | rails-d4cdd4a05f9874127f5052eb7909c7d921ad046e.tar.gz rails-d4cdd4a05f9874127f5052eb7909c7d921ad046e.tar.bz2 rails-d4cdd4a05f9874127f5052eb7909c7d921ad046e.zip |
Extract Analyzable and Representable concerns
Diffstat (limited to 'activestorage/app/models')
3 files changed, 148 insertions, 147 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 |