activestorage 7.1.6 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff38efc7c1678bba2c2f0f74383dcdac13a1254a5a4410edbd94072b7474c812
4
- data.tar.gz: e72d3d342061fbdcc635394fc57cec034493aaaa1d91a77c3e19ff1949ec2880
3
+ metadata.gz: 94213ce5408fd1a4c67c6bf4854d2f2c55289813ef0297dfc33fb3325ec5dfd4
4
+ data.tar.gz: 73de432409f6bb50456de14131e01c649b0172fcd5c0d5178b61b385cc236e37
5
5
  SHA512:
6
- metadata.gz: 949af37ae19e40205e0d3f514fe40e70e28467bde0d7f6cdc017532fbac7e8a62bb13fc5c78b3162ebddda1da75bad083352114eaa5cbf739ffcb53d59b68716
7
- data.tar.gz: 84c5556878a4c63a25127d58253f202bdb02254be14c1963b043110e41b46c2276abd8a2b5b44fc2e22c09a68859f68e0152027979adf4efcc402f41761fd7e0
6
+ metadata.gz: 48271c643e3a2a6ffbabdc398863f9452d3cdc2c01540b94a28f512fcc828d1792d13fd36ec05c890e3b02dc7d6fc33439c792eebbda2ab720479032307c947a
7
+ data.tar.gz: 65acfc493d0aa029d4b533dd086cdeb9e35c04c6b4b4b6ee678101516bfc9428ffc477be92d5d49c5c1f6f43f6bed29b2c7ccbacf11f650e27fae34a779bb672
data/CHANGELOG.md CHANGED
@@ -1,9 +1,23 @@
1
- ## Rails 7.1.6 (October 28, 2025) ##
1
+ ## Rails 7.2.3 (October 28, 2025) ##
2
2
 
3
- * No changes.
3
+ * Fix `config.active_storage.touch_attachment_records` to work with eager loading.
4
+
5
+ *fatkodima*
6
+
7
+ * A Blob will no longer autosave associated Attachment.
8
+
9
+ This fixes an issue where a record with an attachment would have
10
+ its dirty attributes reset, preventing your `after commit` callbacks
11
+ on that record to behave as expected.
4
12
 
13
+ Note that this change doesn't require any changes on your application
14
+ and is supposed to be internal. Active Storage Attachment will continue
15
+ to be autosaved (through a different relation).
5
16
 
6
- ## Rails 7.1.5.2 (August 13, 2025) ##
17
+ *Edouard-chin*
18
+
19
+
20
+ ## Rails 7.2.2.2 (August 13, 2025) ##
7
21
 
8
22
  * Remove dangerous transformations
9
23
 
@@ -11,54 +25,54 @@
11
25
 
12
26
  *Zack Deveau*
13
27
 
14
- ## Rails 7.1.5.1 (December 10, 2024) ##
15
-
16
- * No changes.
17
-
18
28
 
19
- ## Rails 7.1.5 (October 30, 2024) ##
29
+ ## Rails 7.2.2.1 (December 10, 2024) ##
20
30
 
21
31
  * No changes.
22
32
 
23
33
 
24
- ## Rails 7.1.4.2 (October 23, 2024) ##
34
+ ## Rails 7.2.2 (October 30, 2024) ##
25
35
 
26
36
  * No changes.
27
37
 
28
38
 
29
- ## Rails 7.1.4.1 (October 15, 2024) ##
39
+ ## Rails 7.2.1.2 (October 23, 2024) ##
30
40
 
31
41
  * No changes.
32
42
 
33
43
 
34
- ## Rails 7.1.4 (August 22, 2024) ##
35
-
36
- * Fixes race condition for multiple preprocessed video variants.
44
+ ## Rails 7.2.1.1 (October 15, 2024) ##
37
45
 
38
- *Justin Searls*
46
+ * No changes.
39
47
 
40
48
 
41
- ## Rails 7.1.3.4 (June 04, 2024) ##
49
+ ## Rails 7.2.1 (August 22, 2024) ##
42
50
 
43
51
  * No changes.
44
52
 
45
53
 
46
- ## Rails 7.1.3.3 (May 16, 2024) ##
54
+ ## Rails 7.2.0 (August 09, 2024) ##
47
55
 
48
- * No changes.
56
+ * Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
49
57
 
58
+ *Rafael Mendonça França*
50
59
 
51
- ## Rails 7.1.3.2 (February 21, 2024) ##
60
+ * Remove deprecated `config.active_storage.replace_on_assign_to_many`.
52
61
 
53
- * No changes.
62
+ *Rafael Mendonça França*
54
63
 
64
+ * Add support for custom `key` in `ActiveStorage::Blob#compose`.
55
65
 
56
- ## Rails 7.1.3.1 (February 21, 2024) ##
66
+ *Elvin Efendiev*
57
67
 
58
- * No changes.
68
+ * Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
69
+ is set.
59
70
 
