diff options
Diffstat (limited to 'activestorage/app/models/active_storage')
10 files changed, 58 insertions, 53 deletions
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb index 19f48c57d6..c59877a9a5 100644 --- a/activestorage/app/models/active_storage/attachment.rb +++ b/activestorage/app/models/active_storage/attachment.rb @@ -14,7 +14,7 @@ class ActiveStorage::Attachment < ActiveRecord::Base delegate_missing_to :blob - after_create_commit :identify_blob, :analyze_blob_later + after_create_commit :analyze_blob_later, :identify_blob # Synchronously purges the blob (deletes it from the configured service) and destroys the attachment. def purge diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 892a833fae..0cd4ad8128 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -14,7 +14,13 @@ # 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 - include Analyzable, Identifiable, Representable + require_dependency "active_storage/blob/analyzable" + require_dependency "active_storage/blob/identifiable" + require_dependency "active_storage/blob/representable" + + include Analyzable + include Identifiable + include Representable self.table_name = "active_storage_blobs" @@ -25,6 +31,8 @@ class ActiveStorage::Blob < ActiveRecord::Base has_many :attachments + scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) } + 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 @@ -109,7 +117,9 @@ class ActiveStorage::Blob < ActiveRecord::Base # 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 # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. - def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: self.filename, **options) + def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options) + filename = ActiveStorage::Filename.wrap(filename || self.filename) + service.url key, expires_in: expires_in, filename: filename, content_type: content_type, disposition: forcibly_serve_as_binary? ? :attachment : disposition, **options end @@ -192,4 +202,6 @@ class ActiveStorage::Blob < ActiveRecord::Base def forcibly_serve_as_binary? ActiveStorage.content_types_to_serve_as_binary.include?(content_type) end + + ActiveSupport.run_load_hooks(:active_storage_blob, self) end diff --git a/activestorage/app/models/active_storage/blob/identifiable.rb b/activestorage/app/models/active_storage/blob/identifiable.rb index 40ca84ac70..049e45dc3e 100644 --- a/activestorage/app/models/active_storage/blob/identifiable.rb +++ b/activestorage/app/models/active_storage/blob/identifiable.rb @@ -2,10 +2,19 @@ module ActiveStorage::Blob::Identifiable def identify - ActiveStorage::Identification.new(self).apply + update! content_type: identify_content_type, identified: true unless identified? end def identified? identified end + + private + def identify_content_type + Marcel::MimeType.for download_identifiable_chunk, name: filename.to_s, declared_type: content_type + end + + def download_identifiable_chunk + service.download_chunk key, 0...4.kilobytes + end end diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb index 0ad2e2fd77..fea62e62de 100644 --- a/activestorage/app/models/active_storage/blob/representable.rb +++ b/activestorage/app/models/active_storage/blob/representable.rb @@ -20,14 +20,14 @@ module ActiveStorage::Blob::Representable # # <%= 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 + # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController # can then produce on-demand. # # Raises ActiveStorage::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is # variable, call ActiveStorage::Blob#variable?. def variant(transformations) if variable? - ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations)) + ActiveStorage::Variant.new(self, transformations) else raise ActiveStorage::InvariableError end @@ -55,7 +55,7 @@ module ActiveStorage::Blob::Representable # 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)) + ActiveStorage::Preview.new(self, transformations) else raise ActiveStorage::UnpreviewableError end diff --git a/activestorage/app/models/active_storage/current.rb b/activestorage/app/models/active_storage/current.rb new file mode 100644 index 0000000000..7e431d8462 --- /dev/null +++ b/activestorage/app/models/active_storage/current.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ActiveStorage::Current < ActiveSupport::CurrentAttributes #:nodoc: + attribute :host +end diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb index b9413dec95..bebb5e61b3 100644 --- a/activestorage/app/models/active_storage/filename.rb +++ b/activestorage/app/models/active_storage/filename.rb @@ -3,8 +3,18 @@ # 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 + # Returns a Filename instance based on the given filename. If the filename is a Filename, it is + # returned unmodified. If it is a String, it is passed to ActiveStorage::Filename.new. + def wrap(filename) + filename.kind_of?(self) ? filename : new(filename) + end + end + def initialize(filename) @filename = filename end diff --git a/activestorage/app/models/active_storage/identification.rb b/activestorage/app/models/active_storage/identification.rb deleted file mode 100644 index 4f295257ae..0000000000 --- a/activestorage/app/models/active_storage/identification.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -class ActiveStorage::Identification - attr_reader :blob - - def initialize(blob) - @blob = blob - end - - def apply - blob.update!(content_type: content_type, identified: true) unless blob.identified? - end - - private - def content_type - Marcel::MimeType.for(identifiable_chunk, name: filename, declared_type: declared_content_type) - end - - - def identifiable_chunk - Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client| - client.get(uri, "Range" => "0-4096").body - end - end - - def uri - @uri ||= URI.parse(blob.service_url) - end - - - def filename - blob.filename.to_s - end - - def declared_content_type - blob.content_type - end -end diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb index be5053edae..2b87897183 100644 --- a/activestorage/app/models/active_storage/preview.rb +++ b/activestorage/app/models/active_storage/preview.rb @@ -21,10 +21,9 @@ # # Outside of a Rails application, modify +ActiveStorage.previewers+ instead. # -# The built-in previewers rely on third-party system libraries: -# -# * {ffmpeg}[https://www.ffmpeg.org] -# * {mupdf}[https://mupdf.com] +# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires +# {ffmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org], +# and the other requires {mupdf}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or mupdf. # # These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you # install and use third-party software, make sure you understand the licensing implications of doing so. diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index e08a2271ec..d84208419c 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -13,14 +13,14 @@ require "active_storage/downloading" # into memory. The larger the image, the more memory is used. Because of this process, you also want to be # considerate about when the variant is actually processed. You shouldn't be processing variants inline in a # template, for example. Delay the processing to an on-demand controller, like the one provided in -# ActiveStorage::VariantsController. +# ActiveStorage::RepresentationsController. # # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided # by Active Storage 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 +# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController # can then produce on-demand. # # When you do want to actually produce the variant needed, call +processed+. This will check that the variant @@ -65,7 +65,7 @@ class ActiveStorage::Variant # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. # # Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL - # for a variant that points to the ActiveStorage::VariantsController, which in turn will use this +service_call+ method + # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method # for its redirection. def service_url(expires_in: service.url_expires_in, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type @@ -115,7 +115,7 @@ class ActiveStorage::Variant def download_image require "mini_magick" - MiniMagick::Image.create { |file| download_blob_to(file) } + MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) } end def transform(image) diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb index da4af62666..12e7f9f0b5 100644 --- a/activestorage/app/models/active_storage/variation.rb +++ b/activestorage/app/models/active_storage/variation.rb @@ -8,6 +8,14 @@ # # ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90") # +# You can also combine multiple transformations in one step, e.g. for center-weighted cropping: +# +# ActiveStorage::Variation.new(combine_options: { +# resize: "100x100^", +# gravity: "center", +# crop: "100x100+0+0", +# }) +# # A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. class ActiveStorage::Variation attr_reader :transformations |