activestorage 8.1.0.beta1 → 8.1.0.rc1

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: 8959531cd3a50439e1d30882ac0398b7d361da8a6859049b01c31e1a35e58862
4
- data.tar.gz: c4027f992ab9f941cecbe88a7a415d52f5a3800b86e69fe8789567f320a9b9ea
3
+ metadata.gz: 5fdd6b9b99061ff4e07861b93dbf9136f8f38f8e424a7225fcb2ad920e26d397
4
+ data.tar.gz: a9187612f84e46a680fed02e26e279154a3ce93d3bba051e2f128d81a3bc0fcd
5
5
  SHA512:
6
- metadata.gz: 2750ae72f60c4e388b74813b74fa2580a5ebe48887eca53905b1d1173a08bac4ff931f22fadc3fb2ffec0d96417f7661fe22aba44cdaa36e7480c7df4b0f52d0
7
- data.tar.gz: 3201606c2dce4e6e41649c8aca6432dbd2c0635c966366809bc6552ad07cc074596d3d973054bce87f79f3eca9ffb68d91e116d9b8f81ac5dad7d59b8040cd5b
6
+ metadata.gz: '08713db028801c930b720d6ed6d498218f85d62d6b8fb4d5d209bbdbb3e193dd68cbb6969cb42cd3078c0c38d6822408b26df59076892f49a077b5442c8f84ce'
7
+ data.tar.gz: 8ce0d30a68bc92bf8e34b14b6d638cf03c09873b0b925a307d5a43ac3cd345cd63d0cf3d91d358652ec86593a5bf02f4a970be744cca9f6a0e20d757df4350ae
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## Rails 8.1.0.rc1 (October 15, 2025) ##
2
+
3
+ * Add structured events for Active Storage:
4
+ - `active_storage.service_upload`
5
+ - `active_storage.service_download`
6
+ - `active_storage.service_streaming_download`
7
+ - `active_storage.preview`
8
+ - `active_storage.service_delete`
9
+ - `active_storage.service_delete_prefixed`
10
+ - `active_storage.service_exist`
11
+ - `active_storage.service_url`
12
+ - `active_storage.service_mirror`
13
+
14
+ *Gannon McGibbon*
15
+
16
+ * Allow analyzers and variant transformer to be fully configurable
17
+
18
+ ```ruby
19
+ # ActiveStorage.analyzers can be set to an empty array:
20
+ config.active_storage.analyzers = []
21
+ # => ActiveStorage.analyzers = []
22
+
23
+ # or use custom analyzer:
24
+ config.active_storage.analyzers = [ CustomAnalyzer ]
25
+ # => ActiveStorage.analyzers = [ CustomAnalyzer ]
26
+ ```
27
+
28
+ If no configuration is provided, it will use the default analyzers.
29
+
30
+ You can also disable variant processor to remove warnings on startup about missing gems.
31
+
32
+ ```ruby
33
+ config.active_storage.variant_processor = :disabled
34
+ ```
35
+
36
+ *zzak*, *Alexandre Ruban*
37
+
1
38
  ## Rails 8.1.0.beta1 (September 04, 2025) ##
2
39
 
3
40
  * Remove deprecated `:azure` storage service.
@@ -40,13 +77,6 @@
40
77
 
41
78
  *Sean Doyle*
42
79
 
43
- * Add support for alternative MD5 implementation through `config.active_storage.checksum_implementation`.
44
-
45
- Also automatically degrade to using the slower `Digest::MD5` implementation if `OpenSSL::Digest::MD5`
46
- is found to be disabled because of OpenSSL FIPS mode.
47
-
48
- *Matt Pasquini*, *Jean Boussier*
49
-
50
80
  * A Blob will no longer autosave associated Attachment.
51
81
 
52
82
  This fixes an issue where a record with an attachment would have
@@ -25,8 +25,8 @@ module ActiveStorage::Blob::Representable
25
25
  #
26
26
  # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
27
27
  #
28
- # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
29
- # can then produce on-demand.
28
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
29
+ # or ActiveStorage::Representations::RedirectController can then produce on-demand.
30
30
  #
31
31
  # Raises ActiveStorage::InvariableError if the variant processor cannot
32
32
  # transform the blob. To determine whether a blob is variable, call
@@ -332,7 +332,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
332
332
  def compute_checksum_in_chunks(io)
333
333
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)
334
334
 
335
- ActiveStorage.checksum_implementation.new.tap do |checksum|
335
+ OpenSSL::Digest::MD5.new.tap do |checksum|
336
336
  read_buffer = "".b
337
337
  while io.read(5.megabytes, read_buffer)
338
338
  checksum << read_buffer
@@ -22,15 +22,15 @@
22
22
  # Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
