activestorage 7.1.3.4 → 7.2.0

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: 10c31beefd192ae50216812e96dd7326c6d301f84f3a9ac4c42cf41369bfbd68
4
- data.tar.gz: b744929e6b8cab0e2c2d2f834b453d538fad962a42b6b52f62ed74cf67fd000b
3
+ metadata.gz: da8d7f2cbc1f8d032c38af7720a92c07f024dc9ccbe0f67ff3daadd8dc2613e9
4
+ data.tar.gz: ed3bd8cc7f51aa26e1247ce9182ec0f1a81871b5e0c854ef7c974065f6d71134
5
5
  SHA512:
6
- metadata.gz: 93dcf40c82c8a7ecb7308c9e90c42a22816876cfe31c42304369a3979b4a6153a6296a8509d60a9e28bccdd0789f299ef3a734cb0b1b46ca1bd1cbd3c5f411ea
7
- data.tar.gz: 56d0c210295b80a5bd0fa4145901652ddb3d1baede6a22993a294ae4031a378e99c45775e73caefa8890cf6a5bdafb224c215fc12ca33c20491a0cdf1b201b17
6
+ metadata.gz: 19f6c085a1a778c4021f0c66dc2b20ce3062e1f56f05eb38887bb4907dac8272819b5a29ac31aaef924f4ea70ede606da6d29de87a9ea2a4c09b718cfc3e34c6
7
+ data.tar.gz: 1e0156a1428ed9d779ddfd0bf67222796b10ce969b5affad4432e543dcc37cad736c2d2c484457cf465ab257da700ca92a55679d716c88084f929f1387205d62
data/CHANGELOG.md CHANGED
@@ -1,24 +1,25 @@
1
- ## Rails 7.1.3.4 (June 04, 2024) ##
1
+ ## Rails 7.2.0 (August 09, 2024) ##
2
2
 
3
- * No changes.
3
+ * Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
4
4
 
5
+ *Rafael Mendonça França*
5
6
 
6
- ## Rails 7.1.3.3 (May 16, 2024) ##
7
-
8
- * No changes.
9
-
7
+ * Remove deprecated `config.active_storage.replace_on_assign_to_many`.
10
8
 
11
- ## Rails 7.1.3.2 (February 21, 2024) ##
9
+ *Rafael Mendonça França*
12
10
 
13
- * No changes.
11
+ * Add support for custom `key` in `ActiveStorage::Blob#compose`.
14
12
 
13
+ *Elvin Efendiev*
15
14
 
16
- ## Rails 7.1.3.1 (February 21, 2024) ##
15
+ * Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
16
+ is set.
17
17
 
18
- * No changes.
18
+ *Lewis Buckley*
19
19
 
20
+ * Fix JSON-encoding of `ActiveStorage::Filename` instances.
20
21
 
21
- ## Rails 7.1.3 (January 16, 2024) ##
22
+ *Jonathan del Strother*
22
23
 
23
24
  * Fix N+1 query when fetching preview images for non-image assets.
24
25
 
@@ -39,16 +40,20 @@
39
40
 
40
41
  *Chedli Bourguiba*
41
42
 
42
- * Fix direct upload forms when submit button contains nested elements.
43
-
44
- *Marc Köhlbrugge*
45
-
46
43
  * When using the `preprocessed: true` option, avoid enqueuing transform jobs
47
44
  for blobs that are not representable.
48
45
 
49
46
  *Chedli Bourguiba*
50
47
 
48
+ * Prevent `ActiveStorage::Blob#preview` to generate a variant if an empty variation is passed.
49
+
50
+ Calls to `#url`, `#key` or `#download` will now use the original preview
51
+ image instead of generating a variant with the exact same dimensions.
52
+
53
+ *Chedli Bourguiba*
54
+
51
55
  * Process preview image variant when calling `ActiveStorage::Preview#processed`.
56
+
52
57
  For example, `attached_pdf.preview(:thumb).processed` will now immediately
53
58
  generate the full-sized preview image and the `:thumb` variant of it.
