activestorage 7.1.3.4 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.