23
23
  # you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
24
24
  # in a template, for example. Delay the processing to an on-demand controller, like the one provided in
25
- # ActiveStorage::RepresentationsController.
25
+ # ActiveStorage::Representations::ProxyController and ActiveStorage::Representations::RedirectController.
26
26
  #
27
27
  # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
28
28
  # by Active Storage like so:
29
29
  #
30
30
  # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
31
31
  #
32
- # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
33
- # can then produce on-demand.
32
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
33
+ # or ActiveStorage::Representations::RedirectController can then produce on-demand.
34
34
  #
35
35
  # When you do want to actually produce the variant needed, call +processed+. This will check that the variant
36
36
  # has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
@@ -77,8 +77,8 @@ class ActiveStorage::Variant
77
77
  # Returns the URL of the blob variant on the service. See ActiveStorage::Blob#url for details.
78
78
  #
79
79
  # Use <tt>url_for(variant)</tt> (or the implied form, like <tt>link_to variant</tt> or <tt>redirect_to variant</tt>) to get the stable URL
80
- # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
81
- # for its redirection.
80
+ # for a variant that points to the ActiveStorage::Representations::ProxyController or ActiveStorage::Representations::RedirectController,
81
+ # which in turn will use this +service_call+ method for its redirection.
82
82
  def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
83
83
  service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
84
84
  end
@@ -3,7 +3,9 @@
3
3
  begin
4
4
  gem "mini_magick"
5
5
  require "mini_magick"
6
+ ActiveStorage::MINIMAGICK_AVAILABLE = true # :nodoc:
6
7
  rescue LoadError => error
8
+ ActiveStorage::MINIMAGICK_AVAILABLE = false # :nodoc:
7
9
  raise error unless error.message.include?("mini_magick")
8
10
  end
9
11
 
@@ -11,8 +13,17 @@ module ActiveStorage
11
13
  # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
12
14
  # the {ImageMagick}[http://www.imagemagick.org] system library.
13
15
  class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
16
+ def self.accept?(blob)
17
+ super && ActiveStorage.variant_processor == :mini_magick
18
+ end
19
+
14
20
  private
15
21
  def read_image
22
+ unless MINIMAGICK_AVAILABLE
23
+ logger.error "Skipping image analysis because the mini_magick gem isn't installed"
24
+ return {}
25
+ end
26
+
16
27
  download_blob_to_tempfile do |file|
17
28
  image = instrument("mini_magick") do
18
29
  MiniMagick::Image.new(file.path)
@@ -1,18 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ require "nokogiri"
5
+ rescue LoadError
6
+ # Ensure nokogiri is loaded before vips, which also depends on libxml2.
7
+ # See Nokogiri RFC: Stop exporting symbols:
8
+ # https://github.com/sparklemotion/nokogiri/discussions/2746
9
+ end
10
+
3
11
  begin
4
12
  gem "ruby-vips"
5
13
  require "ruby-vips"
14
+ ActiveStorage::VIPS_AVAILABLE = true # :nodoc:
6
15
  rescue LoadError => error
7
- raise error unless error.message.include?("ruby-vips")
16
+ ActiveStorage::VIPS_AVAILABLE = false # :nodoc:
17
+ raise error unless error.message.match?(/libvips|ruby-vips/)
8
18
  end
9
19
 
10
20
  module ActiveStorage
11
21
  # This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem. Ruby-vips requires
12
22
  # the {libvips}[https://libvips.github.io/libvips/] system library.
13
23
  class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
24
+ def self.accept?(blob)
25
+ super && ActiveStorage.variant_processor == :vips
26
+ end
27
+
14
28
  private
15
29
  def read_image
30
+ unless VIPS_AVAILABLE
31
+ logger.error "Skipping image analysis because the ruby-vips gem isn't installed"
32
+ return {}
33
+ end
34
+
16
35
  download_blob_to_tempfile do |file|
17
36
  image = instrument("vips") do
18
37
  # ruby-vips will raise Vips::Error if it can't find an appropriate loader for the file
@@ -35,7 +35,7 @@ module ActiveStorage
35
35
  end
36
36
 
37
37
  def verify_integrity_of(file, checksum:)
38
- unless ActiveStorage.checksum_implementation.file(file).base64digest == checksum
38
+ unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
39
39
  raise ActiveStorage::IntegrityError
40
40
  end
41
41
  end
@@ -25,7 +25,7 @@ module ActiveStorage
25
25
 
26
26
  config.active_storage = ActiveSupport::OrderedOptions.new
27
27
  config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