54
59
  Previously, the `:thumb` variant would not be generated until a further call
@@ -66,266 +71,8 @@
66
71
 
67
72
  *Nico Wenterodt*
68
73
 
74
+ * Allow accepting `service` as a proc as well in `has_one_attached` and `has_many_attached`.
69
75
 
70
- ## Rails 7.1.2 (November 10, 2023) ##
71
-
72
- * No changes.
73
-
74
-
75
- ## Rails 7.1.1 (October 11, 2023) ##
76
-
77
- * No changes.
78
-
79
-
80
- ## Rails 7.1.0 (October 05, 2023) ##
81
-
82
- * No changes.
83
-
84
-
85
- ## Rails 7.1.0.rc2 (October 01, 2023) ##
86
-
87
- * No changes.
88
-
89
-
90
- ## Rails 7.1.0.rc1 (September 27, 2023) ##
91
-
92
- * Add `expires_at` option to `ActiveStorage::Blob#signed_id`.
93
-
94
- ```ruby
95
- rails_blob_path(user.avatar, disposition: "attachment", expires_at: 30.minutes.from_now)
96
- <%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 30.minutes.from_now) %>
97
- ```
98
-
99
- *Aki*
100
-
101
- * Allow attaching File and Pathname when assigning attributes, e.g.
102
-
103
- ```ruby
104
- User.create!(avatar: File.open("image.jpg"))
105
- User.create!(avatar: file_fixture("image.jpg"))
106
- ```
107
-
108
- *Dorian Marié*
109
-
110
-
111
- ## Rails 7.1.0.beta1 (September 13, 2023) ##
112
-
113
- * Disables the session in `ActiveStorage::Blobs::ProxyController`
114
- and `ActiveStorage::Representations::ProxyController`
115
- in order to allow caching by default in some CDNs as CloudFlare
116
-
117
- Fixes #44136
118
-
119
- *Bruno Prieto*
120
-
121
- * Add `tags` to `ActiveStorage::Analyzer::AudioAnalyzer` output
122
-
123
- *Keaton Roux*
124
-
125
- * Add an option to preprocess variants
126
-
127
- ActiveStorage variants are processed on the fly when they are needed but
128
- sometimes we're sure that they are accessed and want to processed them
129
- upfront.
130
-
131
- `preprocessed` option is added when declaring variants.
132
-
133
- ```
134
- class User < ApplicationRecord
135
- has_one_attached :avatar do |attachable|
136
- attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
137
- end
138
- end
139
- ```
140
-
141
- *Shouichi Kamiya*
142
-
143
- * Fix variants not included when eager loading multiple records containing a single attachment
144
-
145
- When using the `with_attached_#{name}` scope for a `has_one_attached` relation,
146
- attachment variants were not eagerly loaded.
147
-
148
- *Russell Porter*
149
-
150
- * Allow an ActiveStorage attachment to be removed via a form post
151
-
152
- Attachments can already be removed by updating the attachment to be nil such as:
153
- ```ruby
154
- User.find(params[:id]).update!(avatar: nil)
155
- ```
156
-
157
- However, a form cannot post a nil param, it can only post an empty string. But, posting an
158
- empty string would result in an `ActiveSupport::MessageVerifier::InvalidSignature: mismatched digest`
159
- error being raised, because it's being treated as a signed blob id.
160
-
161
- Now, nil and an empty string are treated as a delete, which allows attachments to be removed via:
162
- ```ruby
163
- User.find(params[:id]).update!(params.require(:user).permit(:avatar))
164
-
165
- ```
166
-
167
- *Nate Matykiewicz*
168
-
169
- * Remove mini_mime usage in favour of marcel.
170
-
171
- We have two libraries that are have similar usage. This change removes
172
- dependency on mini_mime and makes use of similar methods from marcel.
173
-
174
- *Vipul A M*
175
-
176
- * Allow destroying active storage variants
177
-
178
- ```ruby
179
- User.first.avatar.variant(resize_to_limit: [100, 100]).destroy
180
- ```
181
-
182
- *Shouichi Kamiya*, *Yuichiro NAKAGAWA*, *Ryohei UEDA*
183
-
184
- * Add `sample_rate` to `ActiveStorage::Analyzer::AudioAnalyzer` output
185
-
186
- *Matija Čupić*
187
-
188
- * Remove deprecated `purge` and `purge_later` methods from the attachments association.
189
-
190
- *Rafael Mendonça França*
191
-
192
- * Remove deprecated behavior when assigning to a collection of attachments.
193
-
194
- Instead of appending to the collection, the collection is now replaced.
195
-
196
- *Rafael Mendonça França*
197
-
198
- * Remove deprecated `ActiveStorage::Current#host` and `ActiveStorage::Current#host=` methods.
199
-
200
- *Rafael Mendonça França*
201
-
202
- * Remove deprecated invalid default content types in Active Storage configurations.
203
-
204
- *Rafael Mendonça França*
205
-
206
- * Add missing preview event to `ActiveStorage::LogSubscriber`
207
-
208
- A `preview` event is being instrumented in `ActiveStorage::Previewer`.
209
- However it was not added inside ActiveStorage's LogSubscriber class.
210
-
211
- This will allow to have logs for when a preview happens
212
- in the same fashion as all other ActiveStorage events such as
213
- `upload` and `download` inside `Rails.logger`.
214
-
215
- *Chedli Bourguiba*
216
-
217
- * Fix retrieving rotation value from FFmpeg on version 5.0+.
218
-
219
- In FFmpeg version 5.0+ the rotation value has been removed from tags.
220
- Instead the value can be found in side_data_list. Along with
221
- this update it's possible to have values of -90, -270 to denote the video
222
- has been rotated.
223
-
224
- *Haroon Ahmed*
225
-
226
- * Touch all corresponding model records after ActiveStorage::Blob is analyzed
227
-
228
- This fixes a race condition where a record can be requested and have a cache entry built, before
229
- the initial `analyze_later` completes, which will not be invalidated until something else
230
- updates the record. This also invalidates cache entries when a blob is re-analyzed, which
231
- is helpful if a bug is fixed in an analyzer or a new analyzer is added.
232
-
233
- *Nate Matykiewicz*
234
-
235
- * Add ability to use pre-defined variants when calling `preview` or
236
- `representation` on an attachment.
237
-
238
- ```ruby
239
- class User < ActiveRecord::Base
240
- has_one_attached :file do |attachable|
241
- attachable.variant :thumb, resize_to_limit: [100, 100]
242
- end
243
- end
244
-
245
- <%= image_tag user.file.representation(:thumb) %>
246
- ```
247
-
248
- *Richard Böhme*
249
-
250
- * Method `attach` always returns the attachments except when the record
251
- is persisted, unchanged, and saving it fails, in which case it returns `nil`.
252
-
253
- *Santiago Bartesaghi*
254
-
255
- * Fixes multiple `attach` calls within transaction not uploading files correctly.
256
-
257
- In the following example, the code failed to upload all but the last file to the configured service.
258
- ```ruby
259
- ActiveRecord::Base.transaction do
260
- user.attachments.attach({
261
- content_type: "text/plain",
262
- filename: "dummy.txt",
263
- io: ::StringIO.new("dummy"),
264
- })
265
- user.attachments.attach({
266
- content_type: "text/plain",
267
- filename: "dummy2.txt",
268
- io: ::StringIO.new("dummy2"),
269
- })
270
- end
271
-
272
- assert_equal 2, user.attachments.count
273
- assert user.attachments.first.service.exist?(user.attachments.first.key) # Fails
274
- ```
275
-
276
- This was addressed by keeping track of the subchanges pending upload, and uploading them
277
- once the transaction is committed.
278
-
279
- Fixes #41661
280
-
281
- *Santiago Bartesaghi*, *Bruno Vezoli*, *Juan Roig*, *Abhay Nikam*
282
-
283
- * Raise an exception if `config.active_storage.service` is not set.
284
-
285
- If Active Storage is configured and `config.active_storage.service` is not
286
- set in the respective environment's configuration file, then an exception
287
- is raised with a meaningful message when attempting to use Active Storage.
288
-
289
- *Ghouse Mohamed*
290
-
291
- * Fixes proxy downloads of files over 5mb
292
-
293
- Previously, trying to view and/or download files larger than 5mb stored in
294
- services like S3 via proxy mode could return corrupted files at around
295
- 5.2mb or cause random halts in the download. Now,
296
- `ActiveStorage::Blobs::ProxyController` correctly handles streaming these
297
- larger files from the service to the client without any issues.
298
-
299
- Fixes #44679
300
-
301
- *Felipe Raul*
302
-
303
- * Saving attachment(s) to a record returns the blob/blobs object
304
-
305
- Previously, saving attachments did not return the blob/blobs that
306
- were attached. Now, saving attachments to a record with `#attach`
307
- method returns the blob or array of blobs that were attached to
308
- the record. If it fails to save the attachment(s), then it returns
309
- `false`.
310
-
311
- *Ghouse Mohamed*
312
-
313
- * Don't stream responses in redirect mode
314
-
315
- Previously, both redirect mode and proxy mode streamed their
316
- responses which caused a new thread to be created, and could end
317
- up leaking connections in the connection pool. But since redirect
318
- mode doesn't actually send any data, it doesn't need to be
319
- streamed.
320
-
321
- *Luke Lau*
322
-
323
- * Safe for direct upload on Libraries or Frameworks
324
-
325
- Enable the use of custom headers during direct uploads, which allows for
326
- the inclusion of Authorization bearer tokens or other forms of authorization
327
- tokens through headers.
328
-
329
- *Radamés Roriz*
76
+ *Yogesh Khater*
330
77
 
