activestorage 7.1.5.2 → 8.1.2.1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -295
  3. data/README.md +8 -5
  4. data/app/assets/javascripts/activestorage.esm.js +38 -2
  5. data/app/assets/javascripts/activestorage.js +38 -1
  6. data/app/controllers/active_storage/direct_uploads_controller.rb +1 -1
  7. data/app/controllers/active_storage/disk_controller.rb +6 -2
  8. data/app/controllers/concerns/active_storage/streaming.rb +17 -1
  9. data/app/javascript/activestorage/direct_upload_controller.js +48 -1
  10. data/app/javascript/activestorage/index.js +2 -1
  11. data/app/models/active_storage/attachment.rb +4 -4
  12. data/app/models/active_storage/blob/representable.rb +71 -5
  13. data/app/models/active_storage/blob.rb +43 -16
  14. data/app/models/active_storage/filename.rb +2 -5
  15. data/app/models/active_storage/preview.rb +14 -6
  16. data/app/models/active_storage/variant.rb +12 -12
  17. data/app/models/active_storage/variation.rb +1 -1
  18. data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
  19. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +11 -4
  20. data/lib/active_storage/analyzer/image_analyzer/vips.rb +28 -14
  21. data/lib/active_storage/analyzer/image_analyzer.rb +5 -0
  22. data/lib/active_storage/analyzer/video_analyzer.rb +6 -2
  23. data/lib/active_storage/attached/changes/create_one.rb +6 -1
  24. data/lib/active_storage/attached/changes/create_one_of_many.rb +5 -1
  25. data/lib/active_storage/attached/model.rb +56 -30
  26. data/lib/active_storage/attached.rb +0 -1
  27. data/lib/active_storage/engine.rb +40 -15
  28. data/lib/active_storage/errors.rb +4 -0
  29. data/lib/active_storage/fixture_set.rb +1 -1
  30. data/lib/active_storage/gem_version.rb +3 -3
  31. data/lib/active_storage/log_subscriber.rb +1 -1
  32. data/lib/active_storage/previewer/mupdf_previewer.rb +6 -2
  33. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +6 -2
  34. data/lib/active_storage/previewer/video_previewer.rb +1 -1
  35. data/lib/active_storage/service/configurator.rb +6 -0
  36. data/lib/active_storage/service/disk_service.rb +46 -2
  37. data/lib/active_storage/service/gcs_service.rb +15 -5
  38. data/lib/active_storage/service/mirror_service.rb +13 -4
  39. data/lib/active_storage/service/registry.rb +6 -0
  40. data/lib/active_storage/service/s3_service.rb +19 -4
  41. data/lib/active_storage/service.rb +5 -1
  42. data/lib/active_storage/structured_event_subscriber.rb +79 -0
  43. data/lib/active_storage/transformers/image_magick.rb +72 -0
  44. data/lib/active_storage/transformers/image_processing_transformer.rb +6 -68
  45. data/lib/active_storage/transformers/null_transformer.rb +12 -0
  46. data/lib/active_storage/transformers/vips.rb +11 -0
  47. data/lib/active_storage.rb +18 -14
  48. metadata +18 -15
  49. data/lib/active_storage/service/azure_storage_service.rb +0 -194
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e2fdc79c02f31ae87546d38d509d76e6745aa1613b53d3242036eaede2df98c
4
- data.tar.gz: db43b46d975c7794b7dcc9948899b4c1cdd1f4d8002266e6bd9078fee560732f
3
+ metadata.gz: 95a8a27a89ab0f4be63e4f466fcf5a7eedf15ed806d7b061257ab56aafadc1a6
4
+ data.tar.gz: 4f7046ccc16df5fb3bd06080e914fb54c0ad2009d5dbcb1080dcab7bf945f1e3
5
5
  SHA512:
