activestorage 6.1.7 → 7.0.0.alpha1
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 +127 -282
- data/MIT-LICENSE +1 -1
- data/README.md +25 -11
- data/app/assets/javascripts/activestorage.esm.js +844 -0
- data/app/assets/javascripts/activestorage.js +257 -376
- data/app/controllers/active_storage/base_controller.rb +1 -10
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
- data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
- data/app/controllers/active_storage/representations/base_controller.rb +5 -1
- data/app/controllers/active_storage/representations/proxy_controller.rb +6 -4
- data/app/controllers/active_storage/representations/redirect_controller.rb +6 -4
- data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
- data/app/controllers/concerns/active_storage/set_current.rb +3 -3
- data/app/controllers/concerns/active_storage/streaming.rb +65 -0
- data/app/javascript/activestorage/ujs.js +1 -1
- data/app/models/active_storage/attachment.rb +35 -2
- data/app/models/active_storage/blob/representable.rb +7 -5
- data/app/models/active_storage/blob.rb +26 -27
- data/app/models/active_storage/current.rb +12 -2
- data/app/models/active_storage/preview.rb +6 -4
- data/app/models/active_storage/record.rb +1 -1
- data/app/models/active_storage/variant.rb +6 -9
- data/app/models/active_storage/variant_record.rb +2 -0
- data/app/models/active_storage/variant_with_record.rb +9 -5
- data/app/models/active_storage/variation.rb +3 -3
- data/config/routes.rb +10 -10
- data/db/migrate/20170806125915_create_active_storage_tables.rb +11 -2
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +0 -4
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +1 -3
- data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
- data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
- data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
- data/lib/active_storage/analyzer.rb +8 -4
- data/lib/active_storage/attached/changes/create_many.rb +7 -3
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_one.rb +1 -1
- data/lib/active_storage/attached/changes/detach_many.rb +18 -0
- data/lib/active_storage/attached/changes/detach_one.rb +24 -0
- data/lib/active_storage/attached/changes/purge_many.rb +27 -0
- data/lib/active_storage/attached/changes/purge_one.rb +27 -0
- data/lib/active_storage/attached/changes.rb +7 -1
- data/lib/active_storage/attached/many.rb +27 -15
- data/lib/active_storage/attached/model.rb +31 -5
- data/lib/active_storage/attached/one.rb +32 -27
- data/lib/active_storage/downloader.rb +2 -2
- data/lib/active_storage/engine.rb +28 -16
- data/lib/active_storage/fixture_set.rb +76 -0
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/previewer/video_previewer.rb +0 -2
- data/lib/active_storage/previewer.rb +4 -4
- data/lib/active_storage/reflection.rb +12 -2
- data/lib/active_storage/service/azure_storage_service.rb +1 -1
- data/lib/active_storage/service/configurator.rb +1 -1
- data/lib/active_storage/service/disk_service.rb +13 -18
- data/lib/active_storage/service/gcs_service.rb +91 -7
- data/lib/active_storage/service/mirror_service.rb +1 -1
- data/lib/active_storage/service/registry.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +4 -4
- data/lib/active_storage/service.rb +3 -3
- data/lib/active_storage/transformers/image_processing_transformer.rb +1 -66
- data/lib/active_storage/transformers/transformer.rb +1 -1
- data/lib/active_storage.rb +3 -292
- metadata +32 -24
- data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -2,18 +2,9 @@
|
|
2
2
|
|
3
3
|
# The base class for all Active Storage controllers.
|
4
4
|
class ActiveStorage::BaseController < ActionController::Base
|
5
|
-
include ActiveStorage::SetCurrent
|
5
|
+
include ActiveStorage::SetCurrent, ActiveStorage::Streaming
|
6
6
|
|
7
7
|
protect_from_forgery with: :exception
|
8
8
|
|
9
9
|
self.etag_with_template_digest = false
|
10
|
-
|
11
|
-
private
|
12
|
-
def stream(blob)
|
13
|
-
blob.download do |chunk|
|
14
|
-
response.stream.write chunk
|
15
|
-
end
|
16
|
-
ensure
|
17
|
-
response.stream.close
|
18
|
-
end
|
19
10
|
end
|
@@ -1,14 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Proxy files through application. This avoids having a redirect and makes files easier to cache.
|
4
|
+
#
|
5
|
+
# WARNING: All Active Storage controllers are publicly accessible by default. The
|
6
|
+
# generated URLs are hard to guess, but permanent by design. If your files
|
7
|
+
# require a higher level of protection consider implementing
|
8
|
+
# {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
|
4
9
|
class ActiveStorage::Blobs::ProxyController < ActiveStorage::BaseController
|
5
10
|
include ActiveStorage::SetBlob
|
6
|
-
include ActiveStorage::SetHeaders
|
7
11
|
|
8
12
|
def show
|
9
|
-
|
10
|
-
|
11
|
-
|
13
|
+
if request.headers["Range"].present?
|
14
|
+
send_blob_byte_range_data @blob, request.headers["Range"]
|
15
|
+
else
|
16
|
+
http_cache_forever public: true do
|
17
|
+
response.headers["Accept-Ranges"] = "bytes"
|
18
|
+
response.headers["Content-Length"] = @blob.byte_size.to_s
|
19
|
+
|
20
|
+
send_blob_stream @blob
|
21
|
+
end
|
12
22
|
end
|
13
23
|
end
|
14
24
|
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Take a signed permanent reference for a blob and turn it into an expiring service URL for download.
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
#
|
5
|
+
# WARNING: All Active Storage controllers are publicly accessible by default. The
|
6
|
+
# generated URLs are hard to guess, but permanent by design. If your files
|
7
|
+
# require a higher level of protection consider implementing
|
8
|
+
# {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
|
7
9
|
class ActiveStorage::Blobs::RedirectController < ActiveStorage::BaseController
|
8
10
|
include ActiveStorage::SetBlob
|
9
11
|
|
10
12
|
def show
|
11
13
|
expires_in ActiveStorage.service_urls_expire_in
|
12
|
-
redirect_to @blob.url(disposition: params[:disposition])
|
14
|
+
redirect_to @blob.url(disposition: params[:disposition]), allow_other_host: true
|
13
15
|
end
|
14
16
|
end
|
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class ActiveStorage::Representations::BaseController < ActiveStorage::BaseController
|
3
|
+
class ActiveStorage::Representations::BaseController < ActiveStorage::BaseController # :nodoc:
|
4
4
|
include ActiveStorage::SetBlob
|
5
5
|
|
6
6
|
before_action :set_representation
|
7
7
|
|
8
8
|
private
|
9
|
+
def blob_scope
|
10
|
+
ActiveStorage::Blob.scope_for_strict_loading
|
11
|
+
end
|
12
|
+
|
9
13
|
def set_representation
|
10
14
|
@representation = @blob.representation(params[:variation_key]).processed
|
11
15
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Proxy files through application. This avoids having a redirect and makes files easier to cache.
|
4
|
+
#
|
5
|
+
# WARNING: All Active Storage controllers are publicly accessible by default. The
|
6
|
+
# generated URLs are hard to guess, but permanent by design. If your files
|
7
|
+
# require a higher level of protection consider implementing
|
8
|
+
# {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
|
4
9
|
class ActiveStorage::Representations::ProxyController < ActiveStorage::Representations::BaseController
|
5
|
-
include ActiveStorage::SetHeaders
|
6
|
-
|
7
10
|
def show
|
8
11
|
http_cache_forever public: true do
|
9
|
-
|
10
|
-
stream @representation
|
12
|
+
send_blob_stream @representation.image
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download.
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
#
|
5
|
+
# WARNING: All Active Storage controllers are publicly accessible by default. The
|
6
|
+
# generated URLs are hard to guess, but permanent by design. If your files
|
7
|
+
# require a higher level of protection consider implementing
|
8
|
+
# {Authenticated Controllers}[https://edgeguides.rubyonrails.org/active_storage_overview.html#authenticated-controllers].
|
7
9
|
class ActiveStorage::Representations::RedirectController < ActiveStorage::Representations::BaseController
|
8
10
|
def show
|
9
11
|
expires_in ActiveStorage.service_urls_expire_in
|
10
|
-
redirect_to @representation.url(disposition: params[:disposition])
|
12
|
+
redirect_to @representation.url(disposition: params[:disposition]), allow_other_host: true
|
11
13
|
end
|
12
14
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActiveStorage::SetBlob
|
3
|
+
module ActiveStorage::SetBlob # :nodoc:
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
@@ -9,8 +9,12 @@ module ActiveStorage::SetBlob #:nodoc:
|
|
9
9
|
|
10
10
|
private
|
11
11
|
def set_blob
|
12
|
-
@blob =
|
12
|
+
@blob = blob_scope.find_signed!(params[:signed_blob_id] || params[:signed_id])
|
13
13
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
14
14
|
head :not_found
|
15
15
|
end
|
16
|
+
|
17
|
+
def blob_scope
|
18
|
+
ActiveStorage::Blob
|
19
|
+
end
|
16
20
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Sets the <tt>ActiveStorage::Current.
|
3
|
+
# Sets the <tt>ActiveStorage::Current.url_options</tt> attribute, which the disk service uses to generate URLs.
|
4
4
|
# Include this concern in custom controllers that call ActiveStorage::Blob#url,
|
5
5
|
# ActiveStorage::Variant#url, or ActiveStorage::Preview#url so the disk service can
|
6
|
-
# generate URLs using the same host, protocol, and
|
6
|
+
# generate URLs using the same host, protocol, and port as the current request.
|
7
7
|
module ActiveStorage::SetCurrent
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
11
|
before_action do
|
12
|
-
ActiveStorage::Current.
|
12
|
+
ActiveStorage::Current.url_options = { protocol: request.protocol, host: request.host, port: request.port }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module ActiveStorage::Streaming
|
6
|
+
DEFAULT_BLOB_STREAMING_DISPOSITION = "inline"
|
7
|
+
|
8
|
+
include ActionController::DataStreaming
|
9
|
+
include ActionController::Live
|
10
|
+
|
11
|
+
private
|
12
|
+
# Stream the blob in byte ranges specified through the header
|
13
|
+
def send_blob_byte_range_data(blob, range_header, disposition: nil)
|
14
|
+
ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)
|
15
|
+
|
16
|
+
return head(:range_not_satisfiable) if ranges.blank? || ranges.all?(&:blank?)
|
17
|
+
|
18
|
+
if ranges.length == 1
|
19
|
+
range = ranges.first
|
20
|
+
content_type = blob.content_type_for_serving
|
21
|
+
data = blob.download_chunk(range)
|
22
|
+
|
23
|
+
response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{blob.byte_size}"
|
24
|
+
else
|
25
|
+
boundary = SecureRandom.hex
|
26
|
+
content_type = "multipart/byteranges; boundary=#{boundary}"
|
27
|
+
data = +""
|
28
|
+
|
29
|
+
ranges.compact.each do |range|
|
30
|
+
chunk = blob.download_chunk(range)
|
31
|
+
|
32
|
+
data << "\r\n--#{boundary}\r\n"
|
33
|
+
data << "Content-Type: #{blob.content_type_for_serving}\r\n"
|
34
|
+
data << "Content-Range: bytes #{range.begin}-#{range.end}/#{blob.byte_size}\r\n\r\n"
|
35
|
+
data << chunk
|
36
|
+
end
|
37
|
+
|
38
|
+
data << "\r\n--#{boundary}--\r\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
response.headers["Accept-Ranges"] = "bytes"
|
42
|
+
response.headers["Content-Length"] = data.length.to_s
|
43
|
+
|
44
|
+
send_data(
|
45
|
+
data,
|
46
|
+
disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
|
47
|
+
filename: blob.filename.sanitized,
|
48
|
+
status: :partial_content,
|
49
|
+
type: content_type
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Stream the blob from storage directly to the response. The disposition can be controlled by setting +disposition+.
|
54
|
+
# The content type and filename is set directly from the +blob+.
|
55
|
+
def send_blob_stream(blob, disposition: nil) # :doc:
|
56
|
+
send_stream(
|
57
|
+
filename: blob.filename.sanitized,
|
58
|
+
disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
|
59
|
+
type: blob.content_type_for_serving) do |stream|
|
60
|
+
blob.download do |chunk|
|
61
|
+
stream.write chunk
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -9,7 +9,7 @@ export function start() {
|
|
9
9
|
if (!started) {
|
10
10
|
started = true
|
11
11
|
document.addEventListener("click", didClick, true)
|
12
|
-
document.addEventListener("submit", didSubmitForm)
|
12
|
+
document.addEventListener("submit", didSubmitForm, true)
|
13
13
|
document.addEventListener("ajax:before", didSubmitRemoteElement)
|
14
14
|
}
|
15
15
|
}
|
@@ -7,6 +7,14 @@ require "active_support/core_ext/module/delegation"
|
|
7
7
|
# on the attachments table prevents blobs from being purged if they’re still attached to any records.
|
8
8
|
#
|
9
9
|
# Attachments also have access to all methods from {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob].
|
10
|
+
#
|
11
|
+
# If you wish to preload attachments or blobs, you can use these scopes:
|
12
|
+
#
|
13
|
+
# # preloads attachments, their corresponding blobs, and variant records (if using `ActiveStorage.track_variants`)
|
14
|
+
# User.all.with_attached_avatars
|
15
|
+
#
|
16
|
+
# # preloads blobs and variant records (if using `ActiveStorage.track_variants`)
|
17
|
+
# User.first.avatars.with_all_variant_records
|
10
18
|
class ActiveStorage::Attachment < ActiveStorage::Record
|
11
19
|
self.table_name = "active_storage_attachments"
|
12
20
|
|
@@ -19,11 +27,13 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
19
27
|
after_create_commit :mirror_blob_later, :analyze_blob_later
|
20
28
|
after_destroy_commit :purge_dependent_blob_later
|
21
29
|
|
30
|
+
scope :with_all_variant_records, -> { includes(blob: :variant_records) }
|
31
|
+
|
22
32
|
# Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
|
23
33
|
def purge
|
24
34
|
transaction do
|
25
35
|
delete
|
26
|
-
record&.
|
36
|
+
record.touch if record&.persisted?
|
27
37
|
end
|
28
38
|
blob&.purge
|
29
39
|
end
|
@@ -32,11 +42,30 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
32
42
|
def purge_later
|
33
43
|
transaction do
|
34
44
|
delete
|
35
|
-
record&.
|
45
|
+
record.touch if record&.persisted?
|
36
46
|
end
|
37
47
|
blob&.purge_later
|
38
48
|
end
|
39
49
|
|
50
|
+
# Returns an ActiveStorage::Variant or ActiveStorage::VariantWithRecord
|
51
|
+
# instance for the attachment with the set of +transformations+ provided.
|
52
|
+
# See ActiveStorage::Blob::Representable#variant for more information.
|
53
|
+
#
|
54
|
+
# Raises an +ArgumentError+ if +transformations+ is a +Symbol+ which is an
|
55
|
+
# unknown pre-defined variant of the attachment.
|
56
|
+
def variant(transformations)
|
57
|
+
case transformations
|
58
|
+
when Symbol
|
59
|
+
variant_name = transformations
|
60
|
+
transformations = variants.fetch(variant_name) do
|
61
|
+
record_model_name = record.to_model.model_name.name
|
62
|
+
raise ArgumentError, "Cannot find variant :#{variant_name} for #{record_model_name}##{name}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
blob.variant(transformations)
|
67
|
+
end
|
68
|
+
|
40
69
|
private
|
41
70
|
def analyze_blob_later
|
42
71
|
blob.analyze_later unless blob.analyzed?
|
@@ -53,6 +82,10 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
53
82
|
def dependent
|
54
83
|
record.attachment_reflections[name]&.options&.fetch(:dependent, nil)
|
55
84
|
end
|
85
|
+
|
86
|
+
def variants
|
87
|
+
record.attachment_reflections[name]&.variants
|
88
|
+
end
|
56
89
|
end
|
57
90
|
|
58
91
|
ActiveSupport.run_load_hooks :active_storage_attachment, ActiveStorage::Attachment
|
@@ -12,8 +12,8 @@ module ActiveStorage::Blob::Representable
|
|
12
12
|
has_one_attached :preview_image
|
13
13
|
end
|
14
14
|
|
15
|
-
# Returns an ActiveStorage::Variant instance with the set of +transformations+ provided.
|
16
|
-
# files, and it allows any image to be transformed for size, colors, and the like. Example:
|
15
|
+
# Returns an ActiveStorage::Variant or ActiveStorage::VariantWithRecord instance with the set of +transformations+ provided.
|
16
|
+
# This is only relevant for image files, and it allows any image to be transformed for size, colors, and the like. Example:
|
17
17
|
#
|
18
18
|
# avatar.variant(resize_to_limit: [100, 100]).processed.url
|
19
19
|
#
|
@@ -28,8 +28,9 @@ module ActiveStorage::Blob::Representable
|
|
28
28
|
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
|
29
29
|
# can then produce on-demand.
|
30
30
|
#
|
31
|
-
# Raises ActiveStorage::InvariableError if
|
32
|
-
# variable, call
|
31
|
+
# Raises ActiveStorage::InvariableError if the variant processor cannot
|
32
|
+
# transform the blob. To determine whether a blob is variable, call
|
33
|
+
# ActiveStorage::Blob#variable?.
|
33
34
|
def variant(transformations)
|
34
35
|
if variable?
|
35
36
|
variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
|
@@ -38,7 +39,8 @@ module ActiveStorage::Blob::Representable
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
|
-
# Returns true if
|
42
|
+
# Returns true if the variant processor can transform the blob (its content
|
43
|
+
# type is in +ActiveStorage.variable_content_types+).
|
42
44
|
def variable?
|
43
45
|
ActiveStorage.variable_content_types.include?(content_type)
|
44
46
|
end
|
@@ -86,21 +86,13 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
86
86
|
super(id, purpose: purpose)
|
87
87
|
end
|
88
88
|
|
89
|
-
def
|
90
|
-
new(filename: filename, content_type: content_type, metadata: metadata, service_name: service_name).tap do |blob|
|
91
|
-
blob.upload(io, identify: identify)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
deprecate :build_after_upload
|
96
|
-
|
97
|
-
def build_after_unfurling(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil) #:nodoc:
|
89
|
+
def build_after_unfurling(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil) # :nodoc:
|
98
90
|
new(key: key, filename: filename, content_type: content_type, metadata: metadata, service_name: service_name).tap do |blob|
|
99
91
|
blob.unfurl(io, identify: identify)
|
100
92
|
end
|
101
93
|
end
|
102
94
|
|
103
|
-
def create_after_unfurling!(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil)
|
95
|
+
def create_after_unfurling!(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil) # :nodoc:
|
104
96
|
build_after_unfurling(key: key, io: io, filename: filename, content_type: content_type, metadata: metadata, service_name: service_name, identify: identify).tap(&:save!)
|
105
97
|
end
|
106
98
|
|
@@ -115,9 +107,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
115
107
|
end
|
116
108
|
end
|
117
109
|
|
118
|
-
alias_method :create_after_upload!, :create_and_upload!
|
119
|
-
deprecate create_after_upload!: :create_and_upload!
|
120
|
-
|
121
110
|
# Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is
|
122
111
|
# no file yet. It's intended to be used together with a client-side upload, which will first create the blob
|
123
112
|
# in order to produce the signed URL for uploading. This signed URL points to the key generated by the blob.
|
@@ -137,7 +126,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
137
126
|
end
|
138
127
|
|
139
128
|
# Customize signed ID purposes for backwards compatibility.
|
140
|
-
def combine_signed_id_purposes(purpose)
|
129
|
+
def combine_signed_id_purposes(purpose) # :nodoc:
|
141
130
|
purpose.to_s
|
142
131
|
end
|
143
132
|
|
@@ -145,14 +134,22 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
145
134
|
#
|
146
135
|
# We override the reader (.signed_id_verifier) instead of just calling the writer (.signed_id_verifier=)
|
147
136
|
# to guard against the case where ActiveStorage.verifier isn't yet initialized at load time.
|
148
|
-
def signed_id_verifier
|
137
|
+
def signed_id_verifier # :nodoc:
|
149
138
|
@signed_id_verifier ||= ActiveStorage.verifier
|
150
139
|
end
|
140
|
+
|
141
|
+
def scope_for_strict_loading # :nodoc:
|
142
|
+
if strict_loading_by_default? && ActiveStorage.track_variants
|
143
|
+
includes(variant_records: { image_attachment: :blob }, preview_image_attachment: :blob)
|
144
|
+
else
|
145
|
+
all
|
146
|
+
end
|
147
|
+
end
|
151
148
|
end
|
152
149
|
|
153
150
|
# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
|
154
|
-
def signed_id
|
155
|
-
super
|
151
|
+
def signed_id(purpose: :blob_id, expires_in: nil)
|
152
|
+
super
|
156
153
|
end
|
157
154
|
|
158
155
|
# Returns the key pointing to the file on the service that's associated with this blob. The key is the
|
@@ -200,9 +197,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
200
197
|
content_type: content_type_for_serving, disposition: forced_disposition_for_serving || disposition, **options
|
201
198
|
end
|
202
199
|
|
203
|
-
alias_method :service_url, :url
|
204
|
-
deprecate service_url: :url
|
205
|
-
|
206
200
|
# Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
|
207
201
|
# short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading.
|
208
202
|
def service_url_for_direct_upload(expires_in: ActiveStorage.service_urls_expire_in)
|
@@ -214,11 +208,11 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
214
208
|
service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum
|
215
209
|
end
|
216
210
|
|
217
|
-
def content_type_for_serving
|
211
|
+
def content_type_for_serving # :nodoc:
|
218
212
|
forcibly_serve_as_binary? ? ActiveStorage.binary_content_type : content_type
|
219
213
|
end
|
220
214
|
|
221
|
-
def forced_disposition_for_serving
|
215
|
+
def forced_disposition_for_serving # :nodoc:
|
222
216
|
if forcibly_serve_as_binary? || !allowed_inline?
|
223
217
|
:attachment
|
224
218
|
end
|
@@ -242,14 +236,14 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
242
236
|
upload_without_unfurling io
|
243
237
|
end
|
244
238
|
|
245
|
-
def unfurl(io, identify: true)
|
239
|
+
def unfurl(io, identify: true) # :nodoc:
|
246
240
|
self.checksum = compute_checksum_in_chunks(io)
|
247
241
|
self.content_type = extract_content_type(io) if content_type.nil? || identify
|
248
242
|
self.byte_size = io.size
|
249
243
|
self.identified = true
|
250
244
|
end
|
251
245
|
|
252
|
-
def upload_without_unfurling(io)
|
246
|
+
def upload_without_unfurling(io) # :nodoc:
|
253
247
|
service.upload key, io, checksum: checksum, **service_metadata
|
254
248
|
end
|
255
249
|
|
@@ -259,6 +253,11 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
259
253
|
service.download key, &block
|
260
254
|
end
|
261
255
|
|
256
|
+
# Downloads a part of the file associated with this blob.
|
257
|
+
def download_chunk(range)
|
258
|
+
service.download_chunk key, range
|
259
|
+
end
|
260
|
+
|
262
261
|
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
263
262
|
#
|
264
263
|
# The tempfile's name is prefixed with +ActiveStorage-+ and the blob's ID. Its extension matches that of the blob.
|
@@ -277,7 +276,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
277
276
|
name: [ "ActiveStorage-#{id}-", filename.extension_with_delimiter ], tmpdir: tmpdir, &block
|
278
277
|
end
|
279
278
|
|
280
|
-
def mirror_later
|
279
|
+
def mirror_later # :nodoc:
|
281
280
|
ActiveStorage::MirrorJob.perform_later(key, checksum: checksum) if service.respond_to?(:mirror)
|
282
281
|
end
|
283
282
|
|
@@ -294,7 +293,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
294
293
|
# be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use #purge_later instead.
|
295
294
|
def purge
|
296
295
|
destroy
|
297
|
-
delete
|
296
|
+
delete if previously_persisted?
|
298
297
|
rescue ActiveRecord::InvalidForeignKey
|
299
298
|
end
|
300
299
|
|
@@ -311,7 +310,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
311
310
|
|
312
311
|
private
|
313
312
|
def compute_checksum_in_chunks(io)
|
314
|
-
Digest::MD5.new.tap do |checksum|
|
313
|
+
OpenSSL::Digest::MD5.new.tap do |checksum|
|
315
314
|
while chunk = io.read(5.megabytes)
|
316
315
|
checksum << chunk
|
317
316
|
end
|
@@ -1,5 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class ActiveStorage::Current < ActiveSupport::CurrentAttributes
|
4
|
-
attribute :
|
3
|
+
class ActiveStorage::Current < ActiveSupport::CurrentAttributes # :nodoc:
|
4
|
+
attribute :url_options
|
5
|
+
|
6
|
+
def host=(host)
|
7
|
+
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host= is deprecated, instead use ActiveStorage::Current.url_options=")
|
8
|
+
self.url_options = { host: host }
|
9
|
+
end
|
10
|
+
|
11
|
+
def host
|
12
|
+
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host is deprecated, instead use ActiveStorage::Current.url_options")
|
13
|
+
self.url_options&.dig(:host)
|
14
|
+
end
|
5
15
|
end
|
@@ -66,9 +66,6 @@ class ActiveStorage::Preview
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
alias_method :service_url, :url
|
70
|
-
deprecate service_url: :url
|
71
|
-
|
72
69
|
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
73
70
|
def key
|
74
71
|
if processed?
|
@@ -78,6 +75,11 @@ class ActiveStorage::Preview
|
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
78
|
+
# Downloads the file associated with this preview's variant. If no block is
|
79
|
+
# given, the entire file is read into memory and returned. That'll use a lot
|
80
|
+
# of RAM for very large files. If a block is given, then the download is
|
81
|
+
# streamed and yielded in chunks. Raises ActiveStorage::Preview::UnprocessedError
|
82
|
+
# if the preview has not been processed yet.
|
81
83
|
def download(&block)
|
82
84
|
if processed?
|
83
85
|
variant.download(&block)
|
@@ -93,7 +95,7 @@ class ActiveStorage::Preview
|
|
93
95
|
|
94
96
|
def process
|
95
97
|
previewer.preview(service_name: blob.service_name) do |attachable|
|
96
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
98
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
|
97
99
|
image.attach(attachable)
|
98
100
|
end
|
99
101
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
|
5
5
|
# original.
|
6
6
|
#
|
7
|
-
# Variants rely on {ImageProcessing}[https://github.com/janko/image_processing] gem for the actual transformations
|
7
|
+
# Variants rely on {ImageProcessing}[https://github.com/janko-m/image_processing] gem for the actual transformations
|
8
8
|
# of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
|
9
9
|
# default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
|
10
10
|
# {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
|
@@ -42,13 +42,13 @@
|
|
42
42
|
# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
|
43
43
|
# ImageProcessing gem (such as +resize_to_limit+):
|
44
44
|
#
|
45
|
-
# avatar.variant(resize_to_limit: [800, 800],
|
45
|
+
# avatar.variant(resize_to_limit: [800, 800], colourspace: "b-w", rotate: "-90")
|
46
46
|
#
|
47
47
|
# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
|
48
48
|
#
|
49
|
-
# * {ImageProcessing::MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md#methods]
|
49
|
+
# * {ImageProcessing::MiniMagick}[https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md#methods]
|
50
50
|
# * {ImageMagick reference}[https://www.imagemagick.org/script/mogrify.php]
|
51
|
-
# * {ImageProcessing::Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md#methods]
|
51
|
+
# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
|
52
52
|
# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
|
53
53
|
class ActiveStorage::Variant
|
54
54
|
attr_reader :blob, :variation
|
@@ -67,7 +67,7 @@ class ActiveStorage::Variant
|
|
67
67
|
|
68
68
|
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
69
69
|
def key
|
70
|
-
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
|
70
|
+
"variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
|
71
71
|
end
|
72
72
|
|
73
73
|
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
|
@@ -79,9 +79,6 @@ class ActiveStorage::Variant
|
|
79
79
|
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
|
80
80
|
end
|
81
81
|
|
82
|
-
alias_method :service_url, :url
|
83
|
-
deprecate service_url: :url
|
84
|
-
|
85
82
|
# Downloads the file associated with this variant. If no block is given, the entire file is read into memory and returned.
|
86
83
|
# 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
84
|
def download(&block)
|
@@ -94,7 +91,7 @@ class ActiveStorage::Variant
|
|
94
91
|
|
95
92
|
alias_method :content_type_for_serving, :content_type
|
96
93
|
|
97
|
-
def forced_disposition_for_serving
|
94
|
+
def forced_disposition_for_serving # :nodoc:
|
98
95
|
nil
|
99
96
|
end
|
100
97
|
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Like an ActiveStorage::Variant, but keeps detail about the variant in the database as an
|
4
|
+
# ActiveStorage::VariantRecord. This is only used if `ActiveStorage.track_variants` is enabled.
|
3
5
|
class ActiveStorage::VariantWithRecord
|
4
6
|
attr_reader :blob, :variation
|
7
|
+
delegate :service, to: :blob
|
5
8
|
|
6
9
|
def initialize(blob, variation)
|
7
10
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
|
@@ -26,9 +29,6 @@ class ActiveStorage::VariantWithRecord
|
|
26
29
|
|
27
30
|
delegate :key, :url, :download, to: :image, allow_nil: true
|
28
31
|
|
29
|
-
alias_method :service_url, :url
|
30
|
-
deprecate service_url: :url
|
31
|
-
|
32
32
|
private
|
33
33
|
def transform_blob
|
34
34
|
blob.open do |input|
|
@@ -41,7 +41,7 @@ class ActiveStorage::VariantWithRecord
|
|
41
41
|
|
42
42
|
def create_or_find_record(image:)
|
43
43
|
@record =
|
44
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
44
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
|
45
45
|
blob.variant_records.create_or_find_by!(variation_digest: variation.digest) do |record|
|
46
46
|
record.image.attach(image)
|
47
47
|
end
|
@@ -49,6 +49,10 @@ class ActiveStorage::VariantWithRecord
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def record
|
52
|
-
@record ||= blob.variant_records.
|
52
|
+
@record ||= if blob.variant_records.loaded?
|
53
|
+
blob.variant_records.find { |v| v.variation_digest == variation.digest }
|
54
|
+
else
|
55
|
+
blob.variant_records.find_by(variation_digest: variation.digest)
|
56
|
+
end
|
53
57
|
end
|
54
58
|
end
|