71
+ *Lewis Buckley*
60
72
 
61
- ## Rails 7.1.3 (January 16, 2024) ##
73
+ * Fix JSON-encoding of `ActiveStorage::Filename` instances.
74
+
75
+ *Jonathan del Strother*
62
76
 
63
77
  * Fix N+1 query when fetching preview images for non-image assets.
64
78
 
@@ -79,16 +93,20 @@
79
93
 
80
94
  *Chedli Bourguiba*
81
95
 
82
- * Fix direct upload forms when submit button contains nested elements.
83
-
84
- *Marc Köhlbrugge*
85
-
86
96
  * When using the `preprocessed: true` option, avoid enqueuing transform jobs
87
97
  for blobs that are not representable.
88
98
 
89
99
  *Chedli Bourguiba*
90
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
+
91
108
  * Process preview image variant when calling `ActiveStorage::Preview#processed`.
109
+
92
110
  For example, `attached_pdf.preview(:thumb).processed` will now immediately
93
111
  generate the full-sized preview image and the `:thumb` variant of it.
94
112
  Previously, the `:thumb` variant would not be generated until a further call
@@ -106,266 +124,8 @@
106
124
 
107
125
  *Nico Wenterodt*
108
126
 
127
+ * Allow accepting `service` as a proc as well in `has_one_attached` and `has_many_attached`.
109
128
 