331
- Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md) for previous changes.
78
+ Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md) for previous changes.
@@ -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
@@ -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
@@ -132,8 +132,18 @@ class ActiveStorage::Attachment < ActiveStorage::Record
132
132
  end
133
133
 
134
134
  def transform_variants_later
135
- named_variants.each do |_name, named_variant|
136
- 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
137
147
  end
138
148
  end
139
149
 
@@ -146,7 +156,7 @@ class ActiveStorage::Attachment < ActiveStorage::Record
146
156
  end
147
157
 
148
158
  def named_variants
149
- record.attachment_reflections[name]&.named_variants
159
+ record.attachment_reflections[name]&.named_variants || {}
150
160
  end
151
161
 
152
162
  def transformations_by_name(transformations)
@@ -98,6 +98,14 @@ 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
110
  ActiveStorage::TransformJob.perform_later(self, transformations) if representable?
103
111
  end
@@ -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
@@ -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,39 @@ 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
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
160
171
  end
161
172
 
173
+ include Analyzable
174
+ include Identifiable
175
+ include Representable
176
+ include Servable
177
+
162
178
  # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
163
179
  def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
164
180
  super
@@ -334,8 +350,9 @@ class ActiveStorage::Blob < ActiveStorage::Record
334
350
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
335
351
 
336
352
  OpenSSL::Digest::MD5.new.tap do |checksum|