6
- metadata.gz: 93ff6753974b1f2ce12bb81c867c5ae0432de55dcb90db8e768c9911152651f48594c312f321c5e8e5971be826556f8338e20b80a07fd736a54884b0af2f790c
7
- data.tar.gz: fa516053c9d81e0c6ac8003e5aeaee1be1e62c16e32c9ae0ec2fbcae41ebb255c3e49e780c5b1b58ca57049ee3ed421ecc08eb822d42ba58fb30e86d6c1ebdaf
6
+ metadata.gz: f187b18f73b712921f1dc2252fe5a645ce83b84dad1dc97d6b24be9f136a634e2b0da3290d2a8f9284ae6134bfc8b62fc8e7a7b9f4974b9f3dee6a6ed0af17aa
7
+ data.tar.gz: 24b2da33d24f7f2d34623482b2a2c818e3d87d7dfa8a165e1ff6091134548e37b8dc9eb3908a69231d3e8e9fa3320f75db1a0dabcb54b048e4e543319ec2f555
data/CHANGELOG.md CHANGED
@@ -1,366 +1,167 @@
1
- ## Rails 7.1.5.2 (August 13, 2025) ##
1
+ ## Rails 8.1.2.1 (March 23, 2026) ##
2
2
 
3
- Remove dangerous transformations
3
+ * Filter user supplied metadata in DirectUploadController
4
4
 
5
- [CVE-2025-24293]
5
+ [CVE-2026-33173]
6
6
 
7
- *Zack Deveau*
7
+ *Jean Boussier*
8
8
 
9
- ## Rails 7.1.5.1 (December 10, 2024) ##
10
-
11
- * No changes.
12
-
13
-
14
- ## Rails 7.1.5 (October 30, 2024) ##
15
-
16
- * No changes.
17
-
18
-
19
- ## Rails 7.1.4.2 (October 23, 2024) ##
20
-
21
- * No changes.
22
-
23
-
24
- ## Rails 7.1.4.1 (October 15, 2024) ##
25
-
26
- * No changes.
9
+ * Configurable maxmimum streaming chunk size
27
10
 
11
+ Makes sure that byte ranges for blobs don't exceed 100mb by default.
12
+ Content ranges that are too big can result in denial of service.
28
13
 
29
- ## Rails 7.1.4 (August 22, 2024) ##
14
+ [CVE-2026-33174]
30
15
 
31
- * Fixes race condition for multiple preprocessed video variants.
16
+ *Gannon McGibbon*
32
17
 
33
- *Justin Searls*
18
+ * Limit range requests to a single range
34
19
 
20
+ [CVE-2026-33658]
35
21
 
36
- ## Rails 7.1.3.4 (June 04, 2024) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 7.1.3.3 (May 16, 2024) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 7.1.3.2 (February 21, 2024) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 7.1.3.1 (February 21, 2024) ##
52
-
53
- * No changes.
22
+ *Jean Boussier*
54
23
 
55
24
 
56
- ## Rails 7.1.3 (January 16, 2024) ##
25
+ * Prevent path traversal in `DiskService`.
57
26
 
58
- * Fix N+1 query when fetching preview images for non-image assets.
27
+ `DiskService#path_for` now raises an `InvalidKeyError` when passed keys with dot segments (".",
28
+ ".."), or if the resolved path is outside the storage root directory.
59
29
 
60
- *Aaron Patterson & Justin Searls*
30
+ `#path_for` also now consistently raises `InvalidKeyError` if the key is invalid in any way, for
31
+ example containing null bytes or having an incompatible encoding. Previously, the exception
32
+ raised may have been `ArgumentError` or `Encoding::CompatibilityError`.
61
33
 
62
- * Fix all Active Storage database related models to respect
63
- `ActiveRecord::Base.table_name_prefix` configuration.
34
+ `DiskController` now explicitly rescues `InvalidKeyError` with appropriate HTTP status codes.
64
35
 
65
- *Chedli Bourguiba*
36
+ [CVE-2026-33195]
66
37
 
67
- * Fix `ActiveStorage::Representations::ProxyController` not returning the proper
68
- preview image variant for previewable files.
38
+ *Mike Dalessio*
69
39
 
70
- *Chedli Bourguiba*
40
+ * Prevent glob injection in `DiskService#delete_prefixed`.
71
41
 
72
- * Fix `ActiveStorage::Representations::ProxyController` to proxy untracked
73
- variants.
42
+ Escape glob metacharacters in the resolved path before passing to `Dir.glob`.
74
43
 
75
- *Chedli Bourguiba*
44
+ Note that this change breaks any existing code that is relying on `delete_prefixed` to expand
45
+ glob metacharacters. This change presumes that is unintended behavior (as other storage services
46
+ do not respect these metacharacters).
76
47
 