110
- ## Rails 7.1.2 (November 10, 2023) ##
111
-
112
- * No changes.
113
-
114
-
115
- ## Rails 7.1.1 (October 11, 2023) ##
116
-
117
- * No changes.
118
-
119
-
120
- ## Rails 7.1.0 (October 05, 2023) ##
121
-
122
- * No changes.
123
-
124
-
125
- ## Rails 7.1.0.rc2 (October 01, 2023) ##
126
-
127
- * No changes.
128
-
129
-
130
- ## Rails 7.1.0.rc1 (September 27, 2023) ##
131
-
132
- * Add `expires_at` option to `ActiveStorage::Blob#signed_id`.
133
-
134
- ```ruby
135
- rails_blob_path(user.avatar, disposition: "attachment", expires_at: 30.minutes.from_now)
136
- <%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 30.minutes.from_now) %>
137
- ```
138
-
139
- *Aki*
140
-
141
- * Allow attaching File and Pathname when assigning attributes, e.g.
142
-
143
- ```ruby
144
- User.create!(avatar: File.open("image.jpg"))
145
- User.create!(avatar: file_fixture("image.jpg"))
146
- ```
147
-
148
- *Dorian Marié*
149
-
150
-
151
- ## Rails 7.1.0.beta1 (September 13, 2023) ##
152
-
153
- * Disables the session in `ActiveStorage::Blobs::ProxyController`
154
- and `ActiveStorage::Representations::ProxyController`
155
- in order to allow caching by default in some CDNs as CloudFlare
156
-
157
- Fixes #44136
158
-
159
- *Bruno Prieto*
160
-
161
- * Add `tags` to `ActiveStorage::Analyzer::AudioAnalyzer` output
162
-
163
- *Keaton Roux*
164
-
165
- * Add an option to preprocess variants
166
-
167
- ActiveStorage variants are processed on the fly when they are needed but
168
- sometimes we're sure that they are accessed and want to processed them
169
- upfront.
170
-
171
- `preprocessed` option is added when declaring variants.
172
-
173
- ```
174
- class User < ApplicationRecord
175
- has_one_attached :avatar do |attachable|
176
- attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
177
- end
178
- end
179
- ```
180
-
181
- *Shouichi Kamiya*
182
-
183
- * Fix variants not included when eager loading multiple records containing a single attachment
184
-
185
- When using the `with_attached_#{name}` scope for a `has_one_attached` relation,
186
- attachment variants were not eagerly loaded.
187
-
188
- *Russell Porter*
189
-
190
- * Allow an ActiveStorage attachment to be removed via a form post
191
-
192
- Attachments can already be removed by updating the attachment to be nil such as:
193
- ```ruby
194
- User.find(params[:id]).update!(avatar: nil)
195
- ```
196
-
197
- However, a form cannot post a nil param, it can only post an empty string. But, posting an
198
- empty string would result in an `ActiveSupport::MessageVerifier::InvalidSignature: mismatched digest`
199
- error being raised, because it's being treated as a signed blob id.
200
-
201
- Now, nil and an empty string are treated as a delete, which allows attachments to be removed via:
202
- ```ruby
203
- User.find(params[:id]).update!(params.require(:user).permit(:avatar))
204
-
205
- ```
206
-
207
- *Nate Matykiewicz*
208
-
209
- * Remove mini_mime usage in favour of marcel.
210
-
211
- We have two libraries that are have similar usage. This change removes
212
- dependency on mini_mime and makes use of similar methods from marcel.
213
-
214
- *Vipul A M*
215
-
216
- * Allow destroying active storage variants
217
-
218
- ```ruby
219
- User.first.avatar.variant(resize_to_limit: [100, 100]).destroy
220
- ```
221
-
222
- *Shouichi Kamiya*, *Yuichiro NAKAGAWA*, *Ryohei UEDA*
223
-
224
- * Add `sample_rate` to `ActiveStorage::Analyzer::AudioAnalyzer` output
225
-
226
- *Matija Čupić*
227
-
228
- * Remove deprecated `purge` and `purge_later` methods from the attachments association.
229
-
230
- *Rafael Mendonça França*
231
-
232
- * Remove deprecated behavior when assigning to a collection of attachments.
233
-
234
- Instead of appending to the collection, the collection is now replaced.
235
-
236
- *Rafael Mendonça França*
237
-
238
- * Remove deprecated `ActiveStorage::Current#host` and `ActiveStorage::Current#host=` methods.
239
-
240
- *Rafael Mendonça França*
241
-
242
- * Remove deprecated invalid default content types in Active Storage configurations.
243
-
244
- *Rafael Mendonça França*
245
-
246
- * Add missing preview event to `ActiveStorage::LogSubscriber`
247
-
248
- A `preview` event is being instrumented in `ActiveStorage::Previewer`.
249
- However it was not added inside ActiveStorage's LogSubscriber class.
250
-
251
- This will allow to have logs for when a preview happens
252
- in the same fashion as all other ActiveStorage events such as
253
- `upload` and `download` inside `Rails.logger`.
254
-
255
- *Chedli Bourguiba*
256
-
257
- * Fix retrieving rotation value from FFmpeg on version 5.0+.
258
-
259
- In FFmpeg version 5.0+ the rotation value has been removed from tags.
260
- Instead the value can be found in side_data_list. Along with
261
- this update it's possible to have values of -90, -270 to denote the video
262
- has been rotated.
263
-
264
- *Haroon Ahmed*
265
-
266
- * Touch all corresponding model records after ActiveStorage::Blob is analyzed
267
-
268
- This fixes a race condition where a record can be requested and have a cache entry built, before
269
- the initial `analyze_later` completes, which will not be invalidated until something else
270
- updates the record. This also invalidates cache entries when a blob is re-analyzed, which
271
- is helpful if a bug is fixed in an analyzer or a new analyzer is added.
272
-
273
- *Nate Matykiewicz*
274
-
275
- * Add ability to use pre-defined variants when calling `preview` or
276
- `representation` on an attachment.
277
-
278
- ```ruby
279
- class User < ActiveRecord::Base
280
- has_one_attached :file do |attachable|
281
- attachable.variant :thumb, resize_to_limit: [100, 100]
282
- end
283
- end
284
-
285
- <%= image_tag user.file.representation(:thumb) %>
286
- ```
287
-
288
- *Richard Böhme*
289
-
290
- * Method `attach` always returns the attachments except when the record
291
- is persisted, unchanged, and saving it fails, in which case it returns `nil`.
292
-
293
- *Santiago Bartesaghi*
294
-
295
- * Fixes multiple `attach` calls within transaction not uploading files correctly.
296
-
297
- In the following example, the code failed to upload all but the last file to the configured service.
298
- ```ruby
299
- ActiveRecord::Base.transaction do
300
- user.attachments.attach({
301
- content_type: "text/plain",
302
- filename: "dummy.txt",
303
- io: ::StringIO.new("dummy"),
304
- })
305
- user.attachments.attach({
306
- content_type: "text/plain",
307
- filename: "dummy2.txt",
308
- io: ::StringIO.new("dummy2"),
309
- })
310
- end
311
-
312
- assert_equal 2, user.attachments.count
313
- assert user.attachments.first.service.exist?(user.attachments.first.key) # Fails
314
- ```
315
-
316
- This was addressed by keeping track of the subchanges pending upload, and uploading them
317
- once the transaction is committed.
318
-
319
- Fixes #41661
320
-
321
- *Santiago Bartesaghi*, *Bruno Vezoli*, *Juan Roig*, *Abhay Nikam*
322
-
323
- * Raise an exception if `config.active_storage.service` is not set.
324
-
325
- If Active Storage is configured and `config.active_storage.service` is not
326
- set in the respective environment's configuration file, then an exception
327
- is raised with a meaningful message when attempting to use Active Storage.
328
-
329
- *Ghouse Mohamed*
330
-
331
- * Fixes proxy downloads of files over 5mb
332
-
333
- Previously, trying to view and/or download files larger than 5mb stored in
334
- services like S3 via proxy mode could return corrupted files at around
335
- 5.2mb or cause random halts in the download. Now,
336
- `ActiveStorage::Blobs::ProxyController` correctly handles streaming these
337
- larger files from the service to the client without any issues.
338
-
339
- Fixes #44679
340
-
341
- *Felipe Raul*
342
-
343
- * Saving attachment(s) to a record returns the blob/blobs object
344
-
345
- Previously, saving attachments did not return the blob/blobs that
346
- were attached. Now, saving attachments to a record with `#attach`
347
- method returns the blob or array of blobs that were attached to
348
- the record. If it fails to save the attachment(s), then it returns
349
- `false`.
350
-
351
- *Ghouse Mohamed*
352
-
353
- * Don't stream responses in redirect mode
354
-
355
- Previously, both redirect mode and proxy mode streamed their
356
- responses which caused a new thread to be created, and could end
357
- up leaking connections in the connection pool. But since redirect
358
- mode doesn't actually send any data, it doesn't need to be
359
- streamed.
360
-
361
- *Luke Lau*
362
-
363
- * Safe for direct upload on Libraries or Frameworks
364
-
365
- Enable the use of custom headers during direct uploads, which allows for
366
- the inclusion of Authorization bearer tokens or other forms of authorization
367
- tokens through headers.
368
-
369
- *Radamés Roriz*
129
+ *Yogesh Khater*
370
130
 
