activestorage 7.1.0 → 7.2.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -200
  3. data/app/assets/javascripts/activestorage.esm.js +3 -3
  4. data/app/assets/javascripts/activestorage.js +3 -3
  5. data/app/controllers/active_storage/representations/proxy_controller.rb +1 -1
  6. data/app/javascript/activestorage/ujs.js +3 -3
  7. data/app/jobs/active_storage/preview_image_job.rb +16 -0
  8. data/app/jobs/active_storage/transform_job.rb +2 -2
  9. data/app/models/active_storage/attachment.rb +19 -8
  10. data/app/models/active_storage/blob/representable.rb +9 -1
  11. data/app/models/active_storage/blob/servable.rb +22 -0
  12. data/app/models/active_storage/blob.rb +40 -33
  13. data/app/models/active_storage/filename.rb +0 -4
  14. data/app/models/active_storage/preview.rb +18 -5
  15. data/app/models/active_storage/variant.rb +2 -0
  16. data/app/models/active_storage/variant_record.rb +0 -2
  17. data/app/models/active_storage/variant_with_record.rb +2 -0
  18. data/db/migrate/20170806125915_create_active_storage_tables.rb +1 -1
  19. data/lib/active_storage/analyzer/image_analyzer/vips.rb +5 -9
  20. data/lib/active_storage/analyzer/video_analyzer.rb +6 -2
  21. data/lib/active_storage/attached/changes/create_one.rb +6 -1
  22. data/lib/active_storage/attached/changes/create_one_of_many.rb +5 -1
  23. data/lib/active_storage/attached/model.rb +40 -27
  24. data/lib/active_storage/engine.rb +3 -9
  25. data/lib/active_storage/fixture_set.rb +5 -1
  26. data/lib/active_storage/gem_version.rb +3 -3
  27. data/lib/active_storage/previewer/mupdf_previewer.rb +6 -2
  28. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +6 -2
  29. data/lib/active_storage/previewer/video_previewer.rb +1 -1
  30. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
  31. data/lib/active_storage.rb +2 -17
  32. metadata +20 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f09e751b9896e3e99bda5b07a2f946e14cc08a8b25b29b872c85d918fa906c1
4
- data.tar.gz: f10ad7fbf13f79f91b690a6417a7f035038f62f2c82ee39a94070566773c2bd5
3
+ metadata.gz: 19f4fa25c348a39b38b18a049dd123c5a0c6f80d70e05cdfc4790c7e0dbf96d1
4
+ data.tar.gz: 48ba490e74129a1495ed0b3249730bfeba7b12f916e787849e7cfa4f81235bf9
5
5
  SHA512:
6
- metadata.gz: d5fc945abbfdfbe05b6aeafa393ef6b6995fa1c0e6f908006dc52e570ba9d17e982f40df652814b95e5da0db68a7bacf8f0a7820de2bec928fbaf1519fed00bd
7
- data.tar.gz: 627a0bf0f41c633ce7f3c098b264ffafebe6d081b2aea39ef0c9e30985fe06fb167b192abfd5fd5d729cef9592abe2acade5760ea792089373482dce1183b867
6
+ metadata.gz: 6656a5f10b464e63d86f0e4fc60984735e522d56789f93ce4f0d6ec4684b0b7e6a449f14af01a92e5929b1a4854f283b0bf89b37fe67ecb5d8cc207430b81c71
7
+ data.tar.gz: a8fe146a25f52b241de32978f344a2303ad5221f05306a57776337194ba67aa2691c715790131056dec44dfe21cb26b7df1fe33fef5d09efba3ff5f226b83e58
data/CHANGELOG.md CHANGED
@@ -1,252 +1,103 @@
1
- ## Rails 7.1.0 (October 05, 2023) ##
1
+ ## Rails 7.2.2.1 (December 10, 2024) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 7.1.0.rc2 (October 01, 2023) ##
6
+ ## Rails 7.2.2 (October 30, 2024) ##
7
7
 
8
8
  * No changes.
9
9
 
10
10
 
11
- ## Rails 7.1.0.rc1 (September 27, 2023) ##
11
+ ## Rails 7.2.1.2 (October 23, 2024) ##
12
12
 
13
- * Add `expires_at` option to `ActiveStorage::Blob#signed_id`.
14
-
15
- ```ruby
16
- rails_blob_path(user.avatar, disposition: "attachment", expires_at: 30.minutes.from_now)
17
- <%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 30.minutes.from_now) %>
18
- ```
19
-
20
- *Aki*
21
-
22
- * Allow attaching File and Pathname when assigning attributes, e.g.
23
-
24
- ```ruby
25
- User.create!(avatar: File.open("image.jpg"))
26
- User.create!(avatar: file_fixture("image.jpg"))
27
- ```
28
-
29
- *Dorian Marié*
30
-
31
-
32
- ## Rails 7.1.0.beta1 (September 13, 2023) ##
33
-
34
- * Disables the session in `ActiveStorage::Blobs::ProxyController`
35
- and `ActiveStorage::Representations::ProxyController`
36
- in order to allow caching by default in some CDNs as CloudFlare
37
-
38
- Fixes #44136
39
-
40
- *Bruno Prieto*
41
-
42
- * Add `tags` to `ActiveStorage::Analyzer::AudioAnalyzer` output
43
-
44
- *Keaton Roux*
45
-
46
- * Add an option to preprocess variants
47
-
48
- ActiveStorage variants are processed on the fly when they are needed but
49
- sometimes we're sure that they are accessed and want to processed them
50
- upfront.
51
-
52
- `preprocessed` option is added when declaring variants.
53
-
54
- ```
55
- class User < ApplicationRecord
56
- has_one_attached :avatar do |attachable|
57
- attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
58
- end
59
- end
60
- ```
61
-
62
- *Shouichi Kamiya*
63
-
64
- * Fix variants not included when eager loading multiple records containing a single attachment
65
-
66
- When using the `with_attached_#{name}` scope for a `has_one_attached` relation,
67
- attachment variants were not eagerly loaded.
68
-
69
- *Russell Porter*
70
-
71
- * Allow an ActiveStorage attachment to be removed via a form post
72
-
73
- Attachments can already be removed by updating the attachment to be nil such as:
74
- ```ruby
75
- User.find(params[:id]).update!(avatar: nil)
76
- ```
77
-
78
- However, a form cannot post a nil param, it can only post an empty string. But, posting an
79
- empty string would result in an `ActiveSupport::MessageVerifier::InvalidSignature: mismatched digest`
80
- error being raised, because it's being treated as a signed blob id.
81
-
82
- Now, nil and an empty string are treated as a delete, which allows attachments to be removed via:
83
- ```ruby
84
- User.find(params[:id]).update!(params.require(:user).permit(:avatar))
85
-
86
- ```
87
-
88
- *Nate Matykiewicz*
89
-
90
- * Remove mini_mime usage in favour of marcel.
91
-
92
- We have two libraries that are have similar usage. This change removes
93
- dependency on mini_mime and makes use of similar methods from marcel.
94
-
95
- *Vipul A M*
96
-
97
- * Allow destroying active storage variants
13
+ * No changes.
98
14
 
99
- ```ruby
100
- User.first.avatar.variant(resize_to_limit: [100, 100]).destroy
101
- ```
102
15
 
103
- *Shouichi Kamiya*, *Yuichiro NAKAGAWA*, *Ryohei UEDA*
16
+ ## Rails 7.2.1.1 (October 15, 2024) ##
104
17
 
105
- * Add `sample_rate` to `ActiveStorage::Analyzer::AudioAnalyzer` output
18
+ * No changes.
106
19
 
107
- *Matija Čupić*
108
20
 
109
- * Remove deprecated `purge` and `purge_later` methods from the attachments association.
21
+ ## Rails 7.2.1 (August 22, 2024) ##
110
22
 
111
- *Rafael Mendonça França*
112
-
113
- * Remove deprecated behavior when assigning to a collection of attachments.
23
+ * No changes.
114
24
 