77
- * Fix direct upload forms when submit button contains nested elements.
48
+ [CVE-2026-33202]
78
49
 
79
- *Marc Köhlbrugge*
50
+ *Mike Dalessio*
80
51
 
81
- * When using the `preprocessed: true` option, avoid enqueuing transform jobs
82
- for blobs that are not representable.
83
52
 
84
- *Chedli Bourguiba*
85
-
86
- * Process preview image variant when calling `ActiveStorage::Preview#processed`.
87
- For example, `attached_pdf.preview(:thumb).processed` will now immediately
88
- generate the full-sized preview image and the `:thumb` variant of it.
89
- Previously, the `:thumb` variant would not be generated until a further call
90
- to e.g. `processed.url`.
91
-
92
- *Chedli Bourguiba* and *Jonathan Hefner*
93
-
94
- * Prevent `ActiveRecord::StrictLoadingViolationError` when strict loading is
95
- enabled and the variant of an Active Storage preview has already been
96
- processed (for example, by calling `ActiveStorage::Preview#url`).
97
-
98
- *Jonathan Hefner*
99
-
100
- * Fix `preprocessed: true` option for named variants of previewable files.
101
-
102
- *Nico Wenterodt*
103
-
104
-
105
- ## Rails 7.1.2 (November 10, 2023) ##
106
-
107
- * No changes.
108
-
109
-
110
- ## Rails 7.1.1 (October 11, 2023) ##
111
-
112
- * No changes.
113
-
114
-
115
- ## Rails 7.1.0 (October 05, 2023) ##
116
-
117
- * No changes.
118
-
119
-
120
- ## Rails 7.1.0.rc2 (October 01, 2023) ##
121
-
122
- * No changes.
123
-
124
-
125
- ## Rails 7.1.0.rc1 (September 27, 2023) ##
126
-
127
- * Add `expires_at` option to `ActiveStorage::Blob#signed_id`.
128
-
129
- ```ruby
130
- rails_blob_path(user.avatar, disposition: "attachment", expires_at: 30.minutes.from_now)
131
- <%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 30.minutes.from_now) %>
132
- ```
53
+ ## Rails 8.1.2 (January 08, 2026) ##
133
54
 
134
- *Aki*
55
+ * Restore ADC when signing URLs with IAM for GCS
135
56
 
136
- * Allow attaching File and Pathname when assigning attributes, e.g.
57
+ ADC was previously used for automatic authorization when signing URLs with IAM.
58
+ Now it is again, but the auth client is memoized so that new credentials are only
59
+ requested when the current ones expire. Other auth methods can now be used
60
+ instead by setting the authorization on `ActiveStorage::Service::GCSService#iam_client`.
137
61
 
138
62
  ```ruby
139
- User.create!(avatar: File.open("image.jpg"))
140
- User.create!(avatar: file_fixture("image.jpg"))
63
+ ActiveStorage::Blob.service.iam_client.authorization = Google::Auth::ImpersonatedServiceAccountCredentials.new(options)
141
64
  ```
142
65
 
143
- *Dorian Marié*
144
-
145
-
146
- ## Rails 7.1.0.beta1 (September 13, 2023) ##
147
-
148
- * Disables the session in `ActiveStorage::Blobs::ProxyController`
149
- and `ActiveStorage::Representations::ProxyController`
150
- in order to allow caching by default in some CDNs as CloudFlare
151
-
152
- Fixes #44136
153
-
154
- *Bruno Prieto*
155
-
156
- * Add `tags` to `ActiveStorage::Analyzer::AudioAnalyzer` output
66
+ This is safer than setting `Google::Apis::RequestOptions.default.authorization`
67
+ because it only applies to Active Storage and does not affect other Google API
68
+ clients.
157
69
 
158
- *Keaton Roux*
70
+ *Justin Malčić*
159
71
 
160
- * Add an option to preprocess variants
161
72
 
162
- ActiveStorage variants are processed on the fly when they are needed but
163
- sometimes we're sure that they are accessed and want to processed them
164
- upfront.
73
+ ## Rails 8.1.1 (October 28, 2025) ##
165
74
 