371
- Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md) for previous changes.
131
+ Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md) for previous changes.
data/README.md CHANGED
@@ -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 rails-core mailing list here:
206
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
207
 
208
208
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -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 :unprocessable_entity
28
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
29
  end
30
30
  else
31
31
  head :not_found
32
32
  end
33
33
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
34
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
35
  end
36
36
 
37
37
  private
@@ -22,13 +22,13 @@ class ActiveStorage::Attachment < ActiveStorage::Record
22
22
  # :method:
23
23
  #
24
24
  # Returns the associated record.
25
- belongs_to :record, polymorphic: true, touch: true
25
+ belongs_to :record, polymorphic: true, touch: ActiveStorage.touch_attachment_records
26
26
 
27
27
  ##
28
28
  # :method:
29
29
  #
30
30
  # Returns the associated ActiveStorage::Blob.
31
- belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true
31
+ belongs_to :blob, class_name: "ActiveStorage::Blob", autosave: true, inverse_of: :attachments
32
32
 
33
33
  delegate_missing_to :blob
34
34
  delegate :signed_id, to: :blob
@@ -138,7 +138,7 @@ class ActiveStorage::Attachment < ActiveStorage::Record
138
138
  end
139
139
  }
140
140
 
141
- if blob.preview_image_needed_before_processing_variants?
141
+ if blob.preview_image_needed_before_processing_variants? && preprocessed_variations.any?
142
142
  blob.create_preview_image_later(preprocessed_variations)
143
143
  else
144
144
  preprocessed_variations.each do |transformations|
@@ -156,7 +156,7 @@ class ActiveStorage::Attachment < ActiveStorage::Record
156
156
  end
157
157
 
158
158
  def named_variants
159
- record.attachment_reflections[name]&.named_variants
159
+ record.attachment_reflections[name]&.named_variants || {}
160
160
  end
161
161
 
162
162
  def transformations_by_name(transformations)
@@ -31,6 +31,72 @@ 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
+ #
34
100
  def variant(transformations)
35
101
  if variable?
36
102
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
@@ -17,11 +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
- include Servable
24
-
25
20
  MINIMUM_TOKEN_LENGTH = 28
26
21
 
27
22
  has_secure_token :key, length: MINIMUM_TOKEN_LENGTH
@@ -34,7 +29,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
34
29
  # :method:
35
30
  #
36
31
  # Returns the associated ActiveStorage::Attachment instances.
37
- has_many :attachments
32
+ has_many :attachments, autosave: false
38
33
 
39
34
  ##
40
35
  # :singleton-method:
@@ -46,7 +41,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
46
41
  self.service_name ||= self.class.service&.name
47
42
  end
48
43
 
49
- after_update :touch_attachment_records
44
+ after_update :touch_attachments
50
45
 
51
46
  after_update_commit :update_service_metadata, if: -> { content_type_previously_changed? || metadata_previously_changed? }
52
47
 
@@ -147,18 +142,23 @@ class ActiveStorage::Blob < ActiveStorage::Record
147
142
  end
148
143
 
149
144
  # Concatenate multiple blobs into a single "composed" blob.
150
- def compose(blobs, filename:, content_type: nil, metadata: nil)
145
+ def compose(blobs, key: nil, filename:, content_type: nil, metadata: nil)
151
146
  raise ActiveRecord::RecordNotSaved, "All blobs must be persisted." if blobs.any?(&:new_record?)
152
147
 
153
148
  content_type ||= blobs.pluck(:content_type).compact.first
154
149
 
155
- 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|
156
151
  combined_blob.compose(blobs.pluck(:key))
157
152
  combined_blob.save!
158
153
  end
159
154
  end
160
155
  end
161
156
 
157
+ include Analyzable
158
+ include Identifiable
159
+ include Representable
160
+ include Servable
161
+
162
162
  # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