115
- Instead of appending to the collection, the collection is now replaced.
116
25
 
117
- *Rafael Mendonça França*
26
+ ## Rails 7.2.0 (August 09, 2024) ##
118
27
 
119
- * Remove deprecated `ActiveStorage::Current#host` and `ActiveStorage::Current#host=` methods.
28
+ * Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
120
29
 
121
30
  *Rafael Mendonça França*
122
31
 
123
- * Remove deprecated invalid default content types in Active Storage configurations.
32
+ * Remove deprecated `config.active_storage.replace_on_assign_to_many`.
124
33
 
125
34
  *Rafael Mendonça França*
126
35
 
127
- * Add missing preview event to `ActiveStorage::LogSubscriber`
128
-
129
- A `preview` event is being instrumented in `ActiveStorage::Previewer`.
130
- However it was not added inside ActiveStorage's LogSubscriber class.
131
-
132
- This will allow to have logs for when a preview happens
133
- in the same fashion as all other ActiveStorage events such as
134
- `upload` and `download` inside `Rails.logger`.
135
-
136
- *Chedli Bourguiba*
137
-
138
- * Fix retrieving rotation value from FFmpeg on version 5.0+.
139
-
140
- In FFmpeg version 5.0+ the rotation value has been removed from tags.
141
- Instead the value can be found in side_data_list. Along with
142
- this update it's possible to have values of -90, -270 to denote the video
143
- has been rotated.
144
-
145
- *Haroon Ahmed*
146
-
147
- * Touch all corresponding model records after ActiveStorage::Blob is analyzed
148
-
149
- This fixes a race condition where a record can be requested and have a cache entry built, before
150
- the initial `analyze_later` completes, which will not be invalidated until something else
151
- updates the record. This also invalidates cache entries when a blob is re-analyzed, which
152
- is helpful if a bug is fixed in an analyzer or a new analyzer is added.
153
-
154
- *Nate Matykiewicz*
36
+ * Add support for custom `key` in `ActiveStorage::Blob#compose`.
155
37
 
156
- * Add ability to use pre-defined variants when calling `preview` or
157
- `representation` on an attachment.
38
+ *Elvin Efendiev*
158
39
 
159
- ```ruby
160
- class User < ActiveRecord::Base
161
- has_one_attached :file do |attachable|
162
- attachable.variant :thumb, resize_to_limit: [100, 100]
163
- end
164
- end
40
+ * Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
41
+ is set.
165
42
 
166
- <%= image_tag user.file.representation(:thumb) %>
167
- ```
43
+ *Lewis Buckley*
168
44
 
169
- *Richard Böhme*
45
+ * Fix JSON-encoding of `ActiveStorage::Filename` instances.
170
46
 
171
- * Method `attach` always returns the attachments except when the record
172
- is persisted, unchanged, and saving it fails, in which case it returns `nil`.
47
+ *Jonathan del Strother*
173
48
 
174
- *Santiago Bartesaghi*
49
+ * Fix N+1 query when fetching preview images for non-image assets.
175
50
 
176
- * Fixes multiple `attach` calls within transaction not uploading files correctly.
51
+ *Aaron Patterson & Justin Searls*
177
52
 
178
- In the following example, the code failed to upload all but the last file to the configured service.
179
- ```ruby
180
- ActiveRecord::Base.transaction do
181
- user.attachments.attach({
182
- content_type: "text/plain",
183
- filename: "dummy.txt",
184
- io: ::StringIO.new("dummy"),
185
- })
186
- user.attachments.attach({
187
- content_type: "text/plain",
188
- filename: "dummy2.txt",
189
- io: ::StringIO.new("dummy2"),
190
- })
191
- end
53
+ * Fix all Active Storage database related models to respect
54
+ `ActiveRecord::Base.table_name_prefix` configuration.
192
55
 
193
- assert_equal 2, user.attachments.count
194
- assert user.attachments.first.service.exist?(user.attachments.first.key) # Fails
195
- ```
196
-
197
- This was addressed by keeping track of the subchanges pending upload, and uploading them
198
- once the transaction is committed.
56
+ *Chedli Bourguiba*
199
57
 