166
- `preprocessed` option is added when declaring variants.
167
-
168
- ```
169
- class User < ApplicationRecord
170
- has_one_attached :avatar do |attachable|
171
- attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
172
- end
173
- end
174
- ```
175
-
176
- *Shouichi Kamiya*
177
-
178
- * Fix variants not included when eager loading multiple records containing a single attachment
75
+ * No changes.
179
76
 
180
- When using the `with_attached_#{name}` scope for a `has_one_attached` relation,
181
- attachment variants were not eagerly loaded.
182
77
 
183
- *Russell Porter*
78
+ ## Rails 8.1.0 (October 22, 2025) ##
184
79
 
185
- * Allow an ActiveStorage attachment to be removed via a form post
80
+ * Add structured events for Active Storage:
81
+ - `active_storage.service_upload`
82
+ - `active_storage.service_download`
83
+ - `active_storage.service_streaming_download`
84
+ - `active_storage.preview`
85
+ - `active_storage.service_delete`
86
+ - `active_storage.service_delete_prefixed`
87
+ - `active_storage.service_exist`
88
+ - `active_storage.service_url`
89
+ - `active_storage.service_mirror`
186
90
 
187
- Attachments can already be removed by updating the attachment to be nil such as:
188
- ```ruby
189
- User.find(params[:id]).update!(avatar: nil)
190
- ```
91
+ *Gannon McGibbon*
191
92
 
192
- However, a form cannot post a nil param, it can only post an empty string. But, posting an
193
- empty string would result in an `ActiveSupport::MessageVerifier::InvalidSignature: mismatched digest`
194
- error being raised, because it's being treated as a signed blob id.
93
+ * Allow analyzers and variant transformer to be fully configurable
195
94
 
196
- Now, nil and an empty string are treated as a delete, which allows attachments to be removed via:
197
95
  ```ruby
198
- User.find(params[:id]).update!(params.require(:user).permit(:avatar))
96
+ # ActiveStorage.analyzers can be set to an empty array:
97
+ config.active_storage.analyzers = []
98
+ # => ActiveStorage.analyzers = []
199
99
 
100
+ # or use custom analyzer:
101
+ config.active_storage.analyzers = [ CustomAnalyzer ]
102
+ # => ActiveStorage.analyzers = [ CustomAnalyzer ]
200
103
  ```
201
104
 
202
- *Nate Matykiewicz*
203
-
204
- * Remove mini_mime usage in favour of marcel.
205
-
206
- We have two libraries that are have similar usage. This change removes
207
- dependency on mini_mime and makes use of similar methods from marcel.
208
-
209
- *Vipul A M*
105
+ If no configuration is provided, it will use the default analyzers.
210
106
 
211
- * Allow destroying active storage variants
107
+ You can also disable variant processor to remove warnings on startup about missing gems.
212
108
 
213
109
  ```ruby
214
- User.first.avatar.variant(resize_to_limit: [100, 100]).destroy
110
+ config.active_storage.variant_processor = :disabled
215
111
  ```
216
112
 
217
- *Shouichi Kamiya*, *Yuichiro NAKAGAWA*, *Ryohei UEDA*
218
-
219
- * Add `sample_rate` to `ActiveStorage::Analyzer::AudioAnalyzer` output
220
-
221
- *Matija Čupić*
222
-
223
- * Remove deprecated `purge` and `purge_later` methods from the attachments association.
113
+ *zzak*, *Alexandre Ruban*
224
114
 
225
- *Rafael Mendonça França*
226
-
227
- * Remove deprecated behavior when assigning to a collection of attachments.
228
-
229
- Instead of appending to the collection, the collection is now replaced.
115
+ * Remove deprecated `:azure` storage service.
230
116
 
231
117
  *Rafael Mendonça França*
232
118
 
233
- * Remove deprecated `ActiveStorage::Current#host` and `ActiveStorage::Current#host=` methods.
119
+ * Remove unnecessary calls to the GCP metadata server.
234
120
 
