activestorage 5.2.8.1 → 6.0.0.beta1
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 +102 -157
- data/MIT-LICENSE +1 -1
- data/README.md +6 -5
- data/app/assets/javascripts/activestorage.js +4 -1
- data/app/controllers/active_storage/base_controller.rb +3 -5
- data/app/controllers/active_storage/blobs_controller.rb +1 -1
- data/app/controllers/active_storage/disk_controller.rb +4 -1
- data/app/controllers/active_storage/representations_controller.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/javascript/activestorage/blob_record.js +6 -1
- data/app/jobs/active_storage/analyze_job.rb +4 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +18 -9
- data/app/models/active_storage/blob/representable.rb +5 -5
- data/app/models/active_storage/blob.rb +63 -22
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +3 -3
- data/app/models/active_storage/variant.rb +51 -52
- data/app/models/active_storage/variation.rb +23 -92
- data/config/routes.rb +13 -12
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +7 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +2 -4
- data/lib/active_storage/analyzer.rb +9 -4
- data/lib/active_storage/attached/changes/create_many.rb +46 -0
- data/lib/active_storage/attached/changes/create_one.rb +68 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +23 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/many.rb +16 -10
- data/lib/active_storage/attached/model.rb +140 -0
- data/lib/active_storage/attached/one.rb +16 -19
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/downloader.rb +44 -0
- data/lib/active_storage/downloading.rb +8 -0
- data/lib/active_storage/engine.rb +36 -21
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/video_previewer.rb +2 -3
- data/lib/active_storage/previewer.rb +21 -11
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service/azure_storage_service.rb +30 -14
- data/lib/active_storage/service/configurator.rb +3 -1
- data/lib/active_storage/service/disk_service.rb +20 -16
- data/lib/active_storage/service/gcs_service.rb +48 -46
- data/lib/active_storage/service/mirror_service.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +10 -9
- data/lib/active_storage/service.rb +5 -6
- data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
- data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
- data/lib/active_storage/transformers/transformer.rb +42 -0
- data/lib/active_storage.rb +13 -292
- data/lib/tasks/activestorage.rake +7 -0
- metadata +31 -19
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_storage/downloader"
|
4
|
+
|
3
5
|
# A blob is a record that contains the metadata about a file and a key for where that file resides on the service.
|
4
6
|
# Blobs can be created in two ways:
|
5
7
|
#
|
@@ -38,7 +40,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
38
40
|
end
|
39
41
|
|
40
42
|
class << self
|
41
|
-
# You can
|
43
|
+
# You can use the signed ID of a blob to refer to it on the client side without fear of tampering.
|
42
44
|
# This is particularly helpful for direct uploads where the client-side needs to refer to the blob
|
43
45
|
# that was created ahead of the upload itself on form submission.
|
44
46
|
#
|
@@ -48,21 +50,25 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
48
50
|
end
|
49
51
|
|
50
52
|
# Returns a new, unsaved blob instance after the +io+ has been uploaded to the service.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
blob.
|
55
|
-
|
53
|
+
# When providing a content type, pass <tt>identify: false</tt> to bypass automatic content type inference.
|
54
|
+
def build_after_upload(io:, filename:, content_type: nil, metadata: nil, identify: true)
|
55
|
+
new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
|
56
|
+
blob.upload(io, identify: identify)
|
57
|
+
end
|
58
|
+
end
|
56
59
|
|
57
|
-
|
60
|
+
def build_after_unfurling(io:, filename:, content_type: nil, metadata: nil, identify: true) #:nodoc:
|
61
|
+
new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
|
62
|
+
blob.unfurl(io, identify: identify)
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
61
66
|
# Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built,
|
62
67
|
# then the +io+ is uploaded, then the blob is saved. This is done this way to avoid uploading (which may take
|
63
68
|
# time), while having an open database transaction.
|
64
|
-
|
65
|
-
|
69
|
+
# When providing a content type, pass <tt>identify: false</tt> to bypass automatic content type inference.
|
70
|
+
def create_after_upload!(io:, filename:, content_type: nil, metadata: nil, identify: true)
|
71
|
+
build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata, identify: identify).tap(&:save!)
|
66
72
|
end
|
67
73
|
|
68
74
|
# Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is
|
@@ -73,6 +79,15 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
73
79
|
def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
|
74
80
|
create! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata
|
75
81
|
end
|
82
|
+
|
83
|
+
# To prevent problems with case-insensitive filesystems, especially in combination
|
84
|
+
# with databases which treat indices as case-sensitive, all blob keys generated are going
|
85
|
+
# to only contain the base-36 character alphabet and will therefore be lowercase. To maintain
|
86
|
+
# the same or higher amount of entropy as in the base-58 encoding used by `has_secure_token`
|
87
|
+
# the number of bytes used is increased to 28 from the standard 24
|
88
|
+
def generate_unique_secure_token
|
89
|
+
SecureRandom.base36(28)
|
90
|
+
end
|
76
91
|
end
|
77
92
|
|
78
93
|
# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
|
@@ -81,9 +96,10 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
81
96
|
ActiveStorage.verifier.generate(id, purpose: :blob_id)
|
82
97
|
end
|
83
98
|
|
84
|
-
# Returns the key pointing to the file on the service that's associated with this blob. The key is
|
85
|
-
#
|
86
|
-
# to be revealed directly to the user.
|
99
|
+
# Returns the key pointing to the file on the service that's associated with this blob. The key is the
|
100
|
+
# secure-token format from Rails in lower case. So it'll look like: xtapjjcjiudrlk3tmwyjgpuobabd.
|
101
|
+
# This key is not intended to be revealed directly to the user.
|
102
|
+
# Always refer to blobs using the signed_id or a verified form of the key.
|
87
103
|
def key
|
88
104
|
# We can't wait until the record is first saved to have a key for it
|
89
105
|
self[:key] ||= self.class.generate_unique_secure_token
|
@@ -121,7 +137,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
121
137
|
# with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
|
122
138
|
# Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
|
123
139
|
# it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
|
124
|
-
def service_url(expires_in:
|
140
|
+
def service_url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)
|
125
141
|
filename = ActiveStorage::Filename.wrap(filename || self.filename)
|
126
142
|
|
127
143
|
service.url key, expires_in: expires_in, filename: filename, content_type: content_type_for_service_url,
|
@@ -130,7 +146,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
130
146
|
|
131
147
|
# Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
|
132
148
|
# short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading.
|
133
|
-
def service_url_for_direct_upload(expires_in:
|
149
|
+
def service_url_for_direct_upload(expires_in: ActiveStorage.service_urls_expire_in)
|
134
150
|
service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum
|
135
151
|
end
|
136
152
|
|
@@ -146,16 +162,24 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
146
162
|
#
|
147
163
|
# Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the
|
148
164
|
# checksum does not match what the service receives, an exception will be raised. We also measure the size of the +io+
|
149
|
-
# and store that in +byte_size+ on the blob record.
|
165
|
+
# and store that in +byte_size+ on the blob record. The content type is automatically extracted from the +io+ unless
|
166
|
+
# you specify a +content_type+ and pass +identify+ as false.
|
150
167
|
#
|
151
168
|
# Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+
|
152
169
|
# and +create_after_upload!+.
|
153
|
-
def upload(io)
|
170
|
+
def upload(io, identify: true)
|
171
|
+
unfurl io, identify: identify
|
172
|
+
upload_without_unfurling io
|
173
|
+
end
|
174
|
+
|
175
|
+
def unfurl(io, identify: true) #:nodoc:
|
154
176
|
self.checksum = compute_checksum_in_chunks(io)
|
155
|
-
self.content_type = extract_content_type(io)
|
177
|
+
self.content_type = extract_content_type(io) if content_type.nil? || identify
|
156
178
|
self.byte_size = io.size
|
157
179
|
self.identified = true
|
180
|
+
end
|
158
181
|
|
182
|
+
def upload_without_unfurling(io) #:nodoc:
|
159
183
|
service.upload key, io, checksum: checksum, **service_metadata
|
160
184
|
end
|
161
185
|
|
@@ -165,9 +189,26 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
165
189
|
service.download key, &block
|
166
190
|
end
|
167
191
|
|
192
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
193
|
+
#
|
194
|
+
# The tempfile's name is prefixed with +ActiveStorage-+ and the blob's ID. Its extension matches that of the blob.
|
195
|
+
#
|
196
|
+
# By default, the tempfile is created in <tt>Dir.tmpdir</tt>. Pass +tempdir:+ to create it in a different directory:
|
197
|
+
#
|
198
|
+
# blob.open(tempdir: "/path/to/tmp") do |file|
|
199
|
+
# # ...
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# The tempfile is automatically closed and unlinked after the given block is executed.
|
203
|
+
#
|
204
|
+
# Raises ActiveStorage::IntegrityError if the downloaded data does not match the blob's checksum.
|
205
|
+
def open(tempdir: nil, &block)
|
206
|
+
ActiveStorage::Downloader.new(self, tempdir: tempdir).download_blob_to_tempfile(&block)
|
207
|
+
end
|
208
|
+
|
168
209
|
|
169
|
-
# Deletes the
|
170
|
-
# deleted as well or you will essentially have a dead reference. It's recommended to use
|
210
|
+
# Deletes the files on the service associated with the blob. This should only be done if the blob is going to be
|
211
|
+
# deleted as well or you will essentially have a dead reference. It's recommended to use #purge and #purge_later
|
171
212
|
# methods in most circumstances.
|
172
213
|
def delete
|
173
214
|
service.delete(key)
|
@@ -176,15 +217,15 @@ class ActiveStorage::Blob < ActiveRecord::Base
|
|
176
217
|
|
177
218
|
# Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
|
178
219
|
# blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may
|
179
|
-
# be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use
|
220
|
+
# be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use #purge_later instead.
|
180
221
|
def purge
|
181
222
|
destroy
|
182
223
|
delete
|
183
224
|
rescue ActiveRecord::InvalidForeignKey
|
184
225
|
end
|
185
226
|
|
186
|
-
# Enqueues an ActiveStorage::PurgeJob
|
187
|
-
#
|
227
|
+
# Enqueues an ActiveStorage::PurgeJob to call #purge. This is the recommended way to purge blobs from a transaction,
|
228
|
+
# an Active Record callback, or in any other real-time scenario.
|
188
229
|
def purge_later
|
189
230
|
ActiveStorage::PurgeJob.perform_later(self)
|
190
231
|
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
|
@@ -22,8 +22,8 @@
|
|
22
22
|
# Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
|
23
23
|
#
|
24
24
|
# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
|
25
|
-
# {
|
26
|
-
# and the other requires {
|
25
|
+
# {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
|
26
|
+
# and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
|
27
27
|
#
|
28
28
|
# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
|
29
29
|
# install and use third-party software, make sure you understand the licensing implications of doing so.
|
@@ -38,7 +38,7 @@ class ActiveStorage::Preview
|
|
38
38
|
|
39
39
|
# Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
|
40
40
|
#
|
41
|
-
# blob.preview(
|
41
|
+
# blob.preview(resize_to_fit: [100, 100]).processed.service_url
|
42
42
|
#
|
43
43
|
# Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
|
44
44
|
# image is stored with the blob, it is only generated once.
|
@@ -1,24 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "ostruct"
|
4
4
|
|
5
5
|
# Image blobs can have variants that are the result of a set of transformations applied to the original.
|
6
6
|
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
|
7
7
|
# original.
|
8
8
|
#
|
9
|
-
# Variants rely on {
|
10
|
-
# of the file, so you must add <tt>gem "
|
9
|
+
# Variants rely on {ImageProcessing}[https://github.com/janko-m/image_processing] gem for the actual transformations
|
10
|
+
# of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
|
11
|
+
# default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
|
12
|
+
# {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
|
13
|
+
# {libvips}[http://jcupitt.github.io/libvips/] processor operated by the {ruby-vips}[https://github.com/jcupitt/ruby-vips]
|
14
|
+
# gem).
|
11
15
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
+
# Rails.application.config.active_storage.variant_processor
|
17
|
+
# # => :mini_magick
|
18
|
+
#
|
19
|
+
# Rails.application.config.active_storage.variant_processor = :vips
|
20
|
+
# # => :vips
|
21
|
+
#
|
22
|
+
# Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
|
23
|
+
# you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
|
24
|
+
# in a template, for example. Delay the processing to an on-demand controller, like the one provided in
|
16
25
|
# ActiveStorage::RepresentationsController.
|
17
26
|
#
|
18
27
|
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
|
19
28
|
# by Active Storage like so:
|
20
29
|
#
|
21
|
-
# <%= image_tag Current.user.avatar.variant(
|
30
|
+
# <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
|
22
31
|
#
|
23
32
|
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
|
24
33
|
# can then produce on-demand.
|
@@ -27,19 +36,24 @@ require "active_storage/downloading"
|
|
27
36
|
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
|
28
37
|
# the transformations, upload the variant to the service, and return itself again. Example:
|
29
38
|
#
|
30
|
-
# avatar.variant(
|
39
|
+
# avatar.variant(resize_to_fit: [100, 100]).processed.service_url
|
31
40
|
#
|
32
41
|
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
|
33
42
|
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
|
34
43
|
#
|
35
|
-
#
|
36
|
-
#
|
44
|
+
# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
|
45
|
+
# ImageProcessing gem (such as +resize_to_fit+):
|
46
|
+
#
|
47
|
+
# avatar.variant(resize_to_fit: [800, 800], monochrome: true, rotate: "-90")
|
48
|
+
#
|
49
|
+
# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
|
37
50
|
#
|
38
|
-
#
|
51
|
+
# * {ImageProcessing::MiniMagick}[https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md#methods]
|
52
|
+
# * {ImageMagick reference}[https://www.imagemagick.org/script/mogrify.php]
|
53
|
+
# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
|
54
|
+
# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
|
39
55
|
class ActiveStorage::Variant
|
40
|
-
|
41
|
-
|
42
|
-
WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
|
56
|
+
WEB_IMAGE_CONTENT_TYPES = %w[ image/png image/jpeg image/jpg image/gif ]
|
43
57
|
|
44
58
|
attr_reader :blob, :variation
|
45
59
|
delegate :service, to: :blob
|
@@ -67,7 +81,7 @@ class ActiveStorage::Variant
|
|
67
81
|
# Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
|
68
82
|
# for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
|
69
83
|
# for its redirection.
|
70
|
-
def service_url(expires_in:
|
84
|
+
def service_url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
|
71
85
|
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
|
72
86
|
end
|
73
87
|
|
@@ -82,51 +96,36 @@ class ActiveStorage::Variant
|
|
82
96
|
end
|
83
97
|
|
84
98
|
def process
|
85
|
-
|
86
|
-
transform
|
87
|
-
format image
|
88
|
-
upload image
|
99
|
+
blob.open do |image|
|
100
|
+
transform(image) { |output| upload(output) }
|
89
101
|
end
|
90
102
|
end
|
91
103
|
|
92
|
-
|
93
|
-
|
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
|
104
|
+
def transform(image, &block)
|
105
|
+
variation.transform(image, format: format, &block)
|
99
106
|
end
|
100
107
|
|
101
|
-
def
|
102
|
-
|
108
|
+
def upload(file)
|
109
|
+
service.upload(key, file)
|
103
110
|
end
|
104
111
|
|
105
112
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
113
|
+
def specification
|
114
|
+
@specification ||=
|
115
|
+
if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
|
116
|
+
Specification.new \
|
117
|
+
filename: blob.filename,
|
118
|
+
content_type: blob.content_type,
|
119
|
+
format: nil
|
120
|
+
else
|
121
|
+
Specification.new \
|
122
|
+
filename: ActiveStorage::Filename.new("#{blob.filename.base}.png"),
|
123
|
+
content_type: "image/png",
|
124
|
+
format: "png"
|
125
|
+
end
|
119
126
|
end
|
120
127
|
|
121
|
-
|
122
|
-
variation.transform(image)
|
123
|
-
end
|
128
|
+
delegate :filename, :content_type, :format, to: :specification
|
124
129
|
|
125
|
-
|
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
|
130
|
+
class Specification < OpenStruct; end
|
132
131
|
end
|
@@ -6,23 +6,12 @@
|
|
6
6
|
# In case you do need to use this directly, it's instantiated using a hash of transformations where
|
7
7
|
# the key is the command and the value is the arguments. Example:
|
8
8
|
#
|
9
|
-
# ActiveStorage::Variation.new(
|
9
|
+
# ActiveStorage::Variation.new(resize_to_fit: [100, 100], monochrome: true, trim: true, rotate: "-90")
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# ActiveStorage::Variation.new(combine_options: {
|
14
|
-
# resize: "100x100^",
|
15
|
-
# gravity: "center",
|
16
|
-
# crop: "100x100+0+0",
|
17
|
-
# })
|
18
|
-
#
|
19
|
-
# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
|
11
|
+
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
|
20
12
|
class ActiveStorage::Variation
|
21
13
|
attr_reader :transformations
|
22
14
|
|
23
|
-
class UnsupportedImageProcessingMethod < StandardError; end
|
24
|
-
class UnsupportedImageProcessingArgument < StandardError; end
|
25
|
-
|
26
15
|
class << self
|
27
16
|
# Returns a Variation instance based on the given variator. If the variator is a Variation, it is
|
28
17
|
# returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise,
|
@@ -54,24 +43,13 @@ class ActiveStorage::Variation
|
|
54
43
|
@transformations = transformations
|
55
44
|
end
|
56
45
|
|
57
|
-
# Accepts
|
58
|
-
#
|
59
|
-
|
46
|
+
# Accepts a File object, performs the +transformations+ against it, and
|
47
|
+
# saves the transformed image into a temporary file. If +format+ is specified
|
48
|
+
# it will be the format of the result image, otherwise the result image
|
49
|
+
# retains the source format.
|
50
|
+
def transform(file, format: nil, &block)
|
60
51
|
ActiveSupport::Notifications.instrument("transform.active_storage") do
|
61
|
-
|
62
|
-
validate_transformation(name, argument_or_subtransformations)
|
63
|
-
image.mogrify do |command|
|
64
|
-
if name.to_s == "combine_options"
|
65
|
-
argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
|
66
|
-
validate_transformation(subtransformation_name, subtransformation_argument)
|
67
|
-
pass_transform_argument(command, subtransformation_name, subtransformation_argument)
|
68
|
-
end
|
69
|
-
else
|
70
|
-
validate_transformation(name, argument_or_subtransformations)
|
71
|
-
pass_transform_argument(command, name, argument_or_subtransformations)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
52
|
+
transformer.transform(file, format: format, &block)
|
75
53
|
end
|
76
54
|
end
|
77
55
|
|
@@ -81,69 +59,22 @@ class ActiveStorage::Variation
|
|
81
59
|
end
|
82
60
|
|
83
61
|
private
|
84
|
-
def
|
85
|
-
if
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
method_name = name.to_s.gsub("-","_")
|
98
|
-
|
99
|
-
unless ActiveStorage.supported_image_processing_methods.any? { |method| method_name == method }
|
100
|
-
raise UnsupportedImageProcessingMethod, <<~ERROR.squish
|
101
|
-
One or more of the provided transformation methods is not supported.
|
102
|
-
ERROR
|
103
|
-
end
|
104
|
-
|
105
|
-
if argument.present?
|
106
|
-
if argument.is_a?(String) || argument.is_a?(Symbol)
|
107
|
-
validate_arg_string(argument)
|
108
|
-
elsif argument.is_a?(Array)
|
109
|
-
validate_arg_array(argument)
|
110
|
-
elsif argument.is_a?(Hash)
|
111
|
-
validate_arg_hash(argument)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def validate_arg_string(argument)
|
117
|
-
if ActiveStorage.unsupported_image_processing_arguments.any? { |bad_arg| argument.to_s.downcase.include?(bad_arg) }; raise UnsupportedImageProcessingArgument end
|
118
|
-
end
|
119
|
-
|
120
|
-
def validate_arg_array(argument)
|
121
|
-
argument.each do |arg|
|
122
|
-
if arg.is_a?(Integer) || arg.is_a?(Float)
|
123
|
-
next
|
124
|
-
elsif arg.is_a?(String) || arg.is_a?(Symbol)
|
125
|
-
validate_arg_string(arg)
|
126
|
-
elsif arg.is_a?(Array)
|
127
|
-
validate_arg_array(arg)
|
128
|
-
elsif arg.is_a?(Hash)
|
129
|
-
validate_arg_hash(arg)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def validate_arg_hash(argument)
|
135
|
-
argument.each do |key, value|
|
136
|
-
validate_arg_string(key)
|
137
|
-
|
138
|
-
if value.is_a?(Integer) || value.is_a?(Float)
|
139
|
-
next
|
140
|
-
elsif value.is_a?(String) || value.is_a?(Symbol)
|
141
|
-
validate_arg_string(value)
|
142
|
-
elsif value.is_a?(Array)
|
143
|
-
validate_arg_array(value)
|
144
|
-
elsif value.is_a?(Hash)
|
145
|
-
validate_arg_hash(value)
|
62
|
+
def transformer
|
63
|
+
if ActiveStorage.variant_processor
|
64
|
+
begin
|
65
|
+
require "image_processing"
|
66
|
+
rescue LoadError
|
67
|
+
ActiveSupport::Deprecation.warn <<~WARNING
|
68
|
+
Generating image variants will require the image_processing gem in Rails 6.1.
|
69
|
+
Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
|
70
|
+
WARNING
|
71
|
+
|
72
|
+
ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
|
73
|
+
else
|
74
|
+
ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
|
146
75
|
end
|
76
|
+
else
|
77
|
+
ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
|
147
78
|
end
|
148
79
|
end
|
149
80
|
end
|
data/config/routes.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Rails.application.routes.draw do
|
4
|
-
|
5
|
-
|
6
|
-
direct :rails_blob do |blob, options|
|
7
|
-
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
|
8
|
-
end
|
9
|
-
|
10
|
-
resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
|
11
|
-
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
|
4
|
+
scope ActiveStorage.routes_prefix do
|
5
|
+
get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
|
12
6
|
|
7
|
+
get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
|
13
8
|
|
14
|
-
|
9
|
+
get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
|
10
|
+
put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
|
11
|
+
post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
|
12
|
+
end
|
15
13
|
|
16
14
|
direct :rails_representation do |representation, options|
|
17
15
|
signed_blob_id = representation.blob.signed_id
|
@@ -25,7 +23,10 @@ Rails.application.routes.draw do
|
|
25
23
|
resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
|
26
24
|
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
direct :rails_blob do |blob, options|
|
27
|
+
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
|
31
|
+
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
|
31
32
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
unless foreign_key_exists?(:active_storage_attachments, column: :blob_id)
|
4
|
+
add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/compact"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# Extracts the following from a video blob:
|
7
5
|
#
|
@@ -18,7 +16,7 @@ module ActiveStorage
|
|
18
16
|
#
|
19
17
|
# When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
|
20
18
|
#
|
21
|
-
# This analyzer requires the {
|
19
|
+
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
|
22
20
|
class Analyzer::VideoAnalyzer < Analyzer
|
23
21
|
def self.accept?(blob)
|
24
22
|
blob.video?
|
@@ -109,7 +107,7 @@ module ActiveStorage
|
|
109
107
|
JSON.parse(output.read)
|
110
108
|
end
|
111
109
|
rescue Errno::ENOENT
|
112
|
-
logger.info "Skipping video analysis because
|
110
|
+
logger.info "Skipping video analysis because FFmpeg isn't installed"
|
113
111
|
{}
|
114
112
|
end
|
115
113
|
|
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_storage/downloading"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# This is an abstract base class for analyzers, which extract metadata from blobs. See
|
7
5
|
# ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
|
8
6
|
class Analyzer
|
9
|
-
include Downloading
|
10
|
-
|
11
7
|
attr_reader :blob
|
12
8
|
|
13
9
|
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
@@ -26,8 +22,17 @@ module ActiveStorage
|
|
26
22
|
end
|
27
23
|
|
28
24
|
private
|
25
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
26
|
+
def download_blob_to_tempfile(&block) #:doc:
|
27
|
+
blob.open tempdir: tempdir, &block
|
28
|
+
end
|
29
|
+
|
29
30
|
def logger #:doc:
|
30
31
|
ActiveStorage.logger
|
31
32
|
end
|
33
|
+
|
34
|
+
def tempdir #:doc:
|
35
|
+
Dir.tmpdir
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|