activestorage 5.2.4.4 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +180 -69
- data/MIT-LICENSE +1 -1
- data/README.md +43 -8
- data/app/assets/javascripts/activestorage.js +5 -2
- data/app/controllers/active_storage/base_controller.rb +13 -4
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
- data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
- data/app/controllers/active_storage/disk_controller.rb +13 -22
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -3
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/javascript/activestorage/blob_record.js +7 -2
- data/app/jobs/active_storage/analyze_job.rb +5 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +35 -16
- data/app/models/active_storage/blob.rb +178 -68
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +36 -6
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +37 -12
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +53 -67
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +30 -34
- data/config/routes.rb +66 -15
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage.rb +29 -6
- data/lib/active_storage/analyzer.rb +15 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +47 -0
- data/lib/active_storage/attached/changes/create_one.rb +82 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/many.rb +19 -12
- data/lib/active_storage/attached/model.rb +212 -0
- data/lib/active_storage/attached/one.rb +19 -21
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/engine.rb +58 -23
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +24 -13
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +5 -5
- data/lib/active_storage/previewer/video_previewer.rb +17 -10
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +44 -12
- data/lib/active_storage/service/azure_storage_service.rb +65 -44
- data/lib/active_storage/service/configurator.rb +6 -2
- data/lib/active_storage/service/disk_service.rb +57 -44
- data/lib/active_storage/service/gcs_service.rb +68 -64
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +58 -24
- data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
- data/lib/active_storage/transformers/transformer.rb +39 -0
- data/lib/tasks/activestorage.rake +7 -0
- metadata +84 -19
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
- data/lib/active_storage/downloading.rb +0 -39
@@ -29,12 +29,16 @@ module ActiveStorage::Blob::Analyzable
|
|
29
29
|
update! metadata: metadata.merge(extract_metadata_via_analyzer)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
|
32
|
+
# Enqueues an ActiveStorage::AnalyzeJob which calls #analyze, or calls #analyze inline based on analyzer class configuration.
|
33
33
|
#
|
34
34
|
# This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob
|
35
35
|
# again (e.g. if you add a new analyzer or modify an existing one).
|
36
36
|
def analyze_later
|
37
|
-
|
37
|
+
if analyzer_class.analyze_later?
|
38
|
+
ActiveStorage::AnalyzeJob.perform_later(self)
|
39
|
+
else
|
40
|
+
analyze
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# Returns true if the blob has been analyzed.
|
@@ -2,9 +2,14 @@
|
|
2
2
|
|
3
3
|
module ActiveStorage::Blob::Identifiable
|
4
4
|
def identify
|
5
|
+
identify_without_saving
|
6
|
+
save!
|
7
|
+
end
|
8
|
+
|
9
|
+
def identify_without_saving
|
5
10
|
unless identified?
|
6
|
-
|
7
|
-
|
11
|
+
self.content_type = identify_content_type
|
12
|
+
self.identified = true
|
8
13
|
end
|
9
14
|
end
|
10
15
|
|
@@ -24,8 +29,4 @@ module ActiveStorage::Blob::Identifiable
|
|
24
29
|
""
|
25
30
|
end
|
26
31
|
end
|
27
|
-
|
28
|
-
def update_service_metadata
|
29
|
-
service.update_metadata key, service_metadata if service_metadata.any?
|
30
|
-
end
|
31
32
|
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "mimemagic"
|
4
|
+
|
3
5
|
module ActiveStorage::Blob::Representable
|
4
6
|
extend ActiveSupport::Concern
|
5
7
|
|
6
8
|
included do
|
9
|
+
has_many :variant_records, class_name: "ActiveStorage::VariantRecord", dependent: false
|
10
|
+
before_destroy { variant_records.destroy_all if ActiveStorage.track_variants }
|
11
|
+
|
7
12
|
has_one_attached :preview_image
|
8
13
|
end
|
9
14
|
|
10
15
|
# Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
|
11
16
|
# files, and it allows any image to be transformed for size, colors, and the like. Example:
|
12
17
|
#
|
13
|
-
# avatar.variant(
|
18
|
+
# avatar.variant(resize_to_limit: [100, 100]).processed.url
|
14
19
|
#
|
15
20
|
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
|
16
21
|
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
|
@@ -18,7 +23,7 @@ module ActiveStorage::Blob::Representable
|
|
18
23
|
# Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
|
19
24
|
# specific variant that can be created by a controller on-demand. Like so:
|
20
25
|
#
|
21
|
-
# <%= image_tag Current.user.avatar.variant(
|
26
|
+
# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
|
22
27
|
#
|
23
28
|
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
|
24
29
|
# can then produce on-demand.
|
@@ -27,7 +32,7 @@ module ActiveStorage::Blob::Representable
|
|
27
32
|
# variable, call ActiveStorage::Blob#variable?.
|
28
33
|
def variant(transformations)
|
29
34
|
if variable?
|
30
|
-
|
35
|
+
variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
|
31
36
|
else
|
32
37
|
raise ActiveStorage::InvariableError
|
33
38
|
end
|
@@ -43,13 +48,13 @@ module ActiveStorage::Blob::Representable
|
|
43
48
|
# from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
|
44
49
|
# extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
|
45
50
|
#
|
46
|
-
# blob.preview(
|
51
|
+
# blob.preview(resize_to_limit: [100, 100]).processed.url
|
47
52
|
#
|
48
53
|
# Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
|
49
54
|
# Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
|
50
55
|
# how to use the built-in version:
|
51
56
|
#
|
52
|
-
# <%= image_tag video.preview(
|
57
|
+
# <%= image_tag video.preview(resize_to_limit: [100, 100]) %>
|
53
58
|
#
|
54
59
|
# This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine
|
55
60
|
# whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
|
@@ -69,7 +74,7 @@ module ActiveStorage::Blob::Representable
|
|
69
74
|
|
70
75
|
# Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
|
71
76
|
#
|
72
|
-
# blob.representation(
|
77
|
+
# blob.representation(resize_to_limit: [100, 100]).processed.url
|
73
78
|
#
|
74
79
|
# Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
|
75
80
|
# ActiveStorage::Blob#representable? to determine whether a blob is representable.
|
@@ -90,4 +95,29 @@ module ActiveStorage::Blob::Representable
|
|
90
95
|
def representable?
|
91
96
|
variable? || previewable?
|
92
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def default_variant_transformations
|
101
|
+
{ format: default_variant_format }
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_variant_format
|
105
|
+
if web_image?
|
106
|
+
format || :png
|
107
|
+
else
|
108
|
+
:png
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def format
|
113
|
+
if filename.extension.present? && MimeMagic.by_extension(filename.extension)&.to_s == content_type
|
114
|
+
filename.extension
|
115
|
+
else
|
116
|
+
MimeMagic.new(content_type).extensions.first
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def variant_class
|
121
|
+
ActiveStorage.track_variants ? ActiveStorage::VariantWithRecord : ActiveStorage::Variant
|
122
|
+
end
|
93
123
|
end
|
@@ -3,8 +3,6 @@
|
|
3
3
|
# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
|
4
4
|
# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
|
5
5
|
class ActiveStorage::Filename
|
6
|
-
require_dependency "active_storage/filename/parameters"
|
7
|
-
|
8
6
|
include Comparable
|
9
7
|
|
10
8
|
class << self
|
@@ -60,10 +58,6 @@ class ActiveStorage::Filename
|
|
60
58
|
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
61
59
|
end
|
62
60
|
|
63
|
-
def parameters #:nodoc:
|
64
|
-
Parameters.new self
|
65
|
-
end
|
66
|
-
|
67
61
|
# Returns the sanitized version of the filename.
|
68
62
|
def to_s
|
69
63
|
sanitized.to_s
|
@@ -3,8 +3,9 @@
|
|
3
3
|
# Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
|
4
4
|
# extracting its first frame, and a PDF blob can be previewed by extracting its first page.
|
5
5
|
#
|
6
|
-
# A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs
|
7
|
-
# ActiveStorage::Previewer::VideoPreviewer
|
6
|
+
# A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs.
|
7
|
+
# ActiveStorage::Previewer::VideoPreviewer is used for videos whereas ActiveStorage::Previewer::PopplerPDFPreviewer
|
8
|
+
# and ActiveStorage::Previewer::MuPDFPreviewer are used for PDFs. Build custom previewers by
|
8
9
|
# subclassing ActiveStorage::Previewer and implementing the requisite methods. Consult the ActiveStorage::Previewer
|
9
10
|
# documentation for more details on what's required of previewers.
|
10
11
|
#
|
@@ -13,17 +14,17 @@
|
|
13
14
|
# by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
|
14
15
|
#
|
15
16
|
# Rails.application.config.active_storage.previewers
|
16
|
-
# # => [ ActiveStorage::Previewer::
|
17
|
+
# # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
|
17
18
|
#
|
18
19
|
# # Add a custom previewer for Microsoft Office documents:
|
19
20
|
# Rails.application.config.active_storage.previewers << DOCXPreviewer
|
20
|
-
# # => [ ActiveStorage::Previewer::
|
21
|
+
# # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
|
21
22
|
#
|
22
23
|
# Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
|
23
24
|
#
|
24
25
|
# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
|
25
|
-
# {
|
26
|
-
# and the other requires {
|
26
|
+
# {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
|
27
|
+
# and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
|
27
28
|
#
|
28
29
|
# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
|
29
30
|
# install and use third-party software, make sure you understand the licensing implications of doing so.
|
@@ -38,7 +39,7 @@ class ActiveStorage::Preview
|
|
38
39
|
|
39
40
|
# Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
|
40
41
|
#
|
41
|
-
# blob.preview(
|
42
|
+
# blob.preview(resize_to_limit: [100, 100]).processed.url
|
42
43
|
#
|
43
44
|
# Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
|
44
45
|
# image is stored with the blob, it is only generated once.
|
@@ -56,10 +57,30 @@ class ActiveStorage::Preview
|
|
56
57
|
# preview has not been processed yet.
|
57
58
|
#
|
58
59
|
# This method synchronously processes a variant of the preview image, so do not call it in views. Instead, generate
|
59
|
-
# a stable URL that redirects to the
|
60
|
-
def
|
60
|
+
# a stable URL that redirects to the URL returned by this method.
|
61
|
+
def url(**options)
|
61
62
|
if processed?
|
62
|
-
variant.
|
63
|
+
variant.url(**options)
|
64
|
+
else
|
65
|
+
raise UnprocessedError
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :service_url, :url
|
70
|
+
deprecate service_url: :url
|
71
|
+
|
72
|
+
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
73
|
+
def key
|
74
|
+
if processed?
|
75
|
+
variant.key
|
76
|
+
else
|
77
|
+
raise UnprocessedError
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def download(&block)
|
82
|
+
if processed?
|
83
|
+
variant.download(&block)
|
63
84
|
else
|
64
85
|
raise UnprocessedError
|
65
86
|
end
|
@@ -71,11 +92,15 @@ class ActiveStorage::Preview
|
|
71
92
|
end
|
72
93
|
|
73
94
|
def process
|
74
|
-
previewer.preview
|
95
|
+
previewer.preview(service_name: blob.service_name) do |attachable|
|
96
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
97
|
+
image.attach(attachable)
|
98
|
+
end
|
99
|
+
end
|
75
100
|
end
|
76
101
|
|
77
102
|
def variant
|
78
|
-
|
103
|
+
image.variant(variation).processed
|
79
104
|
end
|
80
105
|
|
81
106
|
|
@@ -1,24 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_storage/downloading"
|
4
|
-
|
5
3
|
# Image blobs can have variants that are the result of a set of transformations applied to the original.
|
6
4
|
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
|
7
5
|
# original.
|
8
6
|
#
|
9
|
-
# Variants rely on {
|
10
|
-
# of the file, so you must add <tt>gem "
|
7
|
+
# Variants rely on {ImageProcessing}[https://github.com/janko-m/image_processing] gem for the actual transformations
|
8
|
+
# of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
|
9
|
+
# default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
|
10
|
+
# {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
|
11
|
+
# {libvips}[http://libvips.github.io/libvips/] processor operated by the {ruby-vips}[https://github.com/libvips/ruby-vips]
|
12
|
+
# gem).
|
13
|
+
#
|
14
|
+
# Rails.application.config.active_storage.variant_processor
|
15
|
+
# # => :mini_magick
|
16
|
+
#
|
17
|
+
# Rails.application.config.active_storage.variant_processor = :vips
|
18
|
+
# # => :vips
|
11
19
|
#
|
12
|
-
# Note that to create a variant it's necessary to download the entire blob file from the service
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# template, for example. Delay the processing to an on-demand controller, like the one provided in
|
20
|
+
# Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
|
21
|
+
# you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
|
22
|
+
# in a template, for example. Delay the processing to an on-demand controller, like the one provided in
|
16
23
|
# ActiveStorage::RepresentationsController.
|
17
24
|
#
|
18
25
|
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
|
19
26
|
# by Active Storage like so:
|
20
27
|
#
|
21
|
-
# <%= image_tag Current.user.avatar.variant(
|
28
|
+
# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
|
22
29
|
#
|
23
30
|
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
|
24
31
|
# can then produce on-demand.
|
@@ -27,22 +34,26 @@ require "active_storage/downloading"
|
|
27
34
|
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
|
28
35
|
# the transformations, upload the variant to the service, and return itself again. Example:
|
29
36
|
#
|
30
|
-
# avatar.variant(
|
37
|
+
# avatar.variant(resize_to_limit: [100, 100]).processed.url
|
31
38
|
#
|
32
39
|
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
|
33
40
|
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
|
34
41
|
#
|
35
|
-
#
|
36
|
-
#
|
42
|
+
# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
|
43
|
+
# ImageProcessing gem (such as +resize_to_limit+):
|
44
|
+
#
|
45
|
+
# avatar.variant(resize_to_limit: [800, 800], monochrome: true, rotate: "-90")
|
46
|
+
#
|
47
|
+
# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
|
37
48
|
#
|
38
|
-
#
|
49
|
+
# * {ImageProcessing::MiniMagick}[https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md#methods]
|
50
|
+
# * {ImageMagick reference}[https://www.imagemagick.org/script/mogrify.php]
|
51
|
+
# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
|
52
|
+
# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
|
39
53
|
class ActiveStorage::Variant
|
40
|
-
include ActiveStorage::Downloading
|
41
|
-
|
42
|
-
WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
|
43
|
-
|
44
54
|
attr_reader :blob, :variation
|
45
55
|
delegate :service, to: :blob
|
56
|
+
delegate :content_type, to: :variation
|
46
57
|
|
47
58
|
def initialize(blob, variation_or_variation_key)
|
48
59
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
|
@@ -59,18 +70,34 @@ class ActiveStorage::Variant
|
|
59
70
|
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
|
60
71
|
end
|
61
72
|
|
62
|
-
# Returns the URL of the variant on the service.
|
63
|
-
# with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
|
64
|
-
# Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
|
65
|
-
# it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
|
73
|
+
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
|
66
74
|
#
|
67
75
|
# Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
|
68
76
|
# for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
|
69
77
|
# for its redirection.
|
70
|
-
def
|
78
|
+
def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
|
71
79
|
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
|
72
80
|
end
|
73
81
|
|
82
|
+
alias_method :service_url, :url
|
83
|
+
deprecate service_url: :url
|
84
|
+
|
85
|
+
# Downloads the file associated with this variant. If no block is given, the entire file is read into memory and returned.
|
86
|
+
# That'll use a lot of RAM for very large files. If a block is given, then the download is streamed and yielded in chunks.
|
87
|
+
def download(&block)
|
88
|
+
service.download key, &block
|
89
|
+
end
|
90
|
+
|
91
|
+
def filename
|
92
|
+
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format}"
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :content_type_for_serving, :content_type
|
96
|
+
|
97
|
+
def forced_disposition_for_serving #:nodoc:
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
74
101
|
# Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
|
75
102
|
def image
|
76
103
|
self
|
@@ -82,51 +109,10 @@ class ActiveStorage::Variant
|
|
82
109
|
end
|
83
110
|
|
84
111
|
def process
|
85
|
-
|
86
|
-
transform
|
87
|
-
|
88
|
-
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
def filename
|
94
|
-
if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
|
95
|
-
blob.filename
|
96
|
-
else
|
97
|
-
ActiveStorage::Filename.new("#{blob.filename.base}.png")
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def content_type
|
102
|
-
blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
|
103
|
-
end
|
104
|
-
|
105
|
-
|
106
|
-
def open_image(&block)
|
107
|
-
image = download_image
|
108
|
-
|
109
|
-
begin
|
110
|
-
yield image
|
111
|
-
ensure
|
112
|
-
image.destroy!
|
112
|
+
blob.open do |input|
|
113
|
+
variation.transform(input) do |output|
|
114
|
+
service.upload(key, output, content_type: content_type)
|
115
|
+
end
|
113
116
|
end
|
114
117
|
end
|
115
|
-
|
116
|
-
def download_image
|
117
|
-
require "mini_magick"
|
118
|
-
MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) }
|
119
|
-
end
|
120
|
-
|
121
|
-
def transform(image)
|
122
|
-
variation.transform(image)
|
123
|
-
end
|
124
|
-
|
125
|
-
def format(image)
|
126
|
-
image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
|
127
|
-
end
|
128
|
-
|
129
|
-
def upload(image)
|
130
|
-
File.open(image.path, "r") { |file| service.upload(key, file) }
|
131
|
-
end
|
132
118
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveStorage::VariantWithRecord
|
4
|
+
attr_reader :blob, :variation
|
5
|
+
|
6
|
+
def initialize(blob, variation)
|
7
|
+
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
|
8
|
+
end
|
9
|
+
|
10
|
+
def processed
|
11
|
+
process
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def process
|
16
|
+
transform_blob { |image| create_or_find_record(image: image) } unless processed?
|
17
|
+
end
|
18
|
+
|
19
|
+
def processed?
|
20
|
+
record.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def image
|
24
|
+
record&.image
|
25
|
+
end
|
26
|
+
|
27
|
+
delegate :key, :url, :download, to: :image, allow_nil: true
|
28
|
+
|
29
|
+
alias_method :service_url, :url
|
30
|
+
deprecate service_url: :url
|
31
|
+
|
32
|
+
private
|
33
|
+
def transform_blob
|
34
|
+
blob.open do |input|
|
35
|
+
variation.transform(input) do |output|
|
36
|
+
yield io: output, filename: "#{blob.filename.base}.#{variation.format}",
|
37
|
+
content_type: variation.content_type, service_name: blob.service.name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_or_find_record(image:)
|
43
|
+
@record =
|
44
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
45
|
+
blob.variant_records.create_or_find_by!(variation_digest: variation.digest) do |record|
|
46
|
+
record.image.attach(image)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def record
|
52
|
+
@record ||= blob.variant_records.find_by(variation_digest: variation.digest)
|
53
|
+
end
|
54
|
+
end
|