235
- *Rafael Mendonça França*
236
-
237
- * Remove deprecated invalid default content types in Active Storage configurations.
238
-
239
- *Rafael Mendonça França*
240
-
241
- * Add missing preview event to `ActiveStorage::LogSubscriber`
242
-
243
- A `preview` event is being instrumented in `ActiveStorage::Previewer`.
244
- However it was not added inside ActiveStorage's LogSubscriber class.
245
-
246
- This will allow to have logs for when a preview happens
247
- in the same fashion as all other ActiveStorage events such as
248
- `upload` and `download` inside `Rails.logger`.
249
-
250
- *Chedli Bourguiba*
251
-
252
- * Fix retrieving rotation value from FFmpeg on version 5.0+.
253
-
254
- In FFmpeg version 5.0+ the rotation value has been removed from tags.
255
- Instead the value can be found in side_data_list. Along with
256
- this update it's possible to have values of -90, -270 to denote the video
257
- has been rotated.
258
-
259
- *Haroon Ahmed*
260
-
261
- * Touch all corresponding model records after ActiveStorage::Blob is analyzed
262
-
263
- This fixes a race condition where a record can be requested and have a cache entry built, before
264
- the initial `analyze_later` completes, which will not be invalidated until something else
265
- updates the record. This also invalidates cache entries when a blob is re-analyzed, which
266
- is helpful if a bug is fixed in an analyzer or a new analyzer is added.
267
-
268
- *Nate Matykiewicz*
269
-
270
- * Add ability to use pre-defined variants when calling `preview` or
271
- `representation` on an attachment.
121
+ Calling Google::Auth.get_application_default triggers an explicit call to
122
+ the metadata server - given it was being called for significant number of
123
+ file operations, it can lead to considerable tail latencies and even metadata
124
+ server overloads. Instead, it's preferable (and significantly more efficient)
125
+ that applications use:
272
126
 
273
127
  ```ruby
274
- class User < ActiveRecord::Base
275
- has_one_attached :file do |attachable|
276
- attachable.variant :thumb, resize_to_limit: [100, 100]
277
- end
278
- end
279
-
280
- <%= image_tag user.file.representation(:thumb) %>
128
+ Google::Apis::RequestOptions.default.authorization = Google::Auth.get_application_default(...)
281
129
  ```
282
130
 
283
- *Richard Böhme*
131
+ In the cases applications do not set that, the GCP libraries automatically determine credentials.
284
132
 
285
- * Method `attach` always returns the attachments except when the record
286
- is persisted, unchanged, and saving it fails, in which case it returns `nil`.
133
+ This also enables using credentials other than those of the associated GCP
134
+ service account like when using impersonation.
287
135
 
288
- *Santiago Bartesaghi*
136
+ *Alex Coomans*
289
137
 
290
- * Fixes multiple `attach` calls within transaction not uploading files correctly.
138
+ * Direct upload progress accounts for server processing time.
291
139
 
292
- In the following example, the code failed to upload all but the last file to the configured service.
293
- ```ruby
294
- ActiveRecord::Base.transaction do
295
- user.attachments.attach({
296
- content_type: "text/plain",
297
- filename: "dummy.txt",
298
- io: ::StringIO.new("dummy"),
299
- })
300
- user.attachments.attach({
301
- content_type: "text/plain",
302
- filename: "dummy2.txt",
303
- io: ::StringIO.new("dummy2"),
304
- })
305
- end
306
-
307
- assert_equal 2, user.attachments.count
308
- assert user.attachments.first.service.exist?(user.attachments.first.key) # Fails
309
- ```
140
+ *Jeremy Daer*
310
141
 
311
- This was addressed by keeping track of the subchanges pending upload, and uploading them
312
- once the transaction is committed.
142
+ * Delegate `ActiveStorage::Filename#to_str` to `#to_s`
313
143
 
314
- Fixes #41661
144
+ Supports checking String equality:
315
145
 
316
- *Santiago Bartesaghi*, *Bruno Vezoli*, *Juan Roig*, *Abhay Nikam*
317
-
318
- * Raise an exception if `config.active_storage.service` is not set.
319
-
320
- If Active Storage is configured and `config.active_storage.service` is not
321
- set in the respective environment's configuration file, then an exception
322
- is raised with a meaningful message when attempting to use Active Storage.
323
-
324
- *Ghouse Mohamed*
325
-
326
- * Fixes proxy downloads of files over 5mb
327
-
328
- Previously, trying to view and/or download files larger than 5mb stored in
329
- services like S3 via proxy mode could return corrupted files at around
330
- 5.2mb or cause random halts in the download. Now,
331
- `ActiveStorage::Blobs::ProxyController` correctly handles streaming these
332
- larger files from the service to the client without any issues.
333
-
334
- Fixes #44679
335
-
336
- *Felipe Raul*
337
-
338
- * Saving attachment(s) to a record returns the blob/blobs object
339
-
340
- Previously, saving attachments did not return the blob/blobs that
341
- were attached. Now, saving attachments to a record with `#attach`
342
- method returns the blob or array of blobs that were attached to
343
- the record. If it fails to save the attachment(s), then it returns
344
- `false`.
345
-
346
- *Ghouse Mohamed*
347
-
348
- * Don't stream responses in redirect mode
146
+ ```ruby
147
+ filename = ActiveStorage::Filename.new("file.txt")
148
+ filename == "file.txt" # => true
149
+ filename in "file.txt" # => true
150
+ "file.txt" == filename # => true
151
+ ```
349
152
 
