aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage/app/models')
-rw-r--r--activestorage/app/models/active_storage/blob.rb29
-rw-r--r--activestorage/app/models/active_storage/filename.rb2
-rw-r--r--activestorage/app/models/active_storage/filename/parameters.rb2
-rw-r--r--activestorage/app/models/active_storage/variant.rb56
-rw-r--r--activestorage/app/models/active_storage/variation.rb20
5 files changed, 91 insertions, 18 deletions
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 2aa05d665e..3b48ee72af 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -16,6 +16,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
@@ -110,6 +111,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
content_type.start_with?("text")
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:
#
@@ -125,8 +127,20 @@ class ActiveStorage::Blob < ActiveRecord::Base
#
# 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)
- ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(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
@@ -158,11 +172,11 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
- # Returns an ActiveStorage::Preview instance for a previewable blob or an ActiveStorage::Variant instance for an image blob.
+ # 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 an image nor previewable. Call
+ # 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.
@@ -170,16 +184,16 @@ class ActiveStorage::Blob < ActiveRecord::Base
case
when previewable?
preview transformations
- when image?
+ when variable?
variant transformations
else
raise UnrepresentableError
end
end
- # Returns true if the blob is an image or is previewable.
+ # Returns true if the blob is variable or previewable.
def representable?
- image? || previewable?
+ variable? || previewable?
end
@@ -270,7 +284,8 @@ class ActiveStorage::Blob < ActiveRecord::Base
# 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.
def delete
- service.delete key
+ service.delete(key)
+ service.delete_prefixed("variants/#{key}/") if image?
end
# Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb
index 79d55dc889..b9413dec95 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -50,7 +50,7 @@ class ActiveStorage::Filename
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
end
- def parameters
+ def parameters #:nodoc:
Parameters.new self
end
diff --git a/activestorage/app/models/active_storage/filename/parameters.rb b/activestorage/app/models/active_storage/filename/parameters.rb
index 58ce198d38..fb9ea10e49 100644
--- a/activestorage/app/models/active_storage/filename/parameters.rb
+++ b/activestorage/app/models/active_storage/filename/parameters.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ActiveStorage::Filename::Parameters
+class ActiveStorage::Filename::Parameters #:nodoc:
attr_reader :filename
def initialize(filename)
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index fa5aa69bd3..e08a2271ec 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 "active_storage/downloading"
+
# 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.
@@ -16,7 +18,7 @@
# 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 url_for(Current.user.avatar.variant(resize: "100x100")) %>
+# <%= 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.
@@ -35,6 +37,10 @@
#
# avatar.variant(resize: "100x100", monochrome: true, flip: "-90")
class ActiveStorage::Variant
+ include ActiveStorage::Downloading
+
+ WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
+
attr_reader :blob, :variation
delegate :service, to: :blob
@@ -62,7 +68,7 @@ class ActiveStorage::Variant
# for a variant that points to the ActiveStorage::VariantsController, 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: blob.filename, content_type: blob.content_type
+ service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
end
# Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
@@ -76,11 +82,51 @@ class ActiveStorage::Variant
end
def process
- service.upload key, transform(service.download(blob.key))
+ open_image do |image|
+ transform image
+ format image
+ upload image
+ end
+ end
+
+
+ def filename
+ if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ blob.filename
+ else
+ ActiveStorage::Filename.new("#{blob.filename.base}.png")
+ end
+ end
+
+ def content_type
+ blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
+ end
+
+
+ def open_image(&block)
+ image = download_image
+
+ begin
+ yield image
+ ensure
+ image.destroy!
+ end
end
- def transform(io)
+ def download_image
require "mini_magick"
- File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path
+ MiniMagick::Image.create { |file| download_blob_to(file) }
+ end
+
+ def transform(image)
+ variation.transform(image)
+ end
+
+ def format(image)
+ image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ end
+
+ def upload(image)
+ File.open(image.path, "r") { |file| service.upload(key, file) }
end
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index 13bad87cac..2b599a4710 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -46,11 +46,15 @@ class ActiveStorage::Variation
# Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
# and performs the +transformations+ against it. The transformed image instance is then returned.
def transform(image)
- transformations.each do |(method, argument)|
- if eligible_argument?(argument)
- image.public_send(method, argument)
+ transformations.each do |(transformation_method, transformation_argument)|
+ if transformation_method.to_s == "combine_options"
+ image.combine_options do |combination|
+ transformation_argument.each do |(method, argument)|
+ pass_transform_argument(combination, method, argument)
+ end
+ end
else
- image.public_send(method)
+ pass_transform_argument(image, transformation_method, transformation_argument)
end
end
end
@@ -64,4 +68,12 @@ class ActiveStorage::Variation
def eligible_argument?(argument)
argument.present? && argument != true
end
+
+ def pass_transform_argument(instance, method, argument)
+ if eligible_argument?(argument)
+ instance.public_send(method, argument)
+ else
+ instance.public_send(method)
+ end
+ end
end