163
163
  def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
164
164
  super
@@ -334,8 +334,9 @@ class ActiveStorage::Blob < ActiveStorage::Record
334
334
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
335
335
 
336
336
  OpenSSL::Digest::MD5.new.tap do |checksum|
337
- while chunk = io.read(5.megabytes)
338
- checksum << chunk
337
+ read_buffer = "".b
338
+ while io.read(5.megabytes, read_buffer)
339
+ checksum << read_buffer
339
340
  end
340
341
 
341
342
  io.rewind
@@ -360,8 +361,14 @@ class ActiveStorage::Blob < ActiveStorage::Record
360
361
  end
361
362
  end
362
363
 
363
- def touch_attachment_records
364
- attachments.includes(:record).each do |attachment|
364
+ def touch_attachments
365
+ attachments.then do |relation|
366
+ if ActiveStorage.touch_attachment_records
367
+ relation.includes(:record)
368
+ else
369
+ relation
370
+ end
371
+ end.each do |attachment|
365
372
  attachment.touch
366
373
  end
367
374
  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
@@ -35,7 +35,7 @@ class ActiveStorage::Preview
35
35
 
36
36
  class UnprocessedError < StandardError; end
37
37
 
38
- delegate :filename, :content_type, to: :variant
38
+ delegate :filename, :content_type, to: :presentation
39
39
 
40
40
  attr_reader :blob, :variation
41
41
 
@@ -51,7 +51,7 @@ class ActiveStorage::Preview
51
51
  # image is stored with the blob, it is only generated once.
52
52
  def processed
53
53
  process unless processed?
54
- variant.processed
54
+ variant.processed if variant?
55
55
  self
56
56
  end
57
57
 
@@ -67,7 +67,7 @@ class ActiveStorage::Preview
67
67
  # a stable URL that redirects to the URL returned by this method.
68
68
  def url(**options)
69
69
  if processed?
70
- variant.url(**options)
70
+ presentation.url(**options)
71
71
  else
72
72
  raise UnprocessedError
73
73
  end
@@ -76,7 +76,7 @@ class ActiveStorage::Preview
76
76
  # Returns a combination key of the blob and the variation that together identifies a specific variant.
77
77
  def key
78
78
  if processed?
79
- variant.key
79
+ presentation.key
80
80
  else
81
81
  raise UnprocessedError
82
82
  end
@@ -89,7 +89,7 @@ class ActiveStorage::Preview
89
89
  # if the preview has not been processed yet.
90
90
  def download(&block)
91
91
  if processed?
92
- variant.download(&block)
92
+ presentation.download(&block)
93
93
  else
94
94
  raise UnprocessedError
95
95
  end
@@ -109,7 +109,15 @@ class ActiveStorage::Preview
109
109
  end
110
110
 
111
111
  def variant
112
- image.variant(variation).processed
112
+ image.variant(variation)
113
+ end
114
+
115
+ def variant?
116
+ variation.transformations.present?
117
+ end
118
+
119
+ def presentation
120
+ variant? ? variant.processed : image
113
121
  end
114
122
 
115
123
 
@@ -51,6 +51,6 @@ class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
51
51
  setting = config.options[config.orm][:primary_key_type]
52
52
  primary_key_type = setting || :primary_key
53
53
  foreign_key_type = setting || :bigint
54
- [primary_key_type, foreign_key_type]
54
+ [ primary_key_type, foreign_key_type ]
55
55
  end
56
56
  end
@@ -19,13 +19,16 @@ module ActiveStorage
19
19
 
20
20
  download_blob_to_tempfile do |file|
21
21
  image = instrument("vips") do
22
+ # ruby-vips will raise Vips::Error if it can't find an appropriate loader for the file
22
23
  ::Vips::Image.new_from_file(file.path, access: :sequential)
24
+ rescue ::Vips::Error
25
+ logger.info "Skipping image analysis because Vips doesn't support the file"
26
+ nil
23
27
  end
24
28
 
25
- if valid_image?(image)
29
+ if image
26
30
  yield image
27
31
  else
28
- logger.info "Skipping image analysis because Vips doesn't support the file"
29
32
  {}
30
33
  end
31
34
  rescue ::Vips::Error => error
@@ -40,12 +43,5 @@ module ActiveStorage
40
43
  rescue ::Vips::Error
41
44
  false
42
45
  end
43
-
44
- def valid_image?(image)
45
- image.avg
46
- true
47
- rescue ::Vips::Error
48
- false
49
- end
50
46
  end
51
47
  end
@@ -55,11 +55,15 @@ module ActiveStorage
55
55
  def angle
56
56
  if tags["rotate"]
57
57
  Integer(tags["rotate"])
58
- elsif side_data && side_data[0] && side_data[0]["rotation"]
59
- Integer(side_data[0]["rotation"])
58
+ elsif display_matrix && display_matrix["rotation"]
59
+ Integer(display_matrix["rotation"])
60
60
  end