28
- config.active_storage.analyzers = [ ActiveStorage::Analyzer::VideoAnalyzer, ActiveStorage::Analyzer::AudioAnalyzer ]
28
+ config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer::Vips, ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick, ActiveStorage::Analyzer::VideoAnalyzer, ActiveStorage::Analyzer::AudioAnalyzer ]
29
29
  config.active_storage.paths = ActiveSupport::OrderedOptions.new
30
30
  config.active_storage.queues = ActiveSupport::InheritableOptions.new
31
31
  config.active_storage.precompile_assets = true
@@ -88,26 +88,20 @@ module ActiveStorage
88
88
 
89
89
  config.after_initialize do |app|
90
90
  ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
91
- ActiveStorage.variant_processor = app.config.active_storage.variant_processor
91
+ ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
92
92
  ActiveStorage.previewers = app.config.active_storage.previewers || []
93
+ ActiveStorage.analyzers = app.config.active_storage.analyzers || []
93
94
 
94
95
  begin
95
- analyzer, transformer =
96
+ ActiveStorage.variant_transformer =
96
97
  case ActiveStorage.variant_processor
98
+ when :disabled
99
+ ActiveStorage::Transformers::NullTransformer
97
100
  when :vips
98
- [
99
- ActiveStorage::Analyzer::ImageAnalyzer::Vips,
100
- ActiveStorage::Transformers::Vips
101
- ]
101
+ ActiveStorage::Transformers::Vips
102
102
  when :mini_magick
103
- [
104
- ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick,
105
- ActiveStorage::Transformers::ImageMagick
106
- ]
103
+ ActiveStorage::Transformers::ImageMagick
107
104
  end
108
-
109
- ActiveStorage.analyzers = [analyzer].compact.concat(app.config.active_storage.analyzers || [])
110
- ActiveStorage.variant_transformer = transformer
111
105
  rescue LoadError => error
112
106
  case error.message
113
107
  when /libvips/
@@ -118,7 +112,8 @@ module ActiveStorage
118
112
  when /image_processing/
119
113
  ActiveStorage.logger.warn <<~WARNING.squish
120
114
  Generating image variants require the image_processing gem.
121
- Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
115
+ Please add `gem "image_processing", "~> 1.2"` to your Gemfile
116
+ or set `config.active_storage.variant_processor = :disabled`.
122
117
  WARNING
123
118
  else
124
119
  raise
@@ -154,9 +149,6 @@ module ActiveStorage
154
149
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
155
150
  ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
156
151
  ActiveStorage.track_variants = app.config.active_storage.track_variants || false
157
- if app.config.active_storage.checksum_implementation
158
- ActiveStorage.checksum_implementation = app.config.active_storage.checksum_implementation
159
- end
160
152
  end
161
153
  end
162
154
 
@@ -10,7 +10,7 @@ module ActiveStorage
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
12
  TINY = 0
13
- PRE = "beta1"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -3,7 +3,7 @@
3
3
  require "active_support/log_subscriber"
4
4
 
5
5
  module ActiveStorage
6
- class LogSubscriber < ActiveSupport::LogSubscriber
6
+ class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
7
7
  def service_upload(event)
8
8
  message = "Uploaded file to key: #{key_in(event)}"
9
9
  message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
@@ -161,7 +161,7 @@ module ActiveStorage
161
161
  end
162
162
 
163
163
  def ensure_integrity_of(key, checksum)
164
- unless ActiveStorage.checksum_implementation.file(path_for(key)).base64digest == checksum
164
+ unless OpenSSL::Digest::MD5.file(path_for(key)).base64digest == checksum
165
165
  delete key
166
166
  raise ActiveStorage::IntegrityError
167
167
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_storage/log_subscriber"
4
+ require "active_storage/structured_event_subscriber"
4
5
  require "active_storage/downloader"
5
6
  require "action_dispatch"