200
- Fixes #41661
58
+ * Fix `ActiveStorage::Representations::ProxyController` not returning the proper
59
+ preview image variant for previewable files.
201
60
 
202
- *Santiago Bartesaghi*, *Bruno Vezoli*, *Juan Roig*, *Abhay Nikam*
61
+ *Chedli Bourguiba*
203
62
 
204
- * Raise an exception if `config.active_storage.service` is not set.
63
+ * Fix `ActiveStorage::Representations::ProxyController` to proxy untracked
64
+ variants.
205
65
 
206
- If Active Storage is configured and `config.active_storage.service` is not
207
- set in the respective environment's configuration file, then an exception
208
- is raised with a meaningful message when attempting to use Active Storage.
66
+ *Chedli Bourguiba*
209
67
 
210
- *Ghouse Mohamed*
68
+ * When using the `preprocessed: true` option, avoid enqueuing transform jobs
69
+ for blobs that are not representable.
211
70
 
212
- * Fixes proxy downloads of files over 5mb
71
+ *Chedli Bourguiba*
213
72
 
214
- Previously, trying to view and/or download files larger than 5mb stored in
215
- services like S3 via proxy mode could return corrupted files at around
216
- 5.2mb or cause random halts in the download. Now,
217
- `ActiveStorage::Blobs::ProxyController` correctly handles streaming these
218
- larger files from the service to the client without any issues.
73
+ * Prevent `ActiveStorage::Blob#preview` to generate a variant if an empty variation is passed.
219
74
 
220
- Fixes #44679
75
+ Calls to `#url`, `#key` or `#download` will now use the original preview
76
+ image instead of generating a variant with the exact same dimensions.
221
77
 
222
- *Felipe Raul*
78
+ *Chedli Bourguiba*
223
79
 
224
- * Saving attachment(s) to a record returns the blob/blobs object
80
+ * Process preview image variant when calling `ActiveStorage::Preview#processed`.
225
81
 
226
- Previously, saving attachments did not return the blob/blobs that
227
- were attached. Now, saving attachments to a record with `#attach`
228
- method returns the blob or array of blobs that were attached to
229
- the record. If it fails to save the attachment(s), then it returns
230
- `false`.
82
+ For example, `attached_pdf.preview(:thumb).processed` will now immediately
83
+ generate the full-sized preview image and the `:thumb` variant of it.
84
+ Previously, the `:thumb` variant would not be generated until a further call
85
+ to e.g. `processed.url`.
231
86
 
232
- *Ghouse Mohamed*
87
+ *Chedli Bourguiba* and *Jonathan Hefner*
233
88
 
234
- * Don't stream responses in redirect mode
89
+ * Prevent `ActiveRecord::StrictLoadingViolationError` when strict loading is
90
+ enabled and the variant of an Active Storage preview has already been
91
+ processed (for example, by calling `ActiveStorage::Preview#url`).
235
92
 
236
- Previously, both redirect mode and proxy mode streamed their
237
- responses which caused a new thread to be created, and could end
238
- up leaking connections in the connection pool. But since redirect
239
- mode doesn't actually send any data, it doesn't need to be
240
- streamed.
93
+ *Jonathan Hefner*
241
94
 
242
- *Luke Lau*
95
+ * Fix `preprocessed: true` option for named variants of previewable files.
243
96
 
244
- * Safe for direct upload on Libraries or Frameworks
97
+ *Nico Wenterodt*
245
98
 
246
- Enable the use of custom headers during direct uploads, which allows for
247
- the inclusion of Authorization bearer tokens or other forms of authorization
248
- tokens through headers.
99
+ * Allow accepting `service` as a proc as well in `has_one_attached` and `has_many_attached`.
249
100
 
250
- *Radamés Roriz*
101
+ *Yogesh Khater*
251
102
 
252
- Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md) for previous changes.
103
+ Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md) for previous changes.
@@ -771,9 +771,9 @@ function start() {
771
771
  }