61
61
  end
62
62
 
63
+ def display_matrix
64
+ side_data.detect { |data| data["side_data_type"] == "Display Matrix" }
65
+ end
66
+
63
67
  def display_aspect_ratio
64
68
  if descriptor = video_stream["display_aspect_ratio"]
65
69
  if terms = descriptor.split(":", 2)
@@ -118,7 +118,12 @@ module ActiveStorage
118
118
  end
119
119
 
120
120
  def attachment_service_name
121
- record.attachment_reflections[name].options[:service_name]
121
+ service_name = record.attachment_reflections[name].options[:service_name]
122
+ if service_name.is_a?(Proc)
123
+ service_name = service_name.call(record)
124
+ Attached::Model.validate_service_configuration(service_name, record.class, name)
125
+ end
126
+ service_name
122
127
  end
123
128
  end
124
129
  end
@@ -4,7 +4,11 @@ module ActiveStorage
4
4
  class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne # :nodoc:
5
5
  private
6
6
  def find_attachment
7
- record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
7
+ if blob.persisted?
8
+ record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
9
+ else
10
+ blob.attachments.find { |attachment| attachment.record == record }
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -80,12 +80,18 @@ module ActiveStorage
80
80
  # +purge+ instead.
81
81
  #
82
82
  # If you need the attachment to use a service which differs from the globally configured one,
83
- # pass the +:service+ option. For instance:
83
+ # pass the +:service+ option. For example:
84
84
  #
85
85
  # class User < ActiveRecord::Base
86
86
  # has_one_attached :avatar, service: :s3
87
87
  # end
88
88
  #
89
+ # +:service+ can also be specified as a proc, and it will be called with the model instance:
90
+ #
91
+ # class User < ActiveRecord::Base
92
+ # has_one_attached :avatar, service: ->(user) { user.in_europe_region? ? :s3_europe : :s3_usa }
93
+ # end
94
+ #
89
95
  # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
90
96
  # pass the +:strict_loading+ option. You can do:
91
97
  #
@@ -93,8 +99,12 @@ module ActiveStorage
93
99
  # has_one_attached :avatar, strict_loading: true
94
100
  # end
95
101
  #
102
+ # Note: Active Storage relies on polymorphic associations, which in turn store class names in the database.
103
+ # When renaming classes that use <tt>has_one_attached</tt>, make sure to also update the class names in the
104
+ # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
105
+ # the corresponding rows.
96
106
  def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
97
- validate_service_configuration(name, service)
107
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
98
108
 
99
109
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
100
110
  # frozen_string_literal: true
@@ -170,12 +180,18 @@ module ActiveStorage
170
180
  # +purge+ instead.
171
181
  #
172
182
  # If you need the attachment to use a service which differs from the globally configured one,
173
- # pass the +:service+ option. For instance:
183
+ # pass the +:service+ option. For example:
174
184
  #
175
185
  # class Gallery < ActiveRecord::Base
176
186
  # has_many_attached :photos, service: :s3
177
187
  # end
178
188
  #
189
+ # +:service+ can also be specified as a proc, and it will be called with the model instance:
190
+ #
191
+ # class Gallery < ActiveRecord::Base
192
+ # has_many_attached :photos, service: ->(gallery) { gallery.personal? ? :personal_s3 : :s3 }
193
+ # end
194
+ #
179
195
  # If you need to enable +strict_loading+ to prevent lazy loading of attachments,
180
196
  # pass the +:strict_loading+ option. You can do:
181
197
  #
@@ -183,8 +199,12 @@ module ActiveStorage
183
199
  # has_many_attached :photos, strict_loading: true
184
200
  # end
185
201
  #
202
+ # Note: Active Storage relies on polymorphic associations, which in turn store class names in the database.
203
+ # When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
204
+ # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
205
+ # the corresponding rows.
186
206
  def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
187
- validate_service_configuration(name, service)
207
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
188
208
 
189
209
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
190
210
  # frozen_string_literal: true
@@ -233,20 +253,22 @@ module ActiveStorage
233
253
  yield reflection if block_given?
234
254
  ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
235
255
  end
256
+ end
236
257
 
237
- private
238
- def validate_service_configuration(association_name, service)
239
- if service.present?
240
- ActiveStorage::Blob.services.fetch(service) do
241
- raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
242
- end
243
- else
244
- validate_global_service_configuration
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}"
245
263
  end
264
+ else
265
+ validate_global_service_configuration(model_class)
246
266
  end
267
+ end
247
268
 
248
- def validate_global_service_configuration
249
- if connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
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?
250
272
  raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
251
273
  end
252
274
  end
@@ -65,6 +65,8 @@ module ActiveStorage
65
65
  )
66
66
 
