activestorage 7.2.2.2 → 8.0.3
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 +39 -68
- data/README.md +3 -3
- data/app/assets/javascripts/activestorage.esm.js +1 -1
- data/app/assets/javascripts/activestorage.js +1 -0
- data/app/controllers/active_storage/direct_uploads_controller.rb +1 -1
- data/app/controllers/active_storage/disk_controller.rb +2 -2
- data/app/controllers/concerns/active_storage/streaming.rb +9 -0
- data/app/javascript/activestorage/index.js +2 -1
- data/app/models/active_storage/blob/representable.rb +71 -5
- data/app/models/active_storage/blob.rb +3 -20
- data/app/models/active_storage/filename.rb +1 -1
- data/app/models/active_storage/variant.rb +6 -6
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +1 -1
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/model.rb +41 -18
- data/lib/active_storage/engine.rb +4 -1
- data/lib/active_storage/fixture_set.rb +1 -1
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/service/azure_storage_service.rb +7 -0
- data/lib/active_storage/service/mirror_service.rb +12 -3
- data/lib/active_storage/service/s3_service.rb +19 -4
- metadata +13 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 022126fe7ad33aff5dd19e9cb369c9a4264b20b87e33af853f07cf8946bc6b4a
|
|
4
|
+
data.tar.gz: e32c5d37045bb9e94d86ae04c24b290a538fe9931eca6d8debdbc8d5d049cdd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16d1b5343105d7b22c8b3e85b2d3200ab1fa9d4b88e0313a2154f26b0a8dcfcf3039ad86acaad6a631bccb6e91bf560f570c5e0ab9f962881edf7d52d75da5ba
|
|
7
|
+
data.tar.gz: f6c5e9f142ee4ddce2be8cb7ae89009d60249ab454160ed3ab4be9fdc55336d2021f9ba411957bdb1aa128d0c7de74d5b4a45e9b5cd4c26500abc75fe0903945
|
data/CHANGELOG.md
CHANGED
|
@@ -1,111 +1,82 @@
|
|
|
1
|
-
## Rails
|
|
1
|
+
## Rails 8.0.3 (September 22, 2025) ##
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
* Address deprecation of `Aws::S3::Object#upload_stream` in `ActiveStorage::Service::S3Service`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
*Joshua Young*
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
* Fix `config.active_storage.touch_attachment_records` to work with eager loading.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
*fatkodima*
|
|
10
10
|
|
|
11
|
-
* No changes.
|
|
12
11
|
|
|
12
|
+
## Rails 8.0.2.1 (August 13, 2025) ##
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
* Remove dangerous transformations
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
[CVE-2025-24293]
|
|
17
17
|
|
|
18
|
+
*Zack Deveau*
|
|
18
19
|
|
|
19
|
-
## Rails
|
|
20
|
+
## Rails 8.0.2 (March 12, 2025) ##
|
|
20
21
|
|
|
21
|
-
*
|
|
22
|
+
* A Blob will no longer autosave associated Attachment.
|
|
22
23
|
|
|
24
|
+
This fixes an issue where a record with an attachment would have
|
|
25
|
+
its dirty attributes reset, preventing your `after commit` callbacks
|
|
26
|
+
on that record to behave as expected.
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
Note that this change doesn't require any changes on your application
|
|
29
|
+
and is supposed to be internal. Active Storage Attachment will continue
|
|
30
|
+
to be autosaved (through a different relation).
|
|
25
31
|
|
|
26
|
-
*
|
|
32
|
+
*Edouard-chin*
|
|
27
33
|
|
|
28
34
|
|
|
29
|
-
## Rails
|
|
35
|
+
## Rails 8.0.1 (December 13, 2024) ##
|
|
30
36
|
|
|
31
37
|
* No changes.
|
|
32
38
|
|
|
33
39
|
|
|
34
|
-
## Rails
|
|
35
|
-
|
|
36
|
-
* Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
|
|
37
|
-
|
|
38
|
-
*Rafael Mendonça França*
|
|
39
|
-
|
|
40
|
-
* Remove deprecated `config.active_storage.replace_on_assign_to_many`.
|
|
40
|
+
## Rails 8.0.0.1 (December 10, 2024) ##
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* Add support for custom `key` in `ActiveStorage::Blob#compose`.
|
|
45
|
-
|
|
46
|
-
*Elvin Efendiev*
|
|
47
|
-
|
|
48
|
-
* Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
|
|
49
|
-
is set.
|
|
50
|
-
|
|
51
|
-
*Lewis Buckley*
|
|
52
|
-
|
|
53
|
-
* Fix JSON-encoding of `ActiveStorage::Filename` instances.
|
|
54
|
-
|
|
55
|
-
*Jonathan del Strother*
|
|
56
|
-
|
|
57
|
-
* Fix N+1 query when fetching preview images for non-image assets.
|
|
42
|
+
* No changes.
|
|
58
43
|
|
|
59
|
-
*Aaron Patterson & Justin Searls*
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
`ActiveRecord::Base.table_name_prefix` configuration.
|
|
45
|
+
## Rails 8.0.0 (November 07, 2024) ##
|
|
63
46
|
|
|
64
|
-
|
|
47
|
+
* No changes.
|
|
65
48
|
|
|
66
|
-
* Fix `ActiveStorage::Representations::ProxyController` not returning the proper
|
|
67
|
-
preview image variant for previewable files.
|
|
68
49
|
|
|
69
|
-
|
|
50
|
+
## Rails 8.0.0.rc2 (October 30, 2024) ##
|
|
70
51
|
|
|
71
|
-
*
|
|
72
|
-
variants.
|
|
52
|
+
* No changes.
|
|
73
53
|
|
|
74
|
-
*Chedli Bourguiba*
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
for blobs that are not representable.
|
|
55
|
+
## Rails 8.0.0.rc1 (October 19, 2024) ##
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
* No changes.
|
|
80
58
|
|
|
81
|
-
* Prevent `ActiveStorage::Blob#preview` to generate a variant if an empty variation is passed.
|
|
82
59
|
|
|
83
|
-
|
|
84
|
-
image instead of generating a variant with the exact same dimensions.
|
|
60
|
+
## Rails 8.0.0.beta1 (September 26, 2024) ##
|
|
85
61
|
|
|
86
|
-
|
|
62
|
+
* Deprecate `ActiveStorage::Service::AzureStorageService`.
|
|
87
63
|
|
|
88
|
-
*
|
|
64
|
+
*zzak*
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Previously, the `:thumb` variant would not be generated until a further call
|
|
93
|
-
to e.g. `processed.url`.
|
|
66
|
+
* Improve `ActiveStorage::Filename#sanitized` method to handle special characters more effectively.
|
|
67
|
+
Replace the characters `"*?<>` with `-` if they exist in the Filename to match the Filename convention of Win OS.
|
|
94
68
|
|
|
95
|
-
*
|
|
69
|
+
*Luong Viet Dung(Martin)*
|
|
96
70
|
|
|
97
|
-
*
|
|
98
|
-
enabled and the variant of an Active Storage preview has already been
|
|
99
|
-
processed (for example, by calling `ActiveStorage::Preview#url`).
|
|
71
|
+
* Improve InvariableError, UnpreviewableError and UnrepresentableError message.
|
|
100
72
|
|
|
101
|
-
|
|
73
|
+
Include Blob ID and content_type in the messages.
|
|
102
74
|
|
|
103
|
-
*
|
|
75
|
+
*Petrik de Heus*
|
|
104
76
|
|
|
105
|
-
|
|
77
|
+
* Mark proxied files as `immutable` in their Cache-Control header
|
|
106
78
|
|
|
107
|
-
*
|
|
79
|
+
*Nate Matykiewicz*
|
|
108
80
|
|
|
109
|
-
*Yogesh Khater*
|
|
110
81
|
|
|
111
|
-
Please check [7-
|
|
82
|
+
Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activestorage/CHANGELOG.md) for previous changes.
|
data/README.md
CHANGED
|
@@ -73,7 +73,7 @@ end
|
|
|
73
73
|
```erb
|
|
74
74
|
<%= form_with model: @message, local: true do |form| %>
|
|
75
75
|
<%= form.text_field :title, placeholder: "Title" %><br>
|
|
76
|
-
<%= form.
|
|
76
|
+
<%= form.textarea :content %><br><br>
|
|
77
77
|
|
|
78
78
|
<%= form.file_field :images, multiple: true %><br>
|
|
79
79
|
<%= form.submit %>
|
|
@@ -88,7 +88,7 @@ class MessagesController < ApplicationController
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def create
|
|
91
|
-
message = Message.create! params.
|
|
91
|
+
message = Message.create! params.expect(message: [ :title, :content, images: [] ])
|
|
92
92
|
redirect_to message
|
|
93
93
|
end
|
|
94
94
|
|
|
@@ -203,6 +203,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
|
|
|
203
203
|
|
|
204
204
|
* https://github.com/rails/rails/issues
|
|
205
205
|
|
|
206
|
-
Feature requests should be discussed on the
|
|
206
|
+
Feature requests should be discussed on the rubyonrails-core forum here:
|
|
207
207
|
|
|
208
208
|
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
|
@@ -822,6 +822,7 @@
|
|
|
822
822
|
exports.DirectUpload = DirectUpload;
|
|
823
823
|
exports.DirectUploadController = DirectUploadController;
|
|
824
824
|
exports.DirectUploadsController = DirectUploadsController;
|
|
825
|
+
exports.dispatchEvent = dispatchEvent;
|
|
825
826
|
exports.start = start;
|
|
826
827
|
Object.defineProperty(exports, "__esModule", {
|
|
827
828
|
value: true
|
|
@@ -11,7 +11,7 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
|
|
|
11
11
|
|
|
12
12
|
private
|
|
13
13
|
def blob_args
|
|
14
|
-
params.
|
|
14
|
+
params.expect(blob: [:filename, :byte_size, :checksum, :content_type, metadata: {}]).to_h.symbolize_keys
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def direct_upload_json(blob)
|
|
@@ -25,13 +25,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
|
|
|
25
25
|
named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
|
|
26
26
|
head :no_content
|
|
27
27
|
else
|
|
28
|
-
head
|
|
28
|
+
head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
|
|
29
29
|
end
|
|
30
30
|
else
|
|
31
31
|
head :not_found
|
|
32
32
|
end
|
|
33
33
|
rescue ActiveStorage::IntegrityError
|
|
34
|
-
head
|
|
34
|
+
head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
private
|
|
@@ -61,6 +61,15 @@ module ActiveStorage::Streaming
|
|
|
61
61
|
blob.download do |chunk|
|
|
62
62
|
stream.write chunk
|
|
63
63
|
end
|
|
64
|
+
rescue ActiveStorage::FileNotFoundError
|
|
65
|
+
expires_now
|
|
66
|
+
head :not_found
|
|
67
|
+
rescue
|
|
68
|
+
# Status and caching headers are already set, but not committed.
|
|
69
|
+
# Change the status to 500 manually.
|
|
70
|
+
expires_now
|
|
71
|
+
head :internal_server_error
|
|
72
|
+
raise
|
|
64
73
|
end
|
|
65
74
|
end
|
|
66
75
|
end
|
|
@@ -2,7 +2,8 @@ import { start } from "./ujs"
|
|
|
2
2
|
import { DirectUpload } from "./direct_upload"
|
|
3
3
|
import { DirectUploadController } from "./direct_upload_controller"
|
|
4
4
|
import { DirectUploadsController } from "./direct_uploads_controller"
|
|
5
|
-
|
|
5
|
+
import { dispatchEvent } from "./helpers"
|
|
6
|
+
export { start, DirectUpload, DirectUploadController, DirectUploadsController, dispatchEvent }
|
|
6
7
|
|
|
7
8
|
function autostart() {
|
|
8
9
|
if (window.ActiveStorage) {
|
|
@@ -25,17 +25,83 @@ module ActiveStorage::Blob::Representable
|
|
|
25
25
|
#
|
|
26
26
|
# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
|
|
27
27
|
#
|
|
28
|
-
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::
|
|
29
|
-
# can then produce on-demand.
|
|
28
|
+
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
|
|
29
|
+
# or ActiveStorage::Representations::RedirectController can then produce on-demand.
|
|
30
30
|
#
|
|
31
31
|
# Raises ActiveStorage::InvariableError if the variant processor cannot
|
|
32
32
|
# transform the blob. To determine whether a blob is variable, call
|
|
33
33
|
# ActiveStorage::Blob#variable?.
|
|
34
|
+
#
|
|
35
|
+
# ==== Options
|
|
36
|
+
#
|
|
37
|
+
# Options are defined by the {image_processing gem}[https://github.com/janko/image_processing],
|
|
38
|
+
# and depend on which variant processor you are using:
|
|
39
|
+
# {Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md] or
|
|
40
|
+
# {MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md].
|
|
41
|
+
# However, both variant processors support the following options:
|
|
42
|
+
#
|
|
43
|
+
# [+:resize_to_limit+]
|
|
44
|
+
# Downsizes the image to fit within the specified dimensions while retaining
|
|
45
|
+
# the original aspect ratio. Will only resize the image if it's larger than
|
|
46
|
+
# the specified dimensions.
|
|
47
|
+
#
|
|
48
|
+
# user.avatar.variant(resize_to_limit: [100, 100])
|
|
49
|
+
#
|
|
50
|
+
# [+:resize_to_fit+]
|
|
51
|
+
# Resizes the image to fit within the specified dimensions while retaining
|
|
52
|
+
# the original aspect ratio. Will downsize the image if it's larger than the
|
|
53
|
+
# specified dimensions or upsize if it's smaller.
|
|
54
|
+
#
|
|
55
|
+
# user.avatar.variant(resize_to_fit: [100, 100])
|
|
56
|
+
#
|
|
57
|
+
# [+:resize_to_fill+]
|
|
58
|
+
# Resizes the image to fill the specified dimensions while retaining the
|
|
59
|
+
# original aspect ratio. If necessary, will crop the image in the larger
|
|
60
|
+
# dimension.
|
|
61
|
+
#
|
|
62
|
+
# user.avatar.variant(resize_to_fill: [100, 100])
|
|
63
|
+
#
|
|
64
|
+
# [+:resize_and_pad+]
|
|
65
|
+
# Resizes the image to fit within the specified dimensions while retaining
|
|
66
|
+
# the original aspect ratio. If necessary, will pad the remaining area with
|
|
67
|
+
# transparent color if source image has alpha channel, black otherwise.
|
|
68
|
+
#
|
|
69
|
+
# user.avatar.variant(resize_and_pad: [100, 100])
|
|
70
|
+
#
|
|
71
|
+
# [+:crop+]
|
|
72
|
+
# Extracts an area from an image. The first two arguments are the left and
|
|
73
|
+
# top edges of area to extract, while the last two arguments are the width
|
|
74
|
+
# and height of the area to extract.
|
|
75
|
+
#
|
|
76
|
+
# user.avatar.variant(crop: [20, 50, 300, 300])
|
|
77
|
+
#
|
|
78
|
+
# [+:rotate+]
|
|
79
|
+
# Rotates the image by the specified angle.
|
|
80
|
+
#
|
|
81
|
+
# user.avatar.variant(rotate: 90)
|
|
82
|
+
#
|
|
83
|
+
# Some options, including those listed above, can accept additional
|
|
84
|
+
# processor-specific values which can be passed as a trailing hash:
|
|
85
|
+
#
|
|
86
|
+
# <!-- Vips supports configuring `crop` for many of its transformations -->
|
|
87
|
+
# <%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>
|
|
88
|
+
#
|
|
89
|
+
# If migrating an existing application between MiniMagick and Vips, you will
|
|
90
|
+
# need to update processor-specific options:
|
|
91
|
+
#
|
|
92
|
+
# <!-- MiniMagick -->
|
|
93
|
+
# <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
|
|
94
|
+
# sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
|
|
95
|
+
#
|
|
96
|
+
# <!-- Vips -->
|
|
97
|
+
# <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
|
|
98
|
+
# saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
|
|
99
|
+
#
|
|
34
100
|
def variant(transformations)
|
|
35
101
|
if variable?
|
|
36
102
|
variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
|
|
37
103
|
else
|
|
38
|
-
raise ActiveStorage::InvariableError
|
|
104
|
+
raise ActiveStorage::InvariableError, "Can't transform blob with ID=#{id} and content_type=#{content_type}"
|
|
39
105
|
end
|
|
40
106
|
end
|
|
41
107
|
|
|
@@ -64,7 +130,7 @@ module ActiveStorage::Blob::Representable
|
|
|
64
130
|
if previewable?
|
|
65
131
|
ActiveStorage::Preview.new(self, transformations)
|
|
66
132
|
else
|
|
67
|
-
raise ActiveStorage::UnpreviewableError
|
|
133
|
+
raise ActiveStorage::UnpreviewableError, "No previewer found for blob with ID=#{id} and content_type=#{content_type}"
|
|
68
134
|
end
|
|
69
135
|
end
|
|
70
136
|
|
|
@@ -89,7 +155,7 @@ module ActiveStorage::Blob::Representable
|
|
|
89
155
|
when variable?
|
|
90
156
|
variant transformations
|
|
91
157
|
else
|
|
92
|
-
raise ActiveStorage::UnrepresentableError
|
|
158
|
+
raise ActiveStorage::UnrepresentableError, "No previewer found and can't transform blob with ID=#{id} and content_type=#{content_type}"
|
|
93
159
|
end
|
|
94
160
|
end
|
|
95
161
|
|
|
@@ -29,7 +29,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
|
29
29
|
# :method:
|
|
30
30
|
#
|
|
31
31
|
# Returns the associated ActiveStorage::Attachment instances.
|
|
32
|
-
has_many :attachments
|
|
32
|
+
has_many :attachments, autosave: false
|
|
33
33
|
|
|
34
34
|
##
|
|
35
35
|
# :singleton-method:
|
|
@@ -71,9 +71,8 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
|
|
74
|
-
# exception if the +signed_id+ has either expired, has a purpose mismatch,
|
|
75
|
-
#
|
|
76
|
-
# the valid signed id can't find a record.
|
|
74
|
+
# exception if the +signed_id+ has either expired, has a purpose mismatch, or has been tampered with.
|
|
75
|
+
# It will also raise an +ActiveRecord::RecordNotFound+ exception if the valid signed id can't find a record.
|
|
77
76
|
def find_signed!(id, record: nil, purpose: :blob_id)
|
|
78
77
|
super(id, purpose: purpose)
|
|
79
78
|
end
|
|
@@ -152,22 +151,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
|
152
151
|
combined_blob.save!
|
|
153
152
|
end
|
|
154
153
|
end
|
|
155
|
-
|
|
156
|
-
def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
|
|
157
|
-
if service_name
|
|
158
|
-
services.fetch(service_name) do
|
|
159
|
-
raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
|
|
160
|
-
end
|
|
161
|
-
else
|
|
162
|
-
validate_global_service_configuration
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def validate_global_service_configuration # :nodoc:
|
|
167
|
-
if connected? && table_exists? && Rails.configuration.active_storage.service.nil?
|
|
168
|
-
raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
154
|
end
|
|
172
155
|
|
|
173
156
|
include Analyzable
|
|
@@ -57,7 +57,7 @@ class ActiveStorage::Filename
|
|
|
57
57
|
#
|
|
58
58
|
# Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash.
|
|
59
59
|
def sanitized
|
|
60
|
-
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}
|
|
60
|
+
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/<>?*\"\t\r\n\\", "-")
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Returns the sanitized version of the filename.
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
# Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
|
|
23
23
|
# you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
|
|
24
24
|
# in a template, for example. Delay the processing to an on-demand controller, like the one provided in
|
|
25
|
-
# ActiveStorage::
|
|
25
|
+
# ActiveStorage::Representations::ProxyController and ActiveStorage::Representations::RedirectController.
|
|
26
26
|
#
|
|
27
27
|
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
|
|
28
28
|
# by Active Storage like so:
|
|
29
29
|
#
|
|
30
30
|
# <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
|
|
31
31
|
#
|
|
32
|
-
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::
|
|
33
|
-
# can then produce on-demand.
|
|
32
|
+
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
|
|
33
|
+
# or ActiveStorage::Representations::RedirectController can then produce on-demand.
|
|
34
34
|
#
|
|
35
35
|
# When you do want to actually produce the variant needed, call +processed+. This will check that the variant
|
|
36
36
|
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
|
|
@@ -74,11 +74,11 @@ class ActiveStorage::Variant
|
|
|
74
74
|
"variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
# Returns the URL of the blob variant on the service. See
|
|
77
|
+
# Returns the URL of the blob variant on the service. See ActiveStorage::Blob#url for details.
|
|
78
78
|
#
|
|
79
79
|
# 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
|
|
80
|
-
# for a variant that points to the ActiveStorage::
|
|
81
|
-
# for its redirection.
|
|
80
|
+
# for a variant that points to the ActiveStorage::Representations::ProxyController or ActiveStorage::Representations::RedirectController,
|
|
81
|
+
# which in turn will use this +service_call+ method for its redirection.
|
|
82
82
|
def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
|
|
83
83
|
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
|
|
84
84
|
end
|
|
@@ -121,7 +121,7 @@ module ActiveStorage
|
|
|
121
121
|
service_name = record.attachment_reflections[name].options[:service_name]
|
|
122
122
|
if service_name.is_a?(Proc)
|
|
123
123
|
service_name = service_name.call(record)
|
|
124
|
-
|
|
124
|
+
Attached::Model.validate_service_configuration(service_name, record.class, name)
|
|
125
125
|
end
|
|
126
126
|
service_name
|
|
127
127
|
end
|
|
@@ -61,18 +61,16 @@ module ActiveStorage
|
|
|
61
61
|
# There is no column defined on the model side, Active Storage takes
|
|
62
62
|
# care of the mapping between your records and the attachment.
|
|
63
63
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# User.with_attached_avatar
|
|
67
|
-
#
|
|
68
|
-
# Under the covers, this relationship is implemented as a +has_one+ association to a
|
|
69
|
-
# ActiveStorage::Attachment record and a +has_one-through+ association to a
|
|
64
|
+
# Under the covers, this relationship is implemented as a +has_one+ association to an
|
|
65
|
+
# ActiveStorage::Attachment record and a +has_one-through+ association to an
|
|
70
66
|
# ActiveStorage::Blob record. These associations are available as +avatar_attachment+
|
|
71
67
|
# and +avatar_blob+. But you shouldn't need to work with these associations directly in
|
|
72
68
|
# most circumstances.
|
|
73
69
|
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
70
|
+
# Instead, +has_one_attached+ generates an ActiveStorage::Attached::One proxy to
|
|
71
|
+
# provide access to the associations and factory methods, like +attach+:
|
|
72
|
+
#
|
|
73
|
+
# user.avatar.attach(uploaded_file)
|
|
76
74
|
#
|
|
77
75
|
# The +:dependent+ option defaults to +:purge_later+. This means the attachment will be
|
|
78
76
|
# purged (i.e. destroyed) in the background whenever the record is destroyed.
|
|
@@ -92,6 +90,10 @@ module ActiveStorage
|
|
|
92
90
|
# has_one_attached :avatar, service: ->(user) { user.in_europe_region? ? :s3_europe : :s3_usa }
|
|
93
91
|
# end
|
|
94
92
|
#
|
|
93
|
+
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
|
94
|
+
#
|
|
95
|
+
# User.with_attached_avatar
|
|
96
|
+
#
|
|
95
97
|
# If you need to enable +strict_loading+ to prevent lazy loading of attachment,
|
|
96
98
|
# pass the +:strict_loading+ option. You can do:
|
|
97
99
|
#
|
|
@@ -104,7 +106,7 @@ module ActiveStorage
|
|
|
104
106
|
# <tt>active_storage_attachments.record_type</tt> polymorphic type column of
|
|
105
107
|
# the corresponding rows.
|
|
106
108
|
def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
|
|
107
|
-
|
|
109
|
+
Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
|
|
108
110
|
|
|
109
111
|
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
110
112
|
# frozen_string_literal: true
|
|
@@ -161,18 +163,16 @@ module ActiveStorage
|
|
|
161
163
|
# There are no columns defined on the model side, Active Storage takes
|
|
162
164
|
# care of the mapping between your records and the attachments.
|
|
163
165
|
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# Gallery.where(user: Current.user).with_attached_photos
|
|
167
|
-
#
|
|
168
|
-
# Under the covers, this relationship is implemented as a +has_many+ association to a
|
|
169
|
-
# ActiveStorage::Attachment record and a +has_many-through+ association to a
|
|
166
|
+
# Under the covers, this relationship is implemented as a +has_many+ association to an
|
|
167
|
+
# ActiveStorage::Attachment record and a +has_many-through+ association to an
|
|
170
168
|
# ActiveStorage::Blob record. These associations are available as +photos_attachments+
|
|
171
169
|
# and +photos_blobs+. But you shouldn't need to work with these associations directly in
|
|
172
170
|
# most circumstances.
|
|
173
171
|
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
172
|
+
# Instead, +has_many_attached+ generates an ActiveStorage::Attached::Many proxy to
|
|
173
|
+
# provide access to the associations and factory methods, like +attach+:
|
|
174
|
+
#
|
|
175
|
+
# user.photos.attach(uploaded_file)
|
|
176
176
|
#
|
|
177
177
|
# The +:dependent+ option defaults to +:purge_later+. This means the attachments will be
|
|
178
178
|
# purged (i.e. destroyed) in the background whenever the record is destroyed.
|
|
@@ -192,6 +192,10 @@ module ActiveStorage
|
|
|
192
192
|
# has_many_attached :photos, service: ->(gallery) { gallery.personal? ? :personal_s3 : :s3 }
|
|
193
193
|
# end
|
|
194
194
|
#
|
|
195
|
+
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
|
196
|
+
#
|
|
197
|
+
# Gallery.where(user: Current.user).with_attached_photos
|
|
198
|
+
#
|
|
195
199
|
# If you need to enable +strict_loading+ to prevent lazy loading of attachments,
|
|
196
200
|
# pass the +:strict_loading+ option. You can do:
|
|
197
201
|
#
|
|
@@ -204,7 +208,7 @@ module ActiveStorage
|
|
|
204
208
|
# <tt>active_storage_attachments.record_type</tt> polymorphic type column of
|
|
205
209
|
# the corresponding rows.
|
|
206
210
|
def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
|
|
207
|
-
|
|
211
|
+
Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
|
|
208
212
|
|
|
209
213
|
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
210
214
|
# frozen_string_literal: true
|
|
@@ -255,6 +259,25 @@ module ActiveStorage
|
|
|
255
259
|
end
|
|
256
260
|
end
|
|
257
261
|
|
|
262
|
+
class << self
|
|
263
|
+
def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
|
|
264
|
+
if service_name
|
|
265
|
+
ActiveStorage::Blob.services.fetch(service_name) do
|
|
266
|
+
raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
|
|
267
|
+
end
|
|
268
|
+
else
|
|
269
|
+
validate_global_service_configuration(model_class)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
private
|
|
274
|
+
def validate_global_service_configuration(model_class)
|
|
275
|
+
if model_class.connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
|
|
276
|
+
raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
258
281
|
def attachment_changes # :nodoc:
|
|
259
282
|
@attachment_changes ||= {}
|
|
260
283
|
end
|
|
@@ -84,6 +84,10 @@ module ActiveStorage
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
initializer "active_storage.configs" do
|
|
87
|
+
config.before_initialize do |app|
|
|
88
|
+
ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
|
|
89
|
+
end
|
|
90
|
+
|
|
87
91
|
config.after_initialize do |app|
|
|
88
92
|
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
|
|
89
93
|
ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
|
|
@@ -112,7 +116,6 @@ module ActiveStorage
|
|
|
112
116
|
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
|
|
113
117
|
ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
|
|
114
118
|
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
|
|
115
|
-
ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
|
|
116
119
|
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
|
|
117
120
|
ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
|
|
118
121
|
ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
|
|
@@ -50,7 +50,7 @@ module ActiveStorage
|
|
|
50
50
|
# by ActiveSupport::Testing::FileFixtures.file_fixture, and upload
|
|
51
51
|
# the file to the Service
|
|
52
52
|
#
|
|
53
|
-
#
|
|
53
|
+
# ==== Examples
|
|
54
54
|
#
|
|
55
55
|
# # tests/fixtures/active_storage/blobs.yml
|
|
56
56
|
# second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
|
|
@@ -15,6 +15,13 @@ module ActiveStorage
|
|
|
15
15
|
attr_reader :client, :container, :signer
|
|
16
16
|
|
|
17
17
|
def initialize(storage_account_name:, storage_access_key:, container:, public: false, **options)
|
|
18
|
+
ActiveStorage.deprecator.warn <<~MSG.squish
|
|
19
|
+
`ActiveStorage::Service::AzureStorageService` is deprecated and will be
|
|
20
|
+
removed in Rails 8.1.
|
|
21
|
+
Please try the `azure-blob` gem instead.
|
|
22
|
+
This gem is not maintained by the Rails team, so please test your applications before deploying to production.
|
|
23
|
+
MSG
|
|
24
|
+
|
|
18
25
|
@client = Azure::Storage::Blob::BlobService.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key, **options)
|
|
19
26
|
@signer = Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
|
|
20
27
|
@container = container
|
|
@@ -30,6 +30,13 @@ module ActiveStorage
|
|
|
30
30
|
|
|
31
31
|
def initialize(primary:, mirrors:)
|
|
32
32
|
@primary, @mirrors = primary, mirrors
|
|
33
|
+
@executor = Concurrent::ThreadPoolExecutor.new(
|
|
34
|
+
min_threads: 1,
|
|
35
|
+
max_threads: mirrors.size,
|
|
36
|
+
max_queue: 0,
|
|
37
|
+
fallback_policy: :caller_runs,
|
|
38
|
+
idle_time: 60
|
|
39
|
+
)
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
# Upload the +io+ to the +key+ specified to all services. The upload to the primary service is done synchronously
|
|
@@ -75,10 +82,12 @@ module ActiveStorage
|
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
def perform_across_services(method, *args)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
tasks = each_service.collect do |service|
|
|
86
|
+
Concurrent::Promise.execute(executor: @executor) do
|
|
87
|
+
service.public_send method, *args
|
|
88
|
+
end
|
|
81
89
|
end
|
|
90
|
+
tasks.each(&:value!)
|
|
82
91
|
end
|
|
83
92
|
end
|
|
84
93
|
end
|
|
@@ -16,6 +16,7 @@ module ActiveStorage
|
|
|
16
16
|
|
|
17
17
|
def initialize(bucket:, upload: {}, public: false, **options)
|
|
18
18
|
@client = Aws::S3::Resource.new(**options)
|
|
19
|
+
@transfer_manager = Aws::S3::TransferManager.new(client: @client.client) if defined?(Aws::S3::TransferManager)
|
|
19
20
|
@bucket = @client.bucket(bucket)
|
|
20
21
|
|
|
21
22
|
@multipart_upload_threshold = upload.delete(:multipart_threshold) || 100.megabytes
|
|
@@ -100,7 +101,8 @@ module ActiveStorage
|
|
|
100
101
|
def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
|
|
101
102
|
content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
upload_stream(
|
|
105
|
+
key: destination_key,
|
|
104
106
|
content_type: content_type,
|
|
105
107
|
content_disposition: content_disposition,
|
|
106
108
|
part_size: MINIMUM_UPLOAD_PART_SIZE,
|
|
@@ -116,6 +118,14 @@ module ActiveStorage
|
|
|
116
118
|
end
|
|
117
119
|
|
|
118
120
|
private
|
|
121
|
+
def upload_stream(key:, **options, &block)
|
|
122
|
+
if @transfer_manager
|
|
123
|
+
@transfer_manager.upload_stream(key: key, bucket: bucket.name, **options, &block)
|
|
124
|
+
else
|
|
125
|
+
object_for(key).upload_stream(**options, &block)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
119
129
|
def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
|
|
120
130
|
object_for(key).presigned_url :get, expires_in: expires_in.to_i,
|
|
121
131
|
response_content_disposition: content_disposition_with(type: disposition, filename: filename),
|
|
@@ -126,7 +136,6 @@ module ActiveStorage
|
|
|
126
136
|
object_for(key).public_url(**client_opts)
|
|
127
137
|
end
|
|
128
138
|
|
|
129
|
-
|
|
130
139
|
MAXIMUM_UPLOAD_PARTS_COUNT = 10000
|
|
131
140
|
MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
|
|
132
141
|
|
|
@@ -139,12 +148,18 @@ module ActiveStorage
|
|
|
139
148
|
def upload_with_multipart(key, io, content_type: nil, content_disposition: nil, custom_metadata: {})
|
|
140
149
|
part_size = [ io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE ].max
|
|
141
150
|
|
|
142
|
-
|
|
151
|
+
upload_stream(
|
|
152
|
+
key: key,
|
|
153
|
+
content_type: content_type,
|
|
154
|
+
content_disposition: content_disposition,
|
|
155
|
+
part_size: part_size,
|
|
156
|
+
metadata: custom_metadata,
|
|
157
|
+
**upload_options
|
|
158
|
+
) do |out|
|
|
143
159
|
IO.copy_stream(io, out)
|
|
144
160
|
end
|
|
145
161
|
end
|
|
146
162
|
|
|
147
|
-
|
|
148
163
|
def object_for(key)
|
|
149
164
|
bucket.object(key)
|
|
150
165
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activestorage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 8.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -15,56 +15,56 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 8.0.3
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 8.0.3
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: actionpack
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 8.0.3
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: 8.0.3
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: activejob
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - '='
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: 8.0.3
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - '='
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: 8.0.3
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: activerecord
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - '='
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
60
|
+
version: 8.0.3
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - '='
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
67
|
+
version: 8.0.3
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: marcel
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -189,10 +189,10 @@ licenses:
|
|
|
189
189
|
- MIT
|
|
190
190
|
metadata:
|
|
191
191
|
bug_tracker_uri: https://github.com/rails/rails/issues
|
|
192
|
-
changelog_uri: https://github.com/rails/rails/blob/
|
|
193
|
-
documentation_uri: https://api.rubyonrails.org/
|
|
192
|
+
changelog_uri: https://github.com/rails/rails/blob/v8.0.3/activestorage/CHANGELOG.md
|
|
193
|
+
documentation_uri: https://api.rubyonrails.org/v8.0.3/
|
|
194
194
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
|
195
|
-
source_code_uri: https://github.com/rails/rails/tree/
|
|
195
|
+
source_code_uri: https://github.com/rails/rails/tree/v8.0.3/activestorage
|
|
196
196
|
rubygems_mfa_required: 'true'
|
|
197
197
|
rdoc_options: []
|
|
198
198
|
require_paths:
|
|
@@ -201,7 +201,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
201
201
|
requirements:
|
|
202
202
|
- - ">="
|
|
203
203
|
- !ruby/object:Gem::Version
|
|
204
|
-
version: 3.
|
|
204
|
+
version: 3.2.0
|
|
205
205
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
206
|
requirements:
|
|
207
207
|
- - ">="
|