350
- Previously, both redirect mode and proxy mode streamed their
351
- responses which caused a new thread to be created, and could end
352
- up leaking connections in the connection pool. But since redirect
353
- mode doesn't actually send any data, it doesn't need to be
354
- streamed.
153
+ *Sean Doyle*
355
154
 
356
- *Luke Lau*
155
+ * A Blob will no longer autosave associated Attachment.
357
156
 
358
- * Safe for direct upload on Libraries or Frameworks
157
+ This fixes an issue where a record with an attachment would have
158
+ its dirty attributes reset, preventing your `after commit` callbacks
159
+ on that record to behave as expected.
359
160
 
360
- Enable the use of custom headers during direct uploads, which allows for
361
- the inclusion of Authorization bearer tokens or other forms of authorization
362
- tokens through headers.
161
+ Note that this change doesn't require any changes on your application
162
+ and is supposed to be internal. Active Storage Attachment will continue
163
+ to be autosaved (through a different relation).
363
164
 
364
- *Radamés Roriz*
165
+ *Edouard-chin*
365
166
 
366
- Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md) for previous changes.
167
+ Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/activestorage/CHANGELOG.md) for previous changes.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Active Storage
2
2
 
3
- Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), [Google Cloud Storage](https://cloud.google.com/storage/docs/), or [Microsoft Azure Storage](https://azure.microsoft.com/en-us/services/storage/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
3
+ Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), or [Google Cloud Storage](https://cloud.google.com/storage/docs/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
4
4
 
5
5
  Files can be uploaded from the server to the cloud or directly from the client to the cloud.
6
6
 
@@ -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.text_area :content %><br><br>
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.require(:message).permit(:title, :content, images: [])
91
+ message = Message.create! params.expect(message: [ :title, :content, images: [] ])
92
92
  redirect_to message
93
93
  end
94
94
 
@@ -173,7 +173,10 @@ Active Storage, with its included JavaScript library, supports uploading directl
173
173
  ```erb
174
174
  <%= form.file_field :attachments, multiple: true, direct_upload: true %>
175
175
  ```
176
- 3. That's it! Uploads begin upon form submission.
176
+
177
+ 3. Configure CORS on third-party storage services to allow direct upload requests.
178
+
179
+ 4. That's it! Uploads begin upon form submission.
177
180
 
178
181
  ### Direct upload JavaScript events
179
182
 
@@ -203,6 +206,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
203
206
 
204
207
  * https://github.com/rails/rails/issues
205
208
 
206
- Feature requests should be discussed on the rails-core mailing list here:
209
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
210
 
208
211
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -672,7 +672,7 @@ class DirectUploadController {
672
672
  }));
673
673
  }