337
- while chunk = io.read(5.megabytes)
338
- checksum << chunk
353
+ read_buffer = "".b
354
+ while io.read(5.megabytes, read_buffer)
355
+ checksum << read_buffer
339
356
  end
340
357
 
341
358
  io.rewind
@@ -360,8 +377,14 @@ class ActiveStorage::Blob < ActiveStorage::Record
360
377
  end
361
378
  end
362
379
 
363
- def touch_attachment_records
364
- 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|
365
388
  attachment.touch
366
389
  end
367
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
@@ -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
+ ActiveStorage::Blob.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
@@ -74,16 +74,24 @@ module ActiveStorage
74
74
  # The system has been designed to having you go through the ActiveStorage::Attached::One
75
75
  # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
76
76
  #
77
- # If the +:dependent+ option isn't set, the attachment will be purged
78
- # (i.e. destroyed) whenever the record is destroyed.
77
+ # The +:dependent+ option defaults to +:purge_later+. This means the attachment will be
78
+ # purged (i.e. destroyed) in the background whenever the record is destroyed.
79
+ # If an ActiveJob::Backend queue adapter is not set in the application set it to
80
+ # +purge+ instead.
79
81
  #
80
82
  # If you need the attachment to use a service which differs from the globally configured one,
81
- # pass the +:service+ option. For instance:
83
+ # pass the +:service+ option. For example:
82
84
  #
