activestorage 7.0.8.5 → 7.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +135 -348
- data/MIT-LICENSE +1 -1
- data/README.md +4 -4
- data/app/assets/javascripts/activestorage.esm.js +8 -4
- data/app/assets/javascripts/activestorage.js +9 -3
- data/app/controllers/active_storage/disk_controller.rb +4 -2
- data/app/controllers/concerns/active_storage/file_server.rb +4 -1
- data/app/javascript/activestorage/blob_record.js +4 -1
- data/app/javascript/activestorage/direct_upload.js +3 -2
- data/app/javascript/activestorage/index.js +3 -1
- data/app/jobs/active_storage/transform_job.rb +12 -0
- data/app/models/active_storage/attachment.rb +87 -13
- data/app/models/active_storage/blob/analyzable.rb +4 -3
- data/app/models/active_storage/blob/identifiable.rb +1 -0
- data/app/models/active_storage/blob/representable.rb +7 -3
- data/app/models/active_storage/blob.rb +25 -45
- data/app/models/active_storage/current.rb +0 -10
- data/app/models/active_storage/filename.rb +2 -0
- data/app/models/active_storage/named_variant.rb +21 -0
- data/app/models/active_storage/preview.rb +5 -3
- data/app/models/active_storage/variant.rb +8 -7
- data/app/models/active_storage/variant_with_record.rb +19 -7
- data/app/models/active_storage/variation.rb +5 -3
- data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
- data/lib/active_storage/analyzer/audio_analyzer.rb +16 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +3 -1
- data/lib/active_storage/analyzer.rb +2 -0
- data/lib/active_storage/attached/changes/create_many.rb +8 -3
- data/lib/active_storage/attached/changes/create_one.rb +14 -2
- data/lib/active_storage/attached/many.rb +5 -4
- data/lib/active_storage/attached/model.rb +66 -43
- data/lib/active_storage/attached/one.rb +5 -4
- data/lib/active_storage/attached.rb +2 -0
- data/lib/active_storage/deprecator.rb +7 -0
- data/lib/active_storage/engine.rb +11 -7
- data/lib/active_storage/fixture_set.rb +2 -0
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +12 -0
- data/lib/active_storage/previewer.rb +8 -1
- data/lib/active_storage/reflection.rb +3 -3
- data/lib/active_storage/service/azure_storage_service.rb +2 -0
- data/lib/active_storage/service/disk_service.rb +2 -0
- data/lib/active_storage/service/gcs_service.rb +11 -20
- data/lib/active_storage/service/mirror_service.rb +10 -5
- data/lib/active_storage/service/s3_service.rb +2 -0
- data/lib/active_storage/service.rb +4 -2
- data/lib/active_storage/transformers/transformer.rb +2 -0
- data/lib/active_storage/version.rb +1 -1
- data/lib/active_storage.rb +19 -3
- metadata +19 -30
@@ -503,7 +503,7 @@
|
|
503
503
|
}
|
504
504
|
}
|
505
505
|
class BlobRecord {
|
506
|
-
constructor(file, checksum, url) {
|
506
|
+
constructor(file, checksum, url, customHeaders = {}) {
|
507
507
|
this.file = file;
|
508
508
|
this.attributes = {
|
509
509
|
filename: file.name,
|
@@ -517,6 +517,9 @@
|
|
517
517
|
this.xhr.setRequestHeader("Content-Type", "application/json");
|
518
518
|
this.xhr.setRequestHeader("Accept", "application/json");
|
519
519
|
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
520
|
+
Object.keys(customHeaders).forEach((headerKey => {
|
521
|
+
this.xhr.setRequestHeader(headerKey, customHeaders[headerKey]);
|
522
|
+
}));
|
520
523
|
const csrfToken = getMetaValue("csrf-token");
|
521
524
|
if (csrfToken != undefined) {
|
522
525
|
this.xhr.setRequestHeader("X-CSRF-Token", csrfToken);
|
@@ -596,11 +599,12 @@
|
|
596
599
|
}
|
597
600
|
let id = 0;
|
598
601
|
class DirectUpload {
|
599
|
-
constructor(file, url, delegate) {
|
602
|
+
constructor(file, url, delegate, customHeaders = {}) {
|
600
603
|
this.id = ++id;
|
601
604
|
this.file = file;
|
602
605
|
this.url = url;
|
603
606
|
this.delegate = delegate;
|
607
|
+
this.customHeaders = customHeaders;
|
604
608
|
}
|
605
609
|
create(callback) {
|
606
610
|
FileChecksum.create(this.file, ((error, checksum) => {
|
@@ -608,7 +612,7 @@
|
|
608
612
|
callback(error);
|
609
613
|
return;
|
610
614
|
}
|
611
|
-
const blob = new BlobRecord(this.file, checksum, this.url);
|
615
|
+
const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders);
|
612
616
|
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
|
613
617
|
blob.create((error => {
|
614
618
|
if (error) {
|
@@ -816,6 +820,8 @@
|
|
816
820
|
}
|
817
821
|
setTimeout(autostart, 1);
|
818
822
|
exports.DirectUpload = DirectUpload;
|
823
|
+
exports.DirectUploadController = DirectUploadController;
|
824
|
+
exports.DirectUploadsController = DirectUploadsController;
|
819
825
|
exports.start = start;
|
820
826
|
Object.defineProperty(exports, "__esModule", {
|
821
827
|
value: true
|
@@ -42,11 +42,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def decode_verified_key
|
45
|
-
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
45
|
+
key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
46
|
+
key&.deep_symbolize_keys
|
46
47
|
end
|
47
48
|
|
48
49
|
def decode_verified_token
|
49
|
-
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
50
|
+
token = ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
51
|
+
token&.deep_symbolize_keys
|
50
52
|
end
|
51
53
|
|
52
54
|
def acceptable_content?(token)
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/hash/except"
|
4
|
+
|
3
5
|
module ActiveStorage::FileServer # :nodoc:
|
4
6
|
private
|
5
7
|
def serve_file(path, content_type:, disposition:)
|
6
|
-
Rack::
|
8
|
+
::Rack::Files.new(nil).serving(request, path).tap do |(status, headers, body)|
|
7
9
|
self.status = status
|
8
10
|
self.response_body = body
|
9
11
|
|
@@ -11,6 +13,7 @@ module ActiveStorage::FileServer # :nodoc:
|
|
11
13
|
response.headers[name] = value
|
12
14
|
end
|
13
15
|
|
16
|
+
response.headers.except!("X-Cascade", "x-cascade") if status == 416
|
14
17
|
response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
|
15
18
|
response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
|
16
19
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { getMetaValue } from "./helpers"
|
2
2
|
|
3
3
|
export class BlobRecord {
|
4
|
-
constructor(file, checksum, url) {
|
4
|
+
constructor(file, checksum, url, customHeaders = {}) {
|
5
5
|
this.file = file
|
6
6
|
|
7
7
|
this.attributes = {
|
@@ -17,6 +17,9 @@ export class BlobRecord {
|
|
17
17
|
this.xhr.setRequestHeader("Content-Type", "application/json")
|
18
18
|
this.xhr.setRequestHeader("Accept", "application/json")
|
19
19
|
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
20
|
+
Object.keys(customHeaders).forEach((headerKey) => {
|
21
|
+
this.xhr.setRequestHeader(headerKey, customHeaders[headerKey])
|
22
|
+
})
|
20
23
|
|
21
24
|
const csrfToken = getMetaValue("csrf-token")
|
22
25
|
if (csrfToken != undefined) {
|
@@ -5,11 +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, delegate, customHeaders = {}) {
|
9
9
|
this.id = ++id
|
10
10
|
this.file = file
|
11
11
|
this.url = url
|
12
12
|
this.delegate = delegate
|
13
|
+
this.customHeaders = customHeaders
|
13
14
|
}
|
14
15
|
|
15
16
|
create(callback) {
|
@@ -19,7 +20,7 @@ export class DirectUpload {
|
|
19
20
|
return
|
20
21
|
}
|
21
22
|
|
22
|
-
const blob = new BlobRecord(this.file, checksum, this.url)
|
23
|
+
const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders)
|
23
24
|
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
|
24
25
|
|
25
26
|
blob.create(error => {
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { start } from "./ujs"
|
2
2
|
import { DirectUpload } from "./direct_upload"
|
3
|
-
|
3
|
+
import { DirectUploadController } from "./direct_upload_controller"
|
4
|
+
import { DirectUploadsController } from "./direct_uploads_controller"
|
5
|
+
export { start, DirectUpload, DirectUploadController, DirectUploadsController }
|
4
6
|
|
5
7
|
function autostart() {
|
6
8
|
if (window.ActiveStorage) {
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveStorage::TransformJob < ActiveStorage::BaseJob
|
4
|
+
queue_as { ActiveStorage.queues[:transform] }
|
5
|
+
|
6
|
+
discard_on ActiveRecord::RecordNotFound
|
7
|
+
retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
|
8
|
+
|
9
|
+
def perform(blob, transformations)
|
10
|
+
blob.variant(transformations).processed
|
11
|
+
end
|
12
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
4
|
|
5
|
+
# = Active Storage \Attachment
|
6
|
+
#
|
5
7
|
# Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
|
6
8
|
# but it is possible to associate many different records with the same blob. A foreign-key constraint
|
7
9
|
# on the attachments table prevents blobs from being purged if they’re still attached to any records.
|
@@ -18,16 +20,31 @@ require "active_support/core_ext/module/delegation"
|
|
18
20
|
class ActiveStorage::Attachment < ActiveStorage::Record
|
19
21
|
self.table_name = "active_storage_attachments"
|
20
22
|
|
23
|
+
##
|
24
|
+
# :method:
|
25
|
+
#
|
26
|
+
# Returns the associated record.
|
21
27
|
belongs_to :record, polymorphic: true, touch: true
|
28
|
+
|
29
|
+
##
|
30
|
+
# :method:
|
31
|
+
#
|
32
|
+
# Returns the associated ActiveStorage::Blob.
|
22
33
|
belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true
|
23
34
|
|
24
35
|
delegate_missing_to :blob
|
25
36
|
delegate :signed_id, to: :blob
|
26
37
|
|
27
|
-
after_create_commit :mirror_blob_later, :analyze_blob_later
|
38
|
+
after_create_commit :mirror_blob_later, :analyze_blob_later, :transform_variants_later
|
28
39
|
after_destroy_commit :purge_dependent_blob_later
|
29
40
|
|
30
|
-
|
41
|
+
##
|
42
|
+
# :singleton-method:
|
43
|
+
#
|
44
|
+
# Eager load all variant records on an attachment at once.
|
45
|
+
#
|
46
|
+
# User.first.avatars.with_all_variant_records
|
47
|
+
scope :with_all_variant_records, -> { includes(blob: { variant_records: { image_attachment: :blob } }) }
|
31
48
|
|
32
49
|
# Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
|
33
50
|
def purge
|
@@ -49,23 +66,61 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
49
66
|
|
50
67
|
# Returns an ActiveStorage::Variant or ActiveStorage::VariantWithRecord
|
51
68
|
# instance for the attachment with the set of +transformations+ provided.
|
69
|
+
# Example:
|
70
|
+
#
|
71
|
+
# avatar.variant(resize_to_limit: [100, 100]).processed.url
|
72
|
+
#
|
73
|
+
# or if you are using pre-defined variants:
|
74
|
+
#
|
75
|
+
# avatar.variant(:thumb).processed.url
|
76
|
+
#
|
52
77
|
# See ActiveStorage::Blob::Representable#variant for more information.
|
53
78
|
#
|
54
79
|
# Raises an +ArgumentError+ if +transformations+ is a +Symbol+ which is an
|
55
80
|
# unknown pre-defined variant of the attachment.
|
56
81
|
def variant(transformations)
|
57
|
-
|
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
|
-
|
82
|
+
transformations = transformations_by_name(transformations)
|
66
83
|
blob.variant(transformations)
|
67
84
|
end
|
68
85
|
|
86
|
+
# Returns an ActiveStorage::Preview instance for the attachment with the set
|
87
|
+
# of +transformations+ provided.
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# video.preview(resize_to_limit: [100, 100]).processed.url
|
91
|
+
#
|
92
|
+
# or if you are using pre-defined variants:
|
93
|
+
#
|
94
|
+
# video.preview(:thumb).processed.url
|
95
|
+
#
|
96
|
+
# See ActiveStorage::Blob::Representable#preview for more information.
|
97
|
+
#
|
98
|
+
# Raises an +ArgumentError+ if +transformations+ is a +Symbol+ which is an
|
99
|
+
# unknown pre-defined variant of the attachment.
|
100
|
+
def preview(transformations)
|
101
|
+
transformations = transformations_by_name(transformations)
|
102
|
+
blob.preview(transformations)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns an ActiveStorage::Preview or an ActiveStorage::Variant for the
|
106
|
+
# attachment with set of +transformations+ provided.
|
107
|
+
# Example:
|
108
|
+
#
|
109
|
+
# avatar.representation(resize_to_limit: [100, 100]).processed.url
|
110
|
+
#
|
111
|
+
# or if you are using pre-defined variants:
|
112
|
+
#
|
113
|
+
# avatar.representation(:thumb).processed.url
|
114
|
+
#
|
115
|
+
# See ActiveStorage::Blob::Representable#representation for more information.
|
116
|
+
#
|
117
|
+
# Raises an +ArgumentError+ if +transformations+ is a +Symbol+ which is an
|
118
|
+
# unknown pre-defined variant of the attachment.
|
119
|
+
def representation(transformations)
|
120
|
+
transformations = transformations_by_name(transformations)
|
121
|
+
blob.representation(transformations)
|
122
|
+
end
|
123
|
+
|
69
124
|
private
|
70
125
|
def analyze_blob_later
|
71
126
|
blob.analyze_later unless blob.analyzed?
|
@@ -75,6 +130,12 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
75
130
|
blob.mirror_later
|
76
131
|
end
|
77
132
|
|
133
|
+
def transform_variants_later
|
134
|
+
named_variants.each do |_name, named_variant|
|
135
|
+
blob.preprocessed(named_variant.transformations) if named_variant.preprocessed?(record)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
78
139
|
def purge_dependent_blob_later
|
79
140
|
blob&.purge_later if dependent == :purge_later
|
80
141
|
end
|
@@ -83,8 +144,21 @@ class ActiveStorage::Attachment < ActiveStorage::Record
|
|
83
144
|
record.attachment_reflections[name]&.options&.fetch(:dependent, nil)
|
84
145
|
end
|
85
146
|
|
86
|
-
def
|
87
|
-
record.attachment_reflections[name]&.
|
147
|
+
def named_variants
|
148
|
+
record.attachment_reflections[name]&.named_variants
|
149
|
+
end
|
150
|
+
|
151
|
+
def transformations_by_name(transformations)
|
152
|
+
case transformations
|
153
|
+
when Symbol
|
154
|
+
variant_name = transformations
|
155
|
+
named_variants.fetch(variant_name) do
|
156
|
+
record_model_name = record.to_model.model_name.name
|
157
|
+
raise ArgumentError, "Cannot find variant :#{variant_name} for #{record_model_name}##{name}"
|
158
|
+
end.transformations
|
159
|
+
else
|
160
|
+
transformations
|
161
|
+
end
|
88
162
|
end
|
89
163
|
end
|
90
164
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_storage/analyzer/null_analyzer"
|
4
4
|
|
5
|
+
# = Active Storage \Blob \Analyzable
|
5
6
|
module ActiveStorage::Blob::Analyzable
|
6
7
|
# Extracts and stores metadata from the file associated with this blob using a relevant analyzer. Active Storage comes
|
7
8
|
# with built-in analyzers for images and videos. See ActiveStorage::Analyzer::ImageAnalyzer and
|
@@ -12,7 +13,7 @@ module ActiveStorage::Blob::Analyzable
|
|
12
13
|
# first analyzer for which +accept?+ returns true when given the blob. If no registered analyzer accepts the blob, no
|
13
14
|
# metadata is extracted from it.
|
14
15
|
#
|
15
|
-
# In a Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+
|
16
|
+
# In a \Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+
|
16
17
|
# in an initializer:
|
17
18
|
#
|
18
19
|
# # Add a custom analyzer for Microsoft Office documents:
|
@@ -21,9 +22,9 @@ module ActiveStorage::Blob::Analyzable
|
|
21
22
|
# # Remove the built-in video analyzer:
|
22
23
|
# Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
|
23
24
|
#
|
24
|
-
# Outside of a Rails application, manipulate +ActiveStorage.analyzers+ instead.
|
25
|
+
# Outside of a \Rails application, manipulate +ActiveStorage.analyzers+ instead.
|
25
26
|
#
|
26
|
-
# You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously
|
27
|
+
# You won't ordinarily need to call this method from a \Rails application. New blobs are automatically and asynchronously
|
27
28
|
# analyzed via #analyze_later when they're attached for the first time.
|
28
29
|
def analyze
|
29
30
|
update! metadata: metadata.merge(extract_metadata_via_analyzer)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "marcel"
|
4
4
|
|
5
5
|
module ActiveStorage::Blob::Representable
|
6
6
|
extend ActiveSupport::Concern
|
@@ -98,6 +98,10 @@ module ActiveStorage::Blob::Representable
|
|
98
98
|
variable? || previewable?
|
99
99
|
end
|
100
100
|
|
101
|
+
def preprocessed(transformations) # :nodoc:
|
102
|
+
ActiveStorage::TransformJob.perform_later(self, transformations)
|
103
|
+
end
|
104
|
+
|
101
105
|
private
|
102
106
|
def default_variant_transformations
|
103
107
|
{ format: default_variant_format }
|
@@ -112,10 +116,10 @@ module ActiveStorage::Blob::Representable
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def format
|
115
|
-
if filename.extension.present? &&
|
119
|
+
if filename.extension.present? && Marcel::MimeType.for(extension: filename.extension) == content_type
|
116
120
|
filename.extension
|
117
121
|
else
|
118
|
-
|
122
|
+
Marcel::Magic.new(content_type.to_s).extensions.first
|
119
123
|
end
|
120
124
|
end
|
121
125
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Blob
|
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
|
#
|
@@ -15,24 +17,9 @@
|
|
15
17
|
# update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
|
16
18
|
# If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
|
17
19
|
class ActiveStorage::Blob < ActiveStorage::Record
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
#
|
22
|
-
# include Analyzable
|
23
|
-
#
|
24
|
-
# would resolve to the top-level one, const_missing would not be triggered,
|
25
|
-
# and therefore ActiveStorage::Blob::Analyzable would not be autoloaded.
|
26
|
-
#
|
27
|
-
# By using qualified names, we ensure const_missing is invoked if needed.
|
28
|
-
# Please, note that Ruby 2.5 or newer is required, so Object is not checked
|
29
|
-
# when looking up the ancestors of ActiveStorage::Blob.
|
30
|
-
#
|
31
|
-
# Zeitwerk mode does not have this gotcha. If we ever drop classic mode, this
|
32
|
-
# can be simplified, bare constant names would just work.
|
33
|
-
include ActiveStorage::Blob::Analyzable
|
34
|
-
include ActiveStorage::Blob::Identifiable
|
35
|
-
include ActiveStorage::Blob::Representable
|
20
|
+
include Analyzable
|
21
|
+
include Identifiable
|
22
|
+
include Representable
|
36
23
|
|
37
24
|
self.table_name = "active_storage_blobs"
|
38
25
|
|
@@ -44,14 +31,24 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
44
31
|
class_attribute :services, default: {}
|
45
32
|
class_attribute :service, instance_accessor: false
|
46
33
|
|
34
|
+
##
|
35
|
+
# :method:
|
36
|
+
#
|
37
|
+
# Returns the associated +ActiveStorage::Attachment+s.
|
47
38
|
has_many :attachments
|
48
39
|
|
40
|
+
##
|
41
|
+
# :singleton-method:
|
42
|
+
#
|
43
|
+
# Returns the blobs that aren't attached to any record.
|
49
44
|
scope :unattached, -> { where.missing(:attachments) }
|
50
45
|
|
51
46
|
after_initialize do
|
52
47
|
self.service_name ||= self.class.service&.name
|
53
48
|
end
|
54
49
|
|
50
|
+
after_update :touch_attachment_records
|
51
|
+
|
55
52
|
after_update_commit :update_service_metadata, if: -> { content_type_previously_changed? || metadata_previously_changed? }
|
56
53
|
|
57
54
|
before_destroy(prepend: true) do
|
@@ -166,7 +163,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
166
163
|
end
|
167
164
|
|
168
165
|
# Returns the key pointing to the file on the service that's associated with this blob. The key is the
|
169
|
-
# secure-token format from Rails in lower case. So it'll look like: xtapjjcjiudrlk3tmwyjgpuobabd.
|
166
|
+
# secure-token format from \Rails in lower case. So it'll look like: xtapjjcjiudrlk3tmwyjgpuobabd.
|
170
167
|
# This key is not intended to be revealed directly to the user.
|
171
168
|
# Always refer to blobs using the signed_id or a verified form of the key.
|
172
169
|
def key
|
@@ -309,7 +306,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
309
306
|
end
|
310
307
|
|
311
308
|
def mirror_later # :nodoc:
|
312
|
-
|
309
|
+
service.mirror_later key, checksum: checksum if service.respond_to?(:mirror_later)
|
313
310
|
end
|
314
311
|
|
315
312
|
# Deletes the files on the service associated with the blob. This should only be done if the blob is going to be
|
@@ -340,33 +337,10 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
340
337
|
services.fetch(service_name)
|
341
338
|
end
|
342
339
|
|
343
|
-
def content_type=(value)
|
344
|
-
unless ActiveStorage.silence_invalid_content_types_warning
|
345
|
-
if INVALID_VARIABLE_CONTENT_TYPES_DEPRECATED_IN_RAILS_7.include?(value)
|
346
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
347
|
-
#{value} is not a valid content type, it should not be used when creating a blob, and support for it will be removed in Rails 7.1.
|
348
|
-
If you want to keep supporting this content type past Rails 7.1, add it to `config.active_storage.variable_content_types`.
|
349
|
-
Dismiss this warning by setting `config.active_storage.silence_invalid_content_types_warning = true`.
|
350
|
-
MSG
|
351
|
-
end
|
352
|
-
|
353
|
-
if INVALID_VARIABLE_CONTENT_TYPES_TO_SERVE_AS_BINARY_DEPRECATED_IN_RAILS_7.include?(value)
|
354
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
355
|
-
#{value} is not a valid content type, it should not be used when creating a blob, and support for it will be removed in Rails 7.1.
|
356
|
-
If you want to keep supporting this content type past Rails 7.1, add it to `config.active_storage.content_types_to_serve_as_binary`.
|
357
|
-
Dismiss this warning by setting `config.active_storage.silence_invalid_content_types_warning = true`.
|
358
|
-
MSG
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
super
|
363
|
-
end
|
364
|
-
|
365
|
-
INVALID_VARIABLE_CONTENT_TYPES_DEPRECATED_IN_RAILS_7 = ["image/jpg", "image/pjpeg", "image/bmp"]
|
366
|
-
INVALID_VARIABLE_CONTENT_TYPES_TO_SERVE_AS_BINARY_DEPRECATED_IN_RAILS_7 = ["text/javascript"]
|
367
|
-
|
368
340
|
private
|
369
341
|
def compute_checksum_in_chunks(io)
|
342
|
+
raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
|
343
|
+
|
370
344
|
OpenSSL::Digest::MD5.new.tap do |checksum|
|
371
345
|
while chunk = io.read(5.megabytes)
|
372
346
|
checksum << chunk
|
@@ -402,6 +376,12 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
402
376
|
end
|
403
377
|
end
|
404
378
|
|
379
|
+
def touch_attachment_records
|
380
|
+
attachments.includes(:record).each do |attachment|
|
381
|
+
attachment.touch
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
405
385
|
def update_service_metadata
|
406
386
|
service.update_metadata key, **service_metadata if service_metadata.any?
|
407
387
|
end
|
@@ -2,14 +2,4 @@
|
|
2
2
|
|
3
3
|
class ActiveStorage::Current < ActiveSupport::CurrentAttributes # :nodoc:
|
4
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
|
15
5
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Filename
|
4
|
+
#
|
3
5
|
# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
|
4
6
|
# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
|
5
7
|
class ActiveStorage::Filename
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveStorage::NamedVariant # :nodoc:
|
4
|
+
attr_reader :transformations, :preprocessed
|
5
|
+
|
6
|
+
def initialize(transformations)
|
7
|
+
@preprocessed = transformations[:preprocessed]
|
8
|
+
@transformations = transformations.except(:preprocessed)
|
9
|
+
end
|
10
|
+
|
11
|
+
def preprocessed?(record)
|
12
|
+
case preprocessed
|
13
|
+
when Symbol
|
14
|
+
record.send(preprocessed)
|
15
|
+
when Proc
|
16
|
+
preprocessed.call(record)
|
17
|
+
else
|
18
|
+
preprocessed
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Preview
|
4
|
+
#
|
3
5
|
# Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
|
4
6
|
# extracting its first frame, and a PDF blob can be previewed by extracting its first page.
|
5
7
|
#
|
@@ -10,7 +12,7 @@
|
|
10
12
|
# documentation for more details on what's required of previewers.
|
11
13
|
#
|
12
14
|
# To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the
|
13
|
-
# first previewer for which +accept?+ returns true when given the blob. In a Rails application, add or remove previewers
|
15
|
+
# first previewer for which +accept?+ returns true when given the blob. In a \Rails application, add or remove previewers
|
14
16
|
# by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
|
15
17
|
#
|
16
18
|
# Rails.application.config.active_storage.previewers
|
@@ -20,13 +22,13 @@
|
|
20
22
|
# Rails.application.config.active_storage.previewers << DOCXPreviewer
|
21
23
|
# # => [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
|
22
24
|
#
|
23
|
-
# Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
|
25
|
+
# Outside of a \Rails application, modify +ActiveStorage.previewers+ instead.
|
24
26
|
#
|
25
27
|
# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
|
26
28
|
# {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
|
27
29
|
# and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
|
28
30
|
#
|
29
|
-
# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
|
31
|
+
# These libraries are not provided by \Rails. You must install them yourself to use the built-in previewers. Before you
|
30
32
|
# install and use third-party software, make sure you understand the licensing implications of doing so.
|
31
33
|
class ActiveStorage::Preview
|
32
34
|
class UnprocessedError < StandardError; end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Variant
|
4
|
+
#
|
3
5
|
# Image blobs can have variants that are the result of a set of transformations applied to the original.
|
4
6
|
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
|
5
7
|
# original.
|
@@ -72,7 +74,7 @@ class ActiveStorage::Variant
|
|
72
74
|
|
73
75
|
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
|
74
76
|
#
|
75
|
-
# Use <tt>url_for(variant)</tt> (or the implied form, like
|
77
|
+
# Use <tt>url_for(variant)</tt> (or the implied form, like <tt>link_to variant</tt> or <tt>redirect_to variant</tt>) to get the stable URL
|
76
78
|
# for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
|
77
79
|
# for its redirection.
|
78
80
|
def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
|
@@ -89,17 +91,16 @@ class ActiveStorage::Variant
|
|
89
91
|
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
90
92
|
end
|
91
93
|
|
92
|
-
alias_method :content_type_for_serving, :content_type
|
93
|
-
|
94
|
-
def forced_disposition_for_serving # :nodoc:
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
94
|
# Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
|
99
95
|
def image
|
100
96
|
self
|
101
97
|
end
|
102
98
|
|
99
|
+
# Deletes variant file from service.
|
100
|
+
def destroy
|
101
|
+
service.delete(key)
|
102
|
+
end
|
103
|
+
|
103
104
|
private
|
104
105
|
def processed?
|
105
106
|
service.exist?(key)
|
@@ -1,35 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# = Active Storage \Variant With Record
|
4
|
+
#
|
3
5
|
# Like an ActiveStorage::Variant, but keeps detail about the variant in the database as an
|
4
6
|
# ActiveStorage::VariantRecord. This is only used if +ActiveStorage.track_variants+ is enabled.
|
5
7
|
class ActiveStorage::VariantWithRecord
|
6
8
|
attr_reader :blob, :variation
|
7
9
|
delegate :service, to: :blob
|
10
|
+
delegate :content_type, to: :variation
|
8
11
|
|
9
12
|
def initialize(blob, variation)
|
10
13
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
|
11
14
|
end
|
12
15
|
|
13
16
|
def processed
|
14
|
-
process
|
17
|
+
process unless processed?
|
15
18
|
self
|
16
19
|
end
|
17
20
|
|
18
|
-
def
|
19
|
-
|
21
|
+
def image
|
22
|
+
record&.image
|
20
23
|
end
|
21
24
|
|
22
|
-
def
|
23
|
-
|
25
|
+
def filename
|
26
|
+
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
|
29
|
+
# Destroys record and deletes file from service.
|
30
|
+
def destroy
|
31
|
+
record&.destroy
|
28
32
|
end
|
29
33
|
|
30
34
|
delegate :key, :url, :download, to: :image, allow_nil: true
|
31
35
|
|
32
36
|
private
|
37
|
+
def processed?
|
38
|
+
record.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def process
|
42
|
+
transform_blob { |image| create_or_find_record(image: image) }
|
43
|
+
end
|
44
|
+
|
33
45
|
def transform_blob
|
34
46
|
blob.open do |input|
|
35
47
|
variation.transform(input) do |output|
|