674
674
  uploadRequestDidProgress(event) {
675
- const progress = event.loaded / event.total * 100;
675
+ const progress = event.loaded / event.total * 90;
676
676
  if (progress) {
677
677
  this.dispatch("progress", {
678
678
  progress: progress
@@ -707,6 +707,42 @@ class DirectUploadController {
707
707
  xhr: xhr
708
708
  });
709
709
  xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
710
+ xhr.upload.addEventListener("loadend", (() => {
711
+ this.simulateResponseProgress(xhr);
712
+ }));
713
+ }
714
+ simulateResponseProgress(xhr) {
715
+ let progress = 90;
716
+ const startTime = Date.now();
717
+ const updateProgress = () => {
718
+ const elapsed = Date.now() - startTime;
719
+ const estimatedResponseTime = this.estimateResponseTime();
720
+ const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
721
+ progress = 90 + responseProgress * 9;
722
+ this.dispatch("progress", {
723
+ progress: progress
724
+ });
725
+ if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
726
+ requestAnimationFrame(updateProgress);
727
+ }
728
+ };
729
+ xhr.addEventListener("loadend", (() => {
730
+ this.dispatch("progress", {
731
+ progress: 100
732
+ });
733
+ }));
734
+ requestAnimationFrame(updateProgress);
735
+ }
736
+ estimateResponseTime() {
737
+ const fileSize = this.file.size;
738
+ const MB = 1024 * 1024;
739
+ if (fileSize < MB) {
740
+ return 1e3;
741
+ } else if (fileSize < 10 * MB) {
742
+ return 2e3;
743
+ } else {
744
+ return 3e3 + fileSize / MB * 50;
745
+ }
710
746
  }
711
747
  }
712
748
 
@@ -845,4 +881,4 @@ function autostart() {
845
881
 
846
882
  setTimeout(autostart, 1);
847
883
 
848
- export { DirectUpload, DirectUploadController, DirectUploadsController, start };
884
+ export { DirectUpload, DirectUploadController, DirectUploadsController, dispatchEvent, start };
@@ -662,7 +662,7 @@
662
662
  }));
663
663
  }
664
664
  uploadRequestDidProgress(event) {
665
- const progress = event.loaded / event.total * 100;
665
+ const progress = event.loaded / event.total * 90;
666
666
  if (progress) {
667
667
  this.dispatch("progress", {
668
668
  progress: progress
@@ -697,6 +697,42 @@
697
697
  xhr: xhr
698
698
  });
699
699
  xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
700
+ xhr.upload.addEventListener("loadend", (() => {
701
+ this.simulateResponseProgress(xhr);
702
+ }));
703
+ }
704
+ simulateResponseProgress(xhr) {
705
+ let progress = 90;
706
+ const startTime = Date.now();
707
+ const updateProgress = () => {
708
+ const elapsed = Date.now() - startTime;
709
+ const estimatedResponseTime = this.estimateResponseTime();
710
+ const responseProgress = Math.min(elapsed / estimatedResponseTime, 1);
711
+ progress = 90 + responseProgress * 9;
712
+ this.dispatch("progress", {
713
+ progress: progress
714
+ });
715
+ if (xhr.readyState !== XMLHttpRequest.DONE && progress < 99) {
716
+ requestAnimationFrame(updateProgress);
717
+ }
718
+ };
719
+ xhr.addEventListener("loadend", (() => {
720
+ this.dispatch("progress", {
721
+ progress: 100
722
+ });
723
+ }));
724
+ requestAnimationFrame(updateProgress);
725
+ }
726
+ estimateResponseTime() {
727
+ const fileSize = this.file.size;
728
+ const MB = 1024 * 1024;
729
+ if (fileSize < MB) {
730
+ return 1e3;
731
+ } else if (fileSize < 10 * MB) {
732
+ return 2e3;
733
+ } else {
734
+ return 3e3 + fileSize / MB * 50;
735
+ }
700
736
  }
701
737
  }
702
738
  const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
@@ -822,6 +858,7 @@
822
858
  exports.DirectUpload = DirectUpload;
823
859
  exports.DirectUploadController = DirectUploadController;
824
860
  exports.DirectUploadsController = DirectUploadsController;
861
+ exports.dispatchEvent = dispatchEvent;
825
862
  exports.start = start;
826
863
  Object.defineProperty(exports, "__esModule", {
827
864
  value: true
@@ -11,7 +11,7 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
11
11
 
12
12
  private
13
13
  def blob_args
14
- params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
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)
@@ -17,6 +17,8 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
17
17
  end
18
18
  rescue Errno::ENOENT
19
19
  head :not_found
20
+ rescue ActiveStorage::InvalidKeyError
21
+ head :not_found
20
22
  end
21
23
 
22
24
  def update
@@ -25,13 +27,15 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
25
27
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
28
  head :no_content
27
29
  else
28
- head :unprocessable_entity
30
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
31
  end
30
32
  else
31
33
  head :not_found
32
34
  end
33
35
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
36
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
37
+ rescue ActiveStorage::InvalidKeyError
38
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
39
  end
36
40
 
37
41
  private