83
85
  # class User < ActiveRecord::Base
84
86
  # has_one_attached :avatar, service: :s3
85
87
  # end
86
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
+ #
87
95
  # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
88
96
  # pass the +:strict_loading+ option. You can do:
89
97
  #
@@ -91,8 +99,12 @@ module ActiveStorage
91
99
  # has_one_attached :avatar, strict_loading: true
92
100
  # end
93
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.
94
106
  def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
95
- validate_service_configuration(name, service)
107
+ ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
96
108
 
97
109
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
98
110
  # frozen_string_literal: true
@@ -162,16 +174,24 @@ module ActiveStorage
162
174
  # The system has been designed to having you go through the ActiveStorage::Attached::Many
163
175
  # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
164
176
  #
165
- # If the +:dependent+ option isn't set, all the attachments will be purged
166
- # (i.e. destroyed) whenever the record is destroyed.
177
+ # The +:dependent+ option defaults to +:purge_later+. This means the attachments will be
178
+ # purged (i.e. destroyed) in the background whenever the record is destroyed.
179
+ # If an ActiveJob::Backend queue adapter is not set in the application set it to
180
+ # +purge+ instead.
167
181
  #
168
182
  # If you need the attachment to use a service which differs from the globally configured one,
169
- # pass the +:service+ option. For instance:
183
+ # pass the +:service+ option. For example:
170
184
  #
171
185
  # class Gallery < ActiveRecord::Base
172
186
  # has_many_attached :photos, service: :s3
173
187
  # end
174
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
+ #
175
195
  # If you need to enable +strict_loading+ to prevent lazy loading of attachments,
176
196
  # pass the +:strict_loading+ option. You can do:
177
197
  #
@@ -179,8 +199,12 @@ module ActiveStorage
179
199
  # has_many_attached :photos, strict_loading: true
180
200
  # end
181
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.
182
206
  def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
183
- validate_service_configuration(name, service)
207
+ ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
184
208
 
185
209
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
186
210
  # frozen_string_literal: true
@@ -229,23 +253,6 @@ module ActiveStorage
229
253
  yield reflection if block_given?
230
254
  ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
231
255
  end
232
-
233
- private
234
- def validate_service_configuration(association_name, service)
235
- if service.present?
236
- ActiveStorage::Blob.services.fetch(service) do
237
- raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
238
- end
239
- else
240
- validate_global_service_configuration
241
- end
242
- end
243
-
244
- def validate_global_service_configuration
245
- if connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
246
- raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
247
- end
248
- end
249
256
  end
250
257
 
251
258
  def attachment_changes # :nodoc:
@@ -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
@@ -110,6 +112,7 @@ module ActiveStorage
110
112
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
111
113
  ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
112
114
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
115
+ ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
113
116
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
114
117
  ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
115
118
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
@@ -8,9 +8,9 @@ module ActiveStorage
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 1
12
- TINY = 3
13
- PRE = "4"
11
+ MINOR = 2
12
+ TINY = 0
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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
@@ -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
 
@@ -35,7 +35,7 @@ require "active_storage/errors"
35
35
  require "marcel"
36
36
 
37
37
  # :markup: markdown
38
- # :include: activestorage/README.md
38
+ # :include: ../README.md
39
39
  module ActiveStorage
40
40
  extend ActiveSupport::Autoload
41
41
 