67
67
  config.active_storage.content_types_allowed_inline = %w(
68
+ image/webp
69
+ image/avif
68
70
  image/png
69
71
  image/gif
70
72
  image/jpeg
@@ -82,6 +84,10 @@ module ActiveStorage
82
84
  end
83
85
 
84
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
+
85
91
  config.after_initialize do |app|
86
92
  ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
87
93
  ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
@@ -115,15 +121,6 @@ module ActiveStorage
115
121
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
116
122
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
117
123
  ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
118
-
119
- unless app.config.active_storage.silence_invalid_content_types_warning.nil?
120
- ActiveStorage.silence_invalid_content_types_warning = app.config.active_storage.silence_invalid_content_types_warning
121
- end
122
-
123
- unless app.config.active_storage.replace_on_assign_to_many.nil?
124
- ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many
125
- end
126
-
127
124
  ActiveStorage.track_variants = app.config.active_storage.track_variants || false
128
125
  end
129
126
  end
@@ -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
- # === Examples
53
+ # ==== Examples
54
54
  #
55
55
  # # tests/fixtures/active_storage/blobs.yml
56
56
  # second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
@@ -8,8 +8,8 @@ module ActiveStorage
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 1
12
- TINY = 6
11
+ MINOR = 2
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -4,7 +4,11 @@ module ActiveStorage
4
4
  class Previewer::MuPDFPreviewer < Previewer
5
5
  class << self
6
6
  def accept?(blob)
7
- blob.content_type == "application/pdf" && mutool_exists?
7
+ pdf?(blob.content_type) && mutool_exists?
8
+ end
9
+
10
+ def pdf?(content_type)
11
+ Marcel::Magic.child? content_type, "application/pdf"
8
12
  end
9
13
 
10
14
  def mutool_path
@@ -12,7 +16,7 @@ module ActiveStorage
12
16
  end
13
17
 
14
18
  def mutool_exists?
15
- return @mutool_exists if defined?(@mutool_exists) && !@mutool_exists.nil?
19
+ return @mutool_exists unless @mutool_exists.nil?
16
20
 
17
21
  system mutool_path, out: File::NULL, err: File::NULL
18
22
 
@@ -4,7 +4,11 @@ module ActiveStorage
4
4
  class Previewer::PopplerPDFPreviewer < Previewer
5
5
  class << self
6
6
  def accept?(blob)
7
- blob.content_type == "application/pdf" && pdftoppm_exists?
7
+ pdf?(blob.content_type) && pdftoppm_exists?
8
+ end
9
+
10
+ def pdf?(content_type)
11
+ Marcel::Magic.child? content_type, "application/pdf"
8
12
  end
9
13
 
10
14
  def pdftoppm_path
@@ -12,7 +16,7 @@ module ActiveStorage
12
16
  end
13
17
 
14
18
  def pdftoppm_exists?
15
- return @pdftoppm_exists if defined?(@pdftoppm_exists)
19
+ return @pdftoppm_exists unless @pdftoppm_exists.nil?
16
20
 
17
21
  @pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL)
18
22
  end
@@ -10,7 +10,7 @@ module ActiveStorage
10
10
  end
11
11
 
12
12
  def ffmpeg_exists?
13
- return @ffmpeg_exists if defined?(@ffmpeg_exists)
13
+ return @ffmpeg_exists unless @ffmpeg_exists.nil?
14
14
 
15
15
  @ffmpeg_exists = system(ffmpeg_path, "-version", out: File::NULL, err: File::NULL)
16
16
  end
@@ -16,6 +16,7 @@ module ActiveStorage
16
16
 
17
17
  def initialize(bucket:, upload: {}, public: false, **options)
18
18
  @client = Aws::S3::Resource.new(**options)
19
+ @transfer_manager = Aws::S3::TransferManager.new(client: @client.client) if defined?(Aws::S3::TransferManager)
19
20
  @bucket = @client.bucket(bucket)
20
21
 
21
22
  @multipart_upload_threshold = upload.delete(:multipart_threshold) || 100.megabytes
@@ -100,7 +101,8 @@ module ActiveStorage
100
101
  def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
101
102
  content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
102
103
 