772
772
 
773
773
  function didClick(event) {
774
- const {target: target} = event;
775
- if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
776
- submitButtonsByForm.set(target.form, target);
774
+ const button = event.target.closest("button, input");
775
+ if (button && button.type === "submit" && button.form) {
776
+ submitButtonsByForm.set(button.form, button);
777
777
  }
778
778
  }
779
779
 
@@ -754,9 +754,9 @@
754
754
  }
755
755
  }
756
756
  function didClick(event) {
757
- const {target: target} = event;
758
- if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
759
- submitButtonsByForm.set(target.form, target);
757
+ const button = event.target.closest("button, input");
758
+ if (button && button.type === "submit" && button.form) {
759
+ submitButtonsByForm.set(button.form, button);
760
760
  }
761
761
  }
762
762
  function didSubmitForm(event) {
@@ -12,7 +12,7 @@ class ActiveStorage::Representations::ProxyController < ActiveStorage::Represent
12
12
 
13
13
  def show
14
14
  http_cache_forever public: true do
15
- send_blob_stream @representation.image, disposition: params[:disposition]
15
+ send_blob_stream @representation, disposition: params[:disposition]
16
16
  end
17
17
  end
18
18
  end
@@ -15,9 +15,9 @@ export function start() {
15
15
  }
16
16
 
17
17
  function didClick(event) {
18
- const { target } = event
19
- if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
20
- submitButtonsByForm.set(target.form, target)
18
+ const button = event.target.closest("button, input")
19
+ if (button && button.type === "submit" && button.form) {
20
+ submitButtonsByForm.set(button.form, button)
21
21
  }
22
22
  }
23
23
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveStorage::PreviewImageJob < ActiveStorage::BaseJob
4
+ queue_as { ActiveStorage.queues[:preview_image] }
5
+
6
+ discard_on ActiveRecord::RecordNotFound, ActiveStorage::UnrepresentableError
7
+ retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :polynomially_longer
8
+
9
+ def perform(blob, variations)
10
+ blob.preview({}).processed
11
+
12
+ variations.each do |transformations|
13
+ blob.preprocessed(transformations)
14
+ end
15
+ end
16
+ end
@@ -3,10 +3,10 @@
3
3
  class ActiveStorage::TransformJob < ActiveStorage::BaseJob
4
4
  queue_as { ActiveStorage.queues[:transform] }
5
5
 
6
- discard_on ActiveRecord::RecordNotFound
6
+ discard_on ActiveRecord::RecordNotFound, ActiveStorage::UnrepresentableError
7
7
  retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :polynomially_longer
8
8
 
9
9
  def perform(blob, transformations)
10
- blob.variant(transformations).processed
10
+ blob.representation(transformations).processed
11
11
  end
12
12
  end
@@ -18,19 +18,17 @@ require "active_support/core_ext/module/delegation"
18
18
  # # preloads blobs and variant records (if using `ActiveStorage.track_variants`)
19
19
  # User.first.avatars.with_all_variant_records
20
20
  class ActiveStorage::Attachment < ActiveStorage::Record
21
- self.table_name = "active_storage_attachments"
22
-
23
21
  ##
24
22
  # :method:
25
23
  #
26
24
  # Returns the associated record.
27
- belongs_to :record, polymorphic: true, touch: true
25
+ belongs_to :record, polymorphic: true, touch: ActiveStorage.touch_attachment_records
28
26
 
29
27
  ##
30
28
  # :method:
31
29
  #
32
30
  # Returns the associated ActiveStorage::Blob.
33
- belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true
31
+ belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true, inverse_of: :attachments
34
32
 
35
33
  delegate_missing_to :blob
36
34
  delegate :signed_id, to: :blob
@@ -44,7 +42,10 @@ class ActiveStorage::Attachment < ActiveStorage::Record
44
42
  # Eager load all variant records on an attachment at once.
45
43
  #
46
44
  # User.first.avatars.with_all_variant_records
47
- scope :with_all_variant_records, -> { includes(blob: { variant_records: { image_attachment: :blob } }) }
45
+ scope :with_all_variant_records, -> { includes(blob: {
46
+ variant_records: { image_attachment: :blob },
47
+ preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
48
+ }) }
48
49
 
49
50
  # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
50
51
  def purge
@@ -131,8 +132,18 @@ class ActiveStorage::Attachment < ActiveStorage::Record
131
132
  end
132
133
 
133
134
  def transform_variants_later
134
- named_variants.each do |_name, named_variant|
135
- blob.preprocessed(named_variant.transformations) if named_variant.preprocessed?(record)
135
+ preprocessed_variations = named_variants.filter_map { |_name, named_variant|
136
+ if named_variant.preprocessed?(record)
137
+ named_variant.transformations
138
+ end
139
+ }
140
+
141
+ if blob.preview_image_needed_before_processing_variants? && preprocessed_variations.any?
142
+ blob.create_preview_image_later(preprocessed_variations)
143
+ else
144
+ preprocessed_variations.each do |transformations|
145
+ blob.preprocessed(transformations)
146
+ end
136
147
  end
137
148
  end
138
149
 
@@ -145,7 +156,7 @@ class ActiveStorage::Attachment < ActiveStorage::Record
145
156
  end
146
157
 
147
158
  def named_variants
148
- record.attachment_reflections[name]&.named_variants
159
+ record.attachment_reflections[name]&.named_variants || {}
149
160
  end
150
161
 
151
162
  def transformations_by_name(transformations)
@@ -98,8 +98,16 @@ module ActiveStorage::Blob::Representable
98
98
  variable? || previewable?
99
99
  end
100
100
 
101
+ def preview_image_needed_before_processing_variants? # :nodoc:
102
+ previewable? && !preview_image.attached?
103
+ end
104
+
105
+ def create_preview_image_later(variations) # :nodoc:
106
+ ActiveStorage::PreviewImageJob.perform_later(self, variations) if representable?
107
+ end
108
+
101
109
  def preprocessed(transformations) # :nodoc:
102
- ActiveStorage::TransformJob.perform_later(self, transformations)
110
+ ActiveStorage::TransformJob.perform_later(self, transformations) if representable?
103
111
  end
104
112
 
105
113
  private
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage::Blob::Servable # :nodoc:
4
+ def content_type_for_serving
5
+ forcibly_serve_as_binary? ? ActiveStorage.binary_content_type : content_type
6
+ end
7
+
8
+ def forced_disposition_for_serving
9
+ if forcibly_serve_as_binary? || !allowed_inline?
10
+ :attachment
11
+ end
12
+ end
13
+
14
+ private
15
+ def forcibly_serve_as_binary?
16
+ ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
17
+ end
18
+
19
+ def allowed_inline?
20
+ ActiveStorage.content_types_allowed_inline.include?(content_type)
21
+ end
22
+ end
@@ -17,12 +17,6 @@
17
17
  # update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
18
18
  # If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
19
19
  class ActiveStorage::Blob < ActiveStorage::Record
20
- include Analyzable
21
- include Identifiable
22
- include Representable
23
-
24
- self.table_name = "active_storage_blobs"
25
-
26
20
  MINIMUM_TOKEN_LENGTH = 28
27
21
 
28
22
  has_secure_token :key, length: MINIMUM_TOKEN_LENGTH
@@ -34,7 +28,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
34
28
  ##
35
29
  # :method:
36
30
  #
37
- # Returns the associated +ActiveStorage::Attachment+s.
31
+ # Returns the associated ActiveStorage::Attachment instances.
38
32
  has_many :attachments
39
33
 
40
34
  ##
@@ -47,7 +41,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
47
41
  self.service_name ||= self.class.service&.name
48
42
  end
49
43
 
50
- after_update :touch_attachment_records
44
+ after_update :touch_attachments
51
45
 
52
46
  after_update_commit :update_service_metadata, if: -> { content_type_previously_changed? || metadata_previously_changed? }
53
47
 
@@ -138,25 +132,49 @@ class ActiveStorage::Blob < ActiveStorage::Record
138
132
 
139
133
  def scope_for_strict_loading # :nodoc:
140
134
  if strict_loading_by_default? && ActiveStorage.track_variants
141
- includes(variant_records: { image_attachment: :blob }, preview_image_attachment: :blob)
135
+ includes(
136
+ variant_records: { image_attachment: :blob },
137
+ preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
138
+ )
142
139
  else
143
140
  all
144
141
  end
145
142
  end
146
143
 
147
144
  # Concatenate multiple blobs into a single "composed" blob.
148
- def compose(blobs, filename:, content_type: nil, metadata: nil)
145
+ def compose(blobs, key: nil, filename:, content_type: nil, metadata: nil)
149
146
  raise ActiveRecord::RecordNotSaved, "All blobs must be persisted." if blobs.any?(&:new_record?)
150
147
 
151
148
  content_type ||= blobs.pluck(:content_type).compact.first
152
149
 
153
- new(filename: filename, content_type: content_type, metadata: metadata, byte_size: blobs.sum(&:byte_size)).tap do |combined_blob|
150
+ new(key: key, filename: filename, content_type: content_type, metadata: metadata, byte_size: blobs.sum(&:byte_size)).tap do |combined_blob|
154
151
  combined_blob.compose(blobs.pluck(:key))
155
152
  combined_blob.save!
156
153
  end
157
154
  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
158
171
  end
159
172
 
173
+ include Analyzable
174
+ include Identifiable
175
+ include Representable
176
+ include Servable
177
+
160
178
  # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
161
179
  def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
162
180
  super
@@ -226,16 +244,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
226
244
  service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum, custom_metadata: custom_metadata
227
245
  end
228
246
 
229
- def content_type_for_serving # :nodoc:
230
- forcibly_serve_as_binary? ? ActiveStorage.binary_content_type : content_type
231
- end
232
-
233
- def forced_disposition_for_serving # :nodoc:
234
- if forcibly_serve_as_binary? || !allowed_inline?
235
- :attachment
236
- end
237
- end
238
-
239
247
 
240
248
  # Uploads the +io+ to the service on the +key+ for this blob. Blobs are intended to be immutable, so you shouldn't be
241
249
  # using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob,
@@ -342,8 +350,9 @@ class ActiveStorage::Blob < ActiveStorage::Record
342
350
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
343
351
 
344
352
  OpenSSL::Digest::MD5.new.tap do |checksum|
345
- while chunk = io.read(5.megabytes)
346
- checksum << chunk
353
+ read_buffer = "".b
354
+ while io.read(5.megabytes, read_buffer)
355
+ checksum << read_buffer
347
356
  end
348
357
 
349
358
  io.rewind
@@ -354,14 +363,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
354
363
  Marcel::MimeType.for io, name: filename.to_s, declared_type: content_type
355
364
  end
356
365
 
357
- def forcibly_serve_as_binary?
358
- ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
359
- end
360
-
361
- def allowed_inline?
362
- ActiveStorage.content_types_allowed_inline.include?(content_type)
363
- end
364
-
365
366
  def web_image?
366
367
  ActiveStorage.web_image_content_types.include?(content_type)
367
368
  end
@@ -376,8 +377,14 @@ class ActiveStorage::Blob < ActiveStorage::Record
376
377
  end
377
378
  end
378
379
 
379
- def touch_attachment_records
380
- attachments.includes(:record).each do |attachment|
380
+ def touch_attachments
381
+ attachments.then do |relation|
382
+ if ActiveStorage.touch_attachment_records
383
+ relation.includes(:record)
384
+ else
385
+ relation
386
+ end
387
+ end.each do |attachment|
381
388
  attachment.touch
382
389
  end
383
390
  end
@@ -69,10 +69,6 @@ class ActiveStorage::Filename
69
69
  to_s
70
70
  end
71
71
 
72
- def to_json
73
- to_s
74
- end
75
-
76
72
  def <=>(other)
77
73
  to_s.downcase <=> other.to_s.downcase
78
74
  end