@@ -354,6 +354,7 @@ module ActiveStorage
354
354
  mattr_accessor :unsupported_image_processing_arguments
355
355
 
356
356
  mattr_accessor :service_urls_expire_in, default: 5.minutes
357
+ mattr_accessor :touch_attachment_records, default: true
357
358
  mattr_accessor :urls_expire_in
358
359
 
359
360
  mattr_accessor :routes_prefix, default: "/rails/active_storage"
@@ -364,22 +365,6 @@ module ActiveStorage
364
365
 
365
366
  mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"
366
367
 
367
- def self.replace_on_assign_to_many
368
- ActiveStorage.deprecator.warn("config.active_storage.replace_on_assign_to_many is deprecated and has no effect.")
369
- end
370
-
371
- def self.replace_on_assign_to_many=(value)
372
- ActiveStorage.deprecator.warn("config.active_storage.replace_on_assign_to_many is deprecated and has no effect.")
373
- end
374
-
375
- def self.silence_invalid_content_types_warning
376
- ActiveStorage.deprecator.warn("config.active_storage.silence_invalid_content_types_warning is deprecated and has no effect.")
377
- end
378
-
379
- def self.silence_invalid_content_types_warning=(value)
380
- ActiveStorage.deprecator.warn("config.active_storage.silence_invalid_content_types_warning is deprecated and has no effect.")
381
- end
382
-
383
368
  module Transformers
384
369
  extend ActiveSupport::Autoload
385
370
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.3.4
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-04 00:00:00.000000000 Z
11
+ date: 2024-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,56 +16,56 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.3.4
19
+ version: 7.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 7.1.3.4
26
+ version: 7.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: actionpack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 7.1.3.4
33
+ version: 7.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 7.1.3.4
40
+ version: 7.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activejob
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 7.1.3.4
47
+ version: 7.2.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 7.1.3.4
54
+ version: 7.2.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activerecord
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 7.1.3.4
61
+ version: 7.2.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 7.1.3.4
68
+ version: 7.2.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: marcel
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -116,6 +116,7 @@ files:
116
116
  - app/jobs/active_storage/analyze_job.rb
117
117
  - app/jobs/active_storage/base_job.rb
118
118
  - app/jobs/active_storage/mirror_job.rb
119
+ - app/jobs/active_storage/preview_image_job.rb
119
120
  - app/jobs/active_storage/purge_job.rb
120
121
  - app/jobs/active_storage/transform_job.rb
121
122
  - app/models/active_storage/attachment.rb
@@ -189,10 +190,10 @@ licenses:
189
190
  - MIT
190
191
  metadata:
191
192
  bug_tracker_uri: https://github.com/rails/rails/issues
192
- changelog_uri: https://github.com/rails/rails/blob/v7.1.3.4/activestorage/CHANGELOG.md
193
- documentation_uri: https://api.rubyonrails.org/v7.1.3.4/
193
+ changelog_uri: https://github.com/rails/rails/blob/v7.2.0/activestorage/CHANGELOG.md
194
+ documentation_uri: https://api.rubyonrails.org/v7.2.0/
194
195
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
195
- source_code_uri: https://github.com/rails/rails/tree/v7.1.3.4/activestorage
196
+ source_code_uri: https://github.com/rails/rails/tree/v7.2.0/activestorage
196
197
  rubygems_mfa_required: 'true'
197
198
  post_install_message:
198
199
  rdoc_options: []
@@ -202,14 +203,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
203
  requirements:
203
204
  - - ">="
204
205
  - !ruby/object:Gem::Version
205
- version: 2.7.0
206
+ version: 3.1.0
206
207
  required_rubygems_version: !ruby/object:Gem::Requirement
207
208
  requirements:
208
209
  - - ">="
209
210
  - !ruby/object:Gem::Version
210
211
  version: '0'
211
212
  requirements: []
212
- rubygems_version: 3.3.27
213
+ rubygems_version: 3.5.11
213
214
  signing_key:
214
215
  specification_version: 4
215
216
  summary: Local and cloud file storage framework.