activestorage 6.1.4.1 → 7.0.0.rc2
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 +144 -204
- data/MIT-LICENSE +1 -1
- data/README.md +25 -11
- data/app/assets/javascripts/activestorage.esm.js +856 -0
- data/app/assets/javascripts/activestorage.js +270 -377
- 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/direct_uploads_controller.rb +7 -1
- data/app/controllers/active_storage/disk_controller.rb +1 -0
- 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/blob_record.js +10 -3
- data/app/javascript/activestorage/direct_upload.js +4 -2
- data/app/javascript/activestorage/direct_upload_controller.js +9 -1
- data/app/javascript/activestorage/ujs.js +1 -1
- data/app/models/active_storage/attachment.rb +36 -3
- data/app/models/active_storage/blob/representable.rb +7 -5
- data/app/models/active_storage/blob.rb +92 -36
- 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 +3 -6
- 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 +2 -2
- data/config/routes.rb +10 -10
- data/db/migrate/20170806125915_create_active_storage_tables.rb +32 -11
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
- data/db/update_migrate/20211119233751_remove_not_null_on_active_storage_blobs_checksum.rb +5 -0
- 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/direct_upload_token.rb +59 -0
- data/lib/active_storage/downloader.rb +4 -4
- data/lib/active_storage/engine.rb +30 -1
- data/lib/active_storage/errors.rb +3 -0
- data/lib/active_storage/fixture_set.rb +76 -0
- data/lib/active_storage/gem_version.rb +4 -4
- 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 +28 -6
- data/lib/active_storage/service/configurator.rb +1 -1
- data/lib/active_storage/service/disk_service.rb +24 -19
- data/lib/active_storage/service/gcs_service.rb +109 -11
- data/lib/active_storage/service/mirror_service.rb +2 -2
- data/lib/active_storage/service/registry.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +37 -15
- data/lib/active_storage/service.rb +13 -5
- data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
- data/lib/active_storage/transformers/transformer.rb +1 -1
- data/lib/active_storage.rb +6 -1
- metadata +31 -19
- 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
|
@@ -4,8 +4,10 @@
|
|
4
4
|
# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference
|
5
5
|
# the blob that was created up front.
|
6
6
|
class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
|
7
|
+
include ActiveStorage::DirectUploadToken
|
8
|
+
|
7
9
|
def create
|
8
|
-
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
|
10
|
+
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args.merge(service_name: verified_service_name))
|
9
11
|
render json: direct_upload_json(blob)
|
10
12
|
end
|
11
13
|
|
@@ -14,6 +16,10 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
|
|
14
16
|
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
|
15
17
|
end
|
16
18
|
|
19
|
+
def verified_service_name
|
20
|
+
ActiveStorage::DirectUploadToken.verify_direct_upload_token(params[:direct_upload_token], params[:attachment_name], session)
|
21
|
+
end
|
22
|
+
|
17
23
|
def direct_upload_json(blob)
|
18
24
|
blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
|
19
25
|
url: blob.service_url_for_direct_upload,
|
@@ -23,6 +23,7 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
|
|
23
23
|
if token = decode_verified_token
|
24
24
|
if acceptable_content?(token)
|
25
25
|
named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
|
26
|
+
head :no_content
|
26
27
|
else
|
27
28
|
head :unprocessable_entity
|
28
29
|
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
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import { getMetaValue } from "./helpers"
|
2
2
|
|
3
3
|
export class BlobRecord {
|
4
|
-
constructor(file, checksum, url) {
|
4
|
+
constructor(file, checksum, url, directUploadToken, attachmentName) {
|
5
5
|
this.file = file
|
6
6
|
|
7
7
|
this.attributes = {
|
8
8
|
filename: file.name,
|
9
9
|
content_type: file.type || "application/octet-stream",
|
10
10
|
byte_size: file.size,
|
11
|
-
checksum: checksum
|
11
|
+
checksum: checksum,
|
12
12
|
}
|
13
13
|
|
14
|
+
this.directUploadToken = directUploadToken
|
15
|
+
this.attachmentName = attachmentName
|
16
|
+
|
14
17
|
this.xhr = new XMLHttpRequest
|
15
18
|
this.xhr.open("POST", url, true)
|
16
19
|
this.xhr.responseType = "json"
|
@@ -43,7 +46,11 @@ export class BlobRecord {
|
|
43
46
|
|
44
47
|
create(callback) {
|
45
48
|
this.callback = callback
|
46
|
-
this.xhr.send(JSON.stringify({
|
49
|
+
this.xhr.send(JSON.stringify({
|
50
|
+
blob: this.attributes,
|
51
|
+
direct_upload_token: this.directUploadToken,
|
52
|
+
attachment_name: this.attachmentName
|
53
|
+
}))
|
47
54
|
}
|
48
55
|
|
49
56
|
requestDidLoad(event) {
|
@@ -5,10 +5,12 @@ import { BlobUpload } from "./blob_upload"
|
|
5
5
|
let id = 0
|
6
6
|
|
7
7
|
export class DirectUpload {
|
8
|
-
constructor(file, url, delegate) {
|
8
|
+
constructor(file, url, serviceName, attachmentName, delegate) {
|
9
9
|
this.id = ++id
|
10
10
|
this.file = file
|
11
11
|
this.url = url
|
12
|
+
this.serviceName = serviceName
|
13
|
+
this.attachmentName = attachmentName
|
12
14
|
this.delegate = delegate
|
13
15
|
}
|
14
16
|
|
@@ -19,7 +21,7 @@ export class DirectUpload {
|
|
19
21
|
return
|
20
22
|
}
|
21
23
|
|
22
|
-
const blob = new BlobRecord(this.file, checksum, this.url)
|
24
|
+
const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName)
|
23
25
|
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
|
24
26
|
|
25
27
|
blob.create(error => {
|
@@ -5,7 +5,7 @@ export class DirectUploadController {
|
|
5
5
|
constructor(input, file) {
|
6
6
|
this.input = input
|
7
7
|
this.file = file
|
8
|
-
this.directUpload = new DirectUpload(this.file, this.url, this)
|
8
|
+
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this)
|
9
9
|
this.dispatch("initialize")
|
10
10
|
}
|
11
11
|
|
@@ -41,6 +41,14 @@ export class DirectUploadController {
|
|
41
41
|
return this.input.getAttribute("data-direct-upload-url")
|
42
42
|
}
|
43
43
|
|
44
|
+
get directUploadToken() {
|
45
|
+
return this.input.getAttribute("data-direct-upload-token")
|
46
|
+
}
|
47
|
+
|
48
|
+
get attachmentName() {
|
49
|
+
return this.input.getAttribute("data-direct-upload-attachment-name")
|
50
|
+
}
|
51
|
+
|
44
52
|
dispatch(name, detail = {}) {
|
45
53
|
detail.file = this.file
|
46
54
|
detail.id = this.directUpload.id
|
@@ -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?
|
@@ -51,7 +80,11 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
51
80
|
end
|
52
81
|
|
53
82
|
def dependent
|
54
|
-
record.attachment_reflections[name]&.options
|
83
|
+
record.attachment_reflections[name]&.options&.fetch(:dependent, nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
def variants
|
87
|
+
record.attachment_reflections[name]&.variants
|
55
88
|
end
|
56
89
|
end
|
57
90
|
|
@@ -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
|