6
7
  require "action_dispatch/http/content_disposition"
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveStorage
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ def service_upload(event)
8
+ emit_event("active_storage.service_upload",
9
+ key: event.payload[:key],
10
+ checksum: event.payload[:checksum],
11
+ duration_ms: event.duration.round(2),
12
+ )
13
+ end
14
+
15
+ def service_download(event)
16
+ emit_event("active_storage.service_download",
17
+ key: event.payload[:key],
18
+ duration_ms: event.duration.round(2),
19
+ )
20
+ end
21
+
22
+ def service_streaming_download(event)
23
+ emit_event("active_storage.service_streaming_download",
24
+ key: event.payload[:key],
25
+ duration_ms: event.duration.round(2),
26
+ )
27
+ end
28
+
29
+ def preview(event)
30
+ emit_event("active_storage.preview",
31
+ key: event.payload[:key],
32
+ duration_ms: event.duration.round(2),
33
+ )
34
+ end
35
+
36
+ def service_delete(event)
37
+ emit_event("active_storage.service_delete",
38
+ key: event.payload[:key],
39
+ duration_ms: event.duration.round(2),
40
+ )
41
+ end
42
+
43
+ def service_delete_prefixed(event)
44
+ emit_event("active_storage.service_delete_prefixed",
45
+ prefix: event.payload[:prefix],
46
+ duration_ms: event.duration.round(2),
47
+ )
48
+ end
49
+
50
+ def service_exist(event)
51
+ emit_debug_event("active_storage.service_exist",
52
+ key: event.payload[:key],
53
+ exist: event.payload[:exist],
54
+ duration_ms: event.duration.round(2),
55
+ )
56
+ end
57
+ debug_only :service_exist
58
+
59
+ def service_url(event)
60
+ emit_debug_event("active_storage.service_url",
61
+ key: event.payload[:key],
62
+ url: event.payload[:url],
63
+ duration_ms: event.duration.round(2),
64
+ )
65
+ end
66
+ debug_only :service_url
67
+
68
+ def service_mirror(event)
69
+ emit_debug_event("active_storage.service_mirror",
70
+ key: event.payload[:key],
71
+ checksum: event.payload[:checksum],
72
+ duration_ms: event.duration.round(2),
73
+ )
74
+ end
75
+ debug_only :service_mirror
76
+ end
77
+ end
78
+
79
+ ActiveStorage::StructuredEventSubscriber.attach_to :active_storage
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module Transformers
5
+ class NullTransformer < Transformer # :nodoc:
6
+ private
7
+ def process(file, format:)
8
+ file
9
+ end
10
+ end
11
+ end
12
+ end
@@ -362,21 +362,13 @@ module ActiveStorage
362
362
 
363
363
  mattr_accessor :track_variants, default: false
364
364
 
365
- singleton_class.attr_accessor :checksum_implementation
366
- @checksum_implementation = OpenSSL::Digest::MD5
367
- begin
368
- @checksum_implementation.hexdigest("test")
369
- rescue # OpenSSL may have MD5 disabled
370
- require "digest/md5"
371
- @checksum_implementation = Digest::MD5
372
- end
373
-
374
365
  mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"
375
366
 
376
367
  module Transformers
377
368
  extend ActiveSupport::Autoload
378
369
 
379
370
  autoload :Transformer
371
+ autoload :NullTransformer
380
372
  autoload :ImageProcessingTransformer
381
373
  autoload :Vips
382
374
  autoload :ImageMagick
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: 8.1.0.beta1
4
+ version: 8.1.0.rc1
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: 8.1.0.beta1
18
+ version: 8.1.0.rc1
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: 8.1.0.beta1
25
+ version: 8.1.0.rc1
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: 8.1.0.beta1
32
+ version: 8.1.0.rc1
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: 8.1.0.beta1
39
+ version: 8.1.0.rc1
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: 8.1.0.beta1
46
+ version: 8.1.0.rc1
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: 8.1.0.beta1
53
+ version: 8.1.0.rc1
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: 8.1.0.beta1
60
+ version: 8.1.0.rc1
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: 8.1.0.beta1
67
+ version: 8.1.0.rc1
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: marcel
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -179,8 +179,10 @@ files:
179
179
  - lib/active_storage/service/mirror_service.rb
180
180
  - lib/active_storage/service/registry.rb
181
181
  - lib/active_storage/service/s3_service.rb
182
+ - lib/active_storage/structured_event_subscriber.rb
182
183
  - lib/active_storage/transformers/image_magick.rb
183
184
  - lib/active_storage/transformers/image_processing_transformer.rb
185
+ - lib/active_storage/transformers/null_transformer.rb
184
186
  - lib/active_storage/transformers/transformer.rb
185
187
  - lib/active_storage/transformers/vips.rb
186
188
  - lib/active_storage/version.rb
@@ -190,10 +192,10 @@ licenses:
190
192
  - MIT
191
193
  metadata:
192
194
  bug_tracker_uri: https://github.com/rails/rails/issues
193
- changelog_uri: https://github.com/rails/rails/blob/v8.1.0.beta1/activestorage/CHANGELOG.md
194
- documentation_uri: https://api.rubyonrails.org/v8.1.0.beta1/
195
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.0.rc1/activestorage/CHANGELOG.md
196
+ documentation_uri: https://api.rubyonrails.org/v8.1.0.rc1/
195
197
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
196
- source_code_uri: https://github.com/rails/rails/tree/v8.1.0.beta1/activestorage
198
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.0.rc1/activestorage
197
199
  rubygems_mfa_required: 'true'
198
200
  rdoc_options: []
199
201
  require_paths: