activestorage 7.2.3 → 8.0.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.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -120
- 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 +3 -69
- data/app/models/active_storage/blob.rb +19 -4
- data/app/models/active_storage/filename.rb +1 -1
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/model.rb +18 -33
- data/lib/active_storage/engine.rb +1 -4
- 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 +4 -19
- data/lib/active_storage.rb +3 -0
- metadata +18 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90eb90580cb0faea4b29eef5ddf0dbc99dd1087158980f968561fe45c2dcb4d4
|
|
4
|
+
data.tar.gz: bdd0ced8684956539dee4f32ea7eacd16744c72e318aebc0bd815adfa72ffe73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46465038ab95d65913a9f359af9b21fcb5b3ee9a37877b32eeabd7943de6373625d7ae51a9383099a89bd7d949ee6b5db37c5518656313121d8991b4e52e7667
|
|
7
|
+
data.tar.gz: 4f7590242bb707d2ed0c9b22871f2e00d28f683224341387c739b9771a85ab18e8655e1a913b2bdabb724bb694cb6b997f4b1c2771fa52a2b96ac1882593471a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,131 +1,23 @@
|
|
|
1
|
-
## Rails
|
|
1
|
+
## Rails 8.0.0.beta1 (September 26, 2024) ##
|
|
2
2
|
|
|
3
|
-
*
|
|
3
|
+
* Deprecate `ActiveStorage::Service::AzureStorageService`.
|
|
4
4
|
|
|
5
|
-
*
|
|
5
|
+
*zzak*
|
|
6
6
|
|
|
7
|
-
*
|
|
7
|
+
* Improve `ActiveStorage::Filename#sanitized` method to handle special characters more effectively.
|
|
8
|
+
Replace the characters `"*?<>` with `-` if they exist in the Filename to match the Filename convention of Win OS.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
its dirty attributes reset, preventing your `after commit` callbacks
|
|
11
|
-
on that record to behave as expected.
|
|
10
|
+
*Luong Viet Dung(Martin)*
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
and is supposed to be internal. Active Storage Attachment will continue
|
|
15
|
-
to be autosaved (through a different relation).
|
|
12
|
+
* Improve InvariableError, UnpreviewableError and UnrepresentableError message.
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
Include Blob ID and content_type in the messages.
|
|
18
15
|
|
|
16
|
+
*Petrik de Heus*
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
* Mark proxied files as `immutable` in their Cache-Control header
|
|
21
19
|
|
|
22
|
-
*
|
|
20
|
+
*Nate Matykiewicz*
|
|
23
21
|
|
|
24
|
-
[CVE-2025-24293]
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Rails 7.2.2.1 (December 10, 2024) ##
|
|
30
|
-
|
|
31
|
-
* No changes.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## Rails 7.2.2 (October 30, 2024) ##
|
|
35
|
-
|
|
36
|
-
* No changes.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## Rails 7.2.1.2 (October 23, 2024) ##
|
|
40
|
-
|
|
41
|
-
* No changes.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Rails 7.2.1.1 (October 15, 2024) ##
|
|
45
|
-
|
|
46
|
-
* No changes.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
## Rails 7.2.1 (August 22, 2024) ##
|
|
50
|
-
|
|
51
|
-
* No changes.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## Rails 7.2.0 (August 09, 2024) ##
|
|
55
|
-
|
|
56
|
-
* Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
|
|
57
|
-
|
|
58
|
-
*Rafael Mendonça França*
|
|
59
|
-
|
|
60
|
-
* Remove deprecated `config.active_storage.replace_on_assign_to_many`.
|
|
61
|
-
|
|
62
|
-
*Rafael Mendonça França*
|
|
63
|
-
|
|
64
|
-
* Add support for custom `key` in `ActiveStorage::Blob#compose`.
|
|
65
|
-
|
|
66
|
-
*Elvin Efendiev*
|
|
67
|
-
|
|
68
|
-
* Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
|
|
69
|
-
is set.
|
|
70
|
-
|
|
71
|
-
*Lewis Buckley*
|
|
72
|
-
|
|
73
|
-
* Fix JSON-encoding of `ActiveStorage::Filename` instances.
|
|
74
|
-
|
|
75
|
-
*Jonathan del Strother*
|
|
76
|
-
|
|
77
|
-
* Fix N+1 query when fetching preview images for non-image assets.
|
|
78
|
-
|
|
79
|
-
*Aaron Patterson & Justin Searls*
|
|
80
|
-
|
|
81
|
-
* Fix all Active Storage database related models to respect
|
|
82
|
-
`ActiveRecord::Base.table_name_prefix` configuration.
|
|
83
|
-
|
|
84
|
-
*Chedli Bourguiba*
|
|
85
|
-
|
|
86
|
-
* Fix `ActiveStorage::Representations::ProxyController` not returning the proper
|
|
87
|
-
preview image variant for previewable files.
|
|
88
|
-
|
|
89
|
-
*Chedli Bourguiba*
|
|
90
|
-
|
|
91
|
-
* Fix `ActiveStorage::Representations::ProxyController` to proxy untracked
|
|
92
|
-
variants.
|
|
93
|
-
|
|
94
|
-
*Chedli Bourguiba*
|
|
95
|
-
|
|
96
|
-
* When using the `preprocessed: true` option, avoid enqueuing transform jobs
|
|
97
|
-
for blobs that are not representable.
|
|
98
|
-
|
|
99
|
-
*Chedli Bourguiba*
|
|
100
|
-
|
|
101
|
-
* Prevent `ActiveStorage::Blob#preview` to generate a variant if an empty variation is passed.
|
|
102
|
-
|
|
103
|
-
Calls to `#url`, `#key` or `#download` will now use the original preview
|
|
104
|
-
image instead of generating a variant with the exact same dimensions.
|
|
105
|
-
|
|
106
|
-
*Chedli Bourguiba*
|
|
107
|
-
|
|
108
|
-
* Process preview image variant when calling `ActiveStorage::Preview#processed`.
|
|
109
|
-
|
|
110
|
-
For example, `attached_pdf.preview(:thumb).processed` will now immediately
|
|
111
|
-
generate the full-sized preview image and the `:thumb` variant of it.
|
|
112
|
-
Previously, the `:thumb` variant would not be generated until a further call
|
|
113
|
-
to e.g. `processed.url`.
|
|
114
|
-
|
|
115
|
-
*Chedli Bourguiba* and *Jonathan Hefner*
|
|
116
|
-
|
|
117
|
-
* Prevent `ActiveRecord::StrictLoadingViolationError` when strict loading is
|
|
118
|
-
enabled and the variant of an Active Storage preview has already been
|
|
119
|
-
processed (for example, by calling `ActiveStorage::Preview#url`).
|
|
120
|
-
|
|
121
|
-
*Jonathan Hefner*
|
|
122
|
-
|
|
123
|
-
* Fix `preprocessed: true` option for named variants of previewable files.
|
|
124
|
-
|
|
125
|
-
*Nico Wenterodt*
|
|
126
|
-
|
|
127
|
-
* Allow accepting `service` as a proc as well in `has_one_attached` and `has_many_attached`.
|
|
128
|
-
|
|
129
|
-
*Yogesh Khater*
|
|
130
|
-
|
|
131
|
-
Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md) for previous changes.
|
|
23
|
+
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 rails-core mailing list 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 :unprocessable_entity
|
|
29
29
|
end
|
|
30
30
|
else
|
|
31
31
|
head :not_found
|
|
32
32
|
end
|
|
33
33
|
rescue ActiveStorage::IntegrityError
|
|
34
|
-
head
|
|
34
|
+
head :unprocessable_entity
|
|
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 commited.
|
|
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) {
|
|
@@ -31,77 +31,11 @@ module ActiveStorage::Blob::Representable
|
|
|
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
|
-
#
|
|
100
34
|
def variant(transformations)
|
|
101
35
|
if variable?
|
|
102
36
|
variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
|
|
103
37
|
else
|
|
104
|
-
raise ActiveStorage::InvariableError
|
|
38
|
+
raise ActiveStorage::InvariableError, "Can't transform blob with ID=#{id} and content_type=#{content_type}"
|
|
105
39
|
end
|
|
106
40
|
end
|
|
107
41
|
|
|
@@ -130,7 +64,7 @@ module ActiveStorage::Blob::Representable
|
|
|
130
64
|
if previewable?
|
|
131
65
|
ActiveStorage::Preview.new(self, transformations)
|
|
132
66
|
else
|
|
133
|
-
raise ActiveStorage::UnpreviewableError
|
|
67
|
+
raise ActiveStorage::UnpreviewableError, "No previewer found for blob with ID=#{id} and content_type=#{content_type}"
|
|
134
68
|
end
|
|
135
69
|
end
|
|
136
70
|
|
|
@@ -155,7 +89,7 @@ module ActiveStorage::Blob::Representable
|
|
|
155
89
|
when variable?
|
|
156
90
|
variant transformations
|
|
157
91
|
else
|
|
158
|
-
raise ActiveStorage::UnrepresentableError
|
|
92
|
+
raise ActiveStorage::UnrepresentableError, "No previewer found and can't transform blob with ID=#{id} and content_type=#{content_type}"
|
|
159
93
|
end
|
|
160
94
|
end
|
|
161
95
|
|
|
@@ -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
|
|
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,6 +151,22 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|
|
152
151
|
combined_blob.save!
|
|
153
152
|
end
|
|
154
153
|
end
|
|
154
|
+
|
|
155
|
+
def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
|
|
156
|
+
if service_name
|
|
157
|
+
services.fetch(service_name) do
|
|
158
|
+
raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
validate_global_service_configuration
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def validate_global_service_configuration # :nodoc:
|
|
166
|
+
if connected? && table_exists? && Rails.configuration.active_storage.service.nil?
|
|
167
|
+
raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
155
170
|
end
|
|
156
171
|
|
|
157
172
|
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.
|
|
@@ -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
|
+
ActiveStorage::Blob.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
|
-
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
|
65
|
-
#
|
|
66
|
-
# User.with_attached_avatar
|
|
67
|
-
#
|
|
68
64
|
# Under the covers, this relationship is implemented as a +has_one+ association to a
|
|
69
65
|
# ActiveStorage::Attachment record and a +has_one-through+ association to a
|
|
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
|
+
ActiveStorage::Blob.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
|
-
# To avoid N+1 queries, you can include the attached blobs in your query like so:
|
|
165
|
-
#
|
|
166
|
-
# Gallery.where(user: Current.user).with_attached_photos
|
|
167
|
-
#
|
|
168
166
|
# Under the covers, this relationship is implemented as a +has_many+ association to a
|
|
169
167
|
# ActiveStorage::Attachment record and a +has_many-through+ association to a
|
|
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
|
+
ActiveStorage::Blob.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,25 +259,6 @@ module ActiveStorage
|
|
|
255
259
|
end
|
|
256
260
|
end
|
|
257
261
|
|
|
258
|
-
class << self
|
|
259
|
-
def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
|
|
260
|
-
if service_name
|
|
261
|
-
ActiveStorage::Blob.services.fetch(service_name) do
|
|
262
|
-
raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
|
|
263
|
-
end
|
|
264
|
-
else
|
|
265
|
-
validate_global_service_configuration(model_class)
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
private
|
|
270
|
-
def validate_global_service_configuration(model_class)
|
|
271
|
-
if model_class.connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
|
|
272
|
-
raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
262
|
def attachment_changes # :nodoc:
|
|
278
263
|
@attachment_changes ||= {}
|
|
279
264
|
end
|
|
@@ -84,10 +84,6 @@ 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
|
-
|
|
91
87
|
config.after_initialize do |app|
|
|
92
88
|
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
|
|
93
89
|
ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
|
|
@@ -116,6 +112,7 @@ module ActiveStorage
|
|
|
116
112
|
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
|
|
117
113
|
ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
|
|
118
114
|
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
|
|
119
116
|
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
|
|
120
117
|
ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
|
|
121
118
|
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,7 +16,6 @@ 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)
|
|
20
19
|
@bucket = @client.bucket(bucket)
|
|
21
20
|
|
|
22
21
|
@multipart_upload_threshold = upload.delete(:multipart_threshold) || 100.megabytes
|
|
@@ -101,8 +100,7 @@ module ActiveStorage
|
|
|
101
100
|
def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
|
|
102
101
|
content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
|
|
103
102
|
|
|
104
|
-
upload_stream(
|
|
105
|
-
key: destination_key,
|
|
103
|
+
object_for(destination_key).upload_stream(
|
|
106
104
|
content_type: content_type,
|
|
107
105
|
content_disposition: content_disposition,
|
|
108
106
|
part_size: MINIMUM_UPLOAD_PART_SIZE,
|
|
@@ -118,14 +116,6 @@ module ActiveStorage
|
|
|
118
116
|
end
|
|
119
117
|
|
|
120
118
|
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
|
-
|
|
129
119
|
def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
|
|
130
120
|
object_for(key).presigned_url :get, expires_in: expires_in.to_i,
|
|
131
121
|
response_content_disposition: content_disposition_with(type: disposition, filename: filename),
|
|
@@ -136,6 +126,7 @@ module ActiveStorage
|
|
|
136
126
|
object_for(key).public_url(**client_opts)
|
|
137
127
|
end
|
|
138
128
|
|
|
129
|
+
|
|
139
130
|
MAXIMUM_UPLOAD_PARTS_COUNT = 10000
|
|
140
131
|
MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
|
|
141
132
|
|
|
@@ -148,18 +139,12 @@ module ActiveStorage
|
|
|
148
139
|
def upload_with_multipart(key, io, content_type: nil, content_disposition: nil, custom_metadata: {})
|
|
149
140
|
part_size = [ io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE ].max
|
|
150
141
|
|
|
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|
|
|
142
|
+
object_for(key).upload_stream(content_type: content_type, content_disposition: content_disposition, part_size: part_size, metadata: custom_metadata, **upload_options) do |out|
|
|
159
143
|
IO.copy_stream(io, out)
|
|
160
144
|
end
|
|
161
145
|
end
|
|
162
146
|
|
|
147
|
+
|
|
163
148
|
def object_for(key)
|
|
164
149
|
bucket.object(key)
|
|
165
150
|
end
|
data/lib/active_storage.rb
CHANGED
|
@@ -72,6 +72,7 @@ module ActiveStorage
|
|
|
72
72
|
"annotate",
|
|
73
73
|
"antialias",
|
|
74
74
|
"append",
|
|
75
|
+
"apply",
|
|
75
76
|
"attenuate",
|
|
76
77
|
"authenticate",
|
|
77
78
|
"auto_gamma",
|
|
@@ -212,6 +213,7 @@ module ActiveStorage
|
|
|
212
213
|
"linewidth",
|
|
213
214
|
"liquid_rescale",
|
|
214
215
|
"list",
|
|
216
|
+
"loader",
|
|
215
217
|
"log",
|
|
216
218
|
"loop",
|
|
217
219
|
"lowlight_color",
|
|
@@ -274,6 +276,7 @@ module ActiveStorage
|
|
|
274
276
|
"rotate",
|
|
275
277
|
"sample",
|
|
276
278
|
"sampling_factor",
|
|
279
|
+
"saver",
|
|
277
280
|
"scale",
|
|
278
281
|
"scene",
|
|
279
282
|
"screen",
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activestorage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 8.0.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2024-09-26 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: activesupport
|
|
@@ -15,56 +16,56 @@ dependencies:
|
|
|
15
16
|
requirements:
|
|
16
17
|
- - '='
|
|
17
18
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
19
|
+
version: 8.0.0.beta1
|
|
19
20
|
type: :runtime
|
|
20
21
|
prerelease: false
|
|
21
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
23
|
requirements:
|
|
23
24
|
- - '='
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
26
|
+
version: 8.0.0.beta1
|
|
26
27
|
- !ruby/object:Gem::Dependency
|
|
27
28
|
name: actionpack
|
|
28
29
|
requirement: !ruby/object:Gem::Requirement
|
|
29
30
|
requirements:
|
|
30
31
|
- - '='
|
|
31
32
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
33
|
+
version: 8.0.0.beta1
|
|
33
34
|
type: :runtime
|
|
34
35
|
prerelease: false
|
|
35
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
37
|
requirements:
|
|
37
38
|
- - '='
|
|
38
39
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
40
|
+
version: 8.0.0.beta1
|
|
40
41
|
- !ruby/object:Gem::Dependency
|
|
41
42
|
name: activejob
|
|
42
43
|
requirement: !ruby/object:Gem::Requirement
|
|
43
44
|
requirements:
|
|
44
45
|
- - '='
|
|
45
46
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
47
|
+
version: 8.0.0.beta1
|
|
47
48
|
type: :runtime
|
|
48
49
|
prerelease: false
|
|
49
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
51
|
requirements:
|
|
51
52
|
- - '='
|
|
52
53
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
54
|
+
version: 8.0.0.beta1
|
|
54
55
|
- !ruby/object:Gem::Dependency
|
|
55
56
|
name: activerecord
|
|
56
57
|
requirement: !ruby/object:Gem::Requirement
|
|
57
58
|
requirements:
|
|
58
59
|
- - '='
|
|
59
60
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
61
|
+
version: 8.0.0.beta1
|
|
61
62
|
type: :runtime
|
|
62
63
|
prerelease: false
|
|
63
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
65
|
requirements:
|
|
65
66
|
- - '='
|
|
66
67
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
68
|
+
version: 8.0.0.beta1
|
|
68
69
|
- !ruby/object:Gem::Dependency
|
|
69
70
|
name: marcel
|
|
70
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -189,11 +190,12 @@ licenses:
|
|
|
189
190
|
- MIT
|
|
190
191
|
metadata:
|
|
191
192
|
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/
|
|
193
|
+
changelog_uri: https://github.com/rails/rails/blob/v8.0.0.beta1/activestorage/CHANGELOG.md
|
|
194
|
+
documentation_uri: https://api.rubyonrails.org/v8.0.0.beta1/
|
|
194
195
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
|
195
|
-
source_code_uri: https://github.com/rails/rails/tree/
|
|
196
|
+
source_code_uri: https://github.com/rails/rails/tree/v8.0.0.beta1/activestorage
|
|
196
197
|
rubygems_mfa_required: 'true'
|
|
198
|
+
post_install_message:
|
|
197
199
|
rdoc_options: []
|
|
198
200
|
require_paths:
|
|
199
201
|
- lib
|
|
@@ -201,14 +203,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
201
203
|
requirements:
|
|
202
204
|
- - ">="
|
|
203
205
|
- !ruby/object:Gem::Version
|
|
204
|
-
version: 3.
|
|
206
|
+
version: 3.2.0
|
|
205
207
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
208
|
requirements:
|
|
207
209
|
- - ">="
|
|
208
210
|
- !ruby/object:Gem::Version
|
|
209
211
|
version: '0'
|
|
210
212
|
requirements: []
|
|
211
|
-
rubygems_version: 3.
|
|
213
|
+
rubygems_version: 3.5.16
|
|
214
|
+
signing_key:
|
|
212
215
|
specification_version: 4
|
|
213
216
|
summary: Local and cloud file storage framework.
|
|
214
217
|
test_files: []
|