103
- object_for(destination_key).upload_stream(
104
+ upload_stream(
105
+ key: destination_key,
104
106
  content_type: content_type,
105
107
  content_disposition: content_disposition,
106
108
  part_size: MINIMUM_UPLOAD_PART_SIZE,
@@ -116,6 +118,14 @@ module ActiveStorage
116
118
  end
117
119
 
118
120
  private
121
+ def upload_stream(key:, **options, &block)
122
+ if @transfer_manager
123
+ @transfer_manager.upload_stream(key: key, bucket: bucket.name, **options, &block)
124
+ else
125
+ object_for(key).upload_stream(**options, &block)
126
+ end
127
+ end
128
+
119
129
  def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
120
130
  object_for(key).presigned_url :get, expires_in: expires_in.to_i,
121
131
  response_content_disposition: content_disposition_with(type: disposition, filename: filename),
@@ -126,7 +136,6 @@ module ActiveStorage
126
136
  object_for(key).public_url(**client_opts)
127
137
  end
128
138
 
129
-
130
139
  MAXIMUM_UPLOAD_PARTS_COUNT = 10000
131
140
  MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
132
141
 
@@ -139,12 +148,18 @@ module ActiveStorage
139
148
  def upload_with_multipart(key, io, content_type: nil, content_disposition: nil, custom_metadata: {})
140
149
  part_size = [ io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE ].max
141
150
 
142
- object_for(key).upload_stream(content_type: content_type, content_disposition: content_disposition, part_size: part_size, metadata: custom_metadata, **upload_options) do |out|
151
+ upload_stream(
152
+ key: key,
153
+ content_type: content_type,
154
+ content_disposition: content_disposition,
155
+ part_size: part_size,
156
+ metadata: custom_metadata,
157
+ **upload_options
158
+ ) do |out|
143
159
  IO.copy_stream(io, out)
144
160
  end
145
161
  end
146
162
 
147
-
148
163
  def object_for(key)
149
164
  bucket.object(key)
150
165
  end
@@ -5,7 +5,7 @@ begin
5
5
  rescue LoadError
6
6
  raise LoadError, <<~ERROR.squish
7
7
  Generating image variants require the image_processing gem.
8
- Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
8
+ Please add `gem "image_processing", "~> 1.2"` to your Gemfile.
9
9
  ERROR
10
10
  end
11
11
 
@@ -351,6 +351,7 @@ module ActiveStorage
351
351
  mattr_accessor :unsupported_image_processing_arguments
352
352
 
353
353
  mattr_accessor :service_urls_expire_in, default: 5.minutes
354
+ mattr_accessor :touch_attachment_records, default: true
354
355
  mattr_accessor :urls_expire_in
355
356
 
356
357
  mattr_accessor :routes_prefix, default: "/rails/active_storage"
@@ -361,22 +362,6 @@ module ActiveStorage
361
362
 
362
363
  mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"
363
364
 
364
- def self.replace_on_assign_to_many
365
- ActiveStorage.deprecator.warn("config.active_storage.replace_on_assign_to_many is deprecated and has no effect.")
366
- end
367
-
368
- def self.replace_on_assign_to_many=(value)
369
- ActiveStorage.deprecator.warn("config.active_storage.replace_on_assign_to_many is deprecated and has no effect.")
370
- end
371
-
372
- def self.silence_invalid_content_types_warning
373
- ActiveStorage.deprecator.warn("config.active_storage.silence_invalid_content_types_warning is deprecated and has no effect.")
374
- end
375
-
376
- def self.silence_invalid_content_types_warning=(value)
377
- ActiveStorage.deprecator.warn("config.active_storage.silence_invalid_content_types_warning is deprecated and has no effect.")
378
- end
379
-
380
365
  module Transformers
381
366
  extend ActiveSupport::Autoload
382
367
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.6
4
+ version: 7.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,56 +15,56 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 7.1.6
18
+ version: 7.2.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 7.1.6
25
+ version: 7.2.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: actionpack
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 7.1.6
32
+ version: 7.2.3
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 7.1.6
39
+ version: 7.2.3
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: activejob
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 7.1.6
46
+ version: 7.2.3
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 7.1.6
53
+ version: 7.2.3
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: activerecord
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - '='
59
59
  - !ruby/object:Gem::Version
60
- version: 7.1.6
60
+ version: 7.2.3
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - '='
66
66
  - !ruby/object:Gem::Version
67
- version: 7.1.6
67
+ version: 7.2.3
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: marcel
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -189,10 +189,10 @@ licenses:
189
189
  - MIT
190
190
  metadata:
191
191
  bug_tracker_uri: https://github.com/rails/rails/issues
192
- changelog_uri: https://github.com/rails/rails/blob/v7.1.6/activestorage/CHANGELOG.md
193
- documentation_uri: https://api.rubyonrails.org/v7.1.6/
192
+ changelog_uri: https://github.com/rails/rails/blob/v7.2.3/activestorage/CHANGELOG.md
193
+ documentation_uri: https://api.rubyonrails.org/v7.2.3/
194
194
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
195
- source_code_uri: https://github.com/rails/rails/tree/v7.1.6/activestorage
195
+ source_code_uri: https://github.com/rails/rails/tree/v7.2.3/activestorage
196
196
  rubygems_mfa_required: 'true'
197
197
  rdoc_options: []
198
198
  require_paths:
@@ -201,7 +201,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
201
201
  requirements:
202
202
  - - ">="
203
203
  - !ruby/object:Gem::Version
204
- version: 2.7.0
204
+ version: 3.1.0
205
205
  required_rubygems_version: !ruby/object:Gem::Requirement
206
206
  requirements:
207
207
  - - ">="