activestorage 8.0.2.1 → 8.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bbd02c5f4a3b3d7b0944ed4846eaf10e7732224767d0ba3bbab219397ea2dee
4
- data.tar.gz: 74d1f142bbbc49fbc83bf956e7ef996d16a56b6ce58e65521296c4f049c88661
3
+ metadata.gz: 022126fe7ad33aff5dd19e9cb369c9a4264b20b87e33af853f07cf8946bc6b4a
4
+ data.tar.gz: e32c5d37045bb9e94d86ae04c24b290a538fe9931eca6d8debdbc8d5d049cdd4
5
5
  SHA512:
6
- metadata.gz: c2e0e50977527a52df9e9c8c91a86cef573e79cb8bdcb4decc2fca0f6712e1ac59da4a99b6b1908ecf478f27d072aa4c94969199a192cc751c6cda3b37e6556c
7
- data.tar.gz: 797b1e3a07e006ff8a80ca7af37ea45bcf25f69feb3f4dd03d56c5cc01364dfd4cc318c9370e68cbbb8aa5c46f2ffb3e1e83261dfe99b6f3c2b79d0606f0c4b1
6
+ metadata.gz: 16d1b5343105d7b22c8b3e85b2d3200ab1fa9d4b88e0313a2154f26b0a8dcfcf3039ad86acaad6a631bccb6e91bf560f570c5e0ab9f962881edf7d52d75da5ba
7
+ data.tar.gz: f6c5e9f142ee4ddce2be8cb7ae89009d60249ab454160ed3ab4be9fdc55336d2021f9ba411957bdb1aa128d0c7de74d5b4a45e9b5cd4c26500abc75fe0903945
data/CHANGELOG.md CHANGED
@@ -1,15 +1,21 @@
1
- ## Rails 8.0.2.1 (August 13, 2025) ##
1
+ ## Rails 8.0.3 (September 22, 2025) ##
2
2
 
3
- Remove dangerous transformations
3
+ * Address deprecation of `Aws::S3::Object#upload_stream` in `ActiveStorage::Service::S3Service`.
4
4
 
5
- [CVE-2025-24293]
5
+ *Joshua Young*
6
6
 
7
- *Zack Deveau*
7
+ * Fix `config.active_storage.touch_attachment_records` to work with eager loading.
8
8
 
9
- ## Rails 8.0.2 (March 12, 2025) ##
9
+ *fatkodima*
10
10
 
11
- * No changes.
12
11
 
12
+ ## Rails 8.0.2.1 (August 13, 2025) ##
13
+
14
+ * Remove dangerous transformations
15
+
16
+ [CVE-2025-24293]
17
+
18
+ *Zack Deveau*
13
19
 
14
20
  ## Rails 8.0.2 (March 12, 2025) ##
15
21
 
data/README.md CHANGED
@@ -203,6 +203,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
203
203
 
204
204
  * https://github.com/rails/rails/issues
205
205
 
206
- Feature requests should be discussed on the rails-core mailing list here:
206
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
207
 
208
208
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -25,13 +25,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
25
25
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
26
  head :no_content
27
27
  else
28
- head :unprocessable_entity
28
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
29
  end
30
30
  else
31
31
  head :not_found
32
32
  end
33
33
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
34
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
35
  end
36
36
 
37
37
  private
@@ -25,12 +25,78 @@ 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
33
33
  # ActiveStorage::Blob#variable?.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # Options are defined by the {image_processing gem}[https://github.com/janko/image_processing],
38
+ # and depend on which variant processor you are using:
39
+ # {Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md] or
40
+ # {MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md].
41
+ # However, both variant processors support the following options:
42
+ #
43
+ # [+:resize_to_limit+]
44
+ # Downsizes the image to fit within the specified dimensions while retaining
45
+ # the original aspect ratio. Will only resize the image if it's larger than
46
+ # the specified dimensions.
47
+ #
48
+ # user.avatar.variant(resize_to_limit: [100, 100])
49
+ #
50
+ # [+:resize_to_fit+]
51
+ # Resizes the image to fit within the specified dimensions while retaining
52
+ # the original aspect ratio. Will downsize the image if it's larger than the
53
+ # specified dimensions or upsize if it's smaller.
54
+ #
55
+ # user.avatar.variant(resize_to_fit: [100, 100])
56
+ #
57
+ # [+:resize_to_fill+]
58
+ # Resizes the image to fill the specified dimensions while retaining the
59
+ # original aspect ratio. If necessary, will crop the image in the larger
60
+ # dimension.
61
+ #
62
+ # user.avatar.variant(resize_to_fill: [100, 100])
63
+ #
64
+ # [+:resize_and_pad+]
65
+ # Resizes the image to fit within the specified dimensions while retaining
66
+ # the original aspect ratio. If necessary, will pad the remaining area with
67
+ # transparent color if source image has alpha channel, black otherwise.
68
+ #
69
+ # user.avatar.variant(resize_and_pad: [100, 100])
70
+ #
71
+ # [+:crop+]
72
+ # Extracts an area from an image. The first two arguments are the left and
73
+ # top edges of area to extract, while the last two arguments are the width
74
+ # and height of the area to extract.
75
+ #
76
+ # user.avatar.variant(crop: [20, 50, 300, 300])
77
+ #
78
+ # [+:rotate+]
79
+ # Rotates the image by the specified angle.
80
+ #
81
+ # user.avatar.variant(rotate: 90)
82
+ #
83
+ # Some options, including those listed above, can accept additional
84
+ # processor-specific values which can be passed as a trailing hash:
85
+ #
86
+ # <!-- Vips supports configuring `crop` for many of its transformations -->
87
+ # <%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>
88
+ #
89
+ # If migrating an existing application between MiniMagick and Vips, you will
90
+ # need to update processor-specific options:
91
+ #
92
+ # <!-- MiniMagick -->
93
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
94
+ # sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
95
+ #
96
+ # <!-- Vips -->
97
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
98
+ # saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
99
+ #
34
100
  def variant(transformations)
35
101
  if variable?
36
102
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
@@ -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
@@ -74,11 +74,11 @@ class ActiveStorage::Variant
74
74
  "variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
75
75
  end
76
76
 
77
- # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
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
@@ -61,8 +61,8 @@ module ActiveStorage
61
61
  # There is no column defined on the model side, Active Storage takes
62
62
  # care of the mapping between your records and the attachment.
63
63
  #
64
- # Under the covers, this relationship is implemented as a +has_one+ association to a
65
- # ActiveStorage::Attachment record and a +has_one-through+ association to a
64
+ # Under the covers, this relationship is implemented as a +has_one+ association to an
65
+ # ActiveStorage::Attachment record and a +has_one-through+ association to an
66
66
  # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
67
67
  # and +avatar_blob+. But you shouldn't need to work with these associations directly in
68
68
  # most circumstances.
@@ -163,8 +163,8 @@ module ActiveStorage
163
163
  # There are no columns defined on the model side, Active Storage takes
164
164
  # care of the mapping between your records and the attachments.
165
165
  #
166
- # Under the covers, this relationship is implemented as a +has_many+ association to a
167
- # ActiveStorage::Attachment record and a +has_many-through+ association to a
166
+ # Under the covers, this relationship is implemented as a +has_many+ association to an
167
+ # ActiveStorage::Attachment record and a +has_many-through+ association to an
168
168
  # ActiveStorage::Blob record. These associations are available as +photos_attachments+
169
169
  # and +photos_blobs+. But you shouldn't need to work with these associations directly in
170
170
  # most circumstances.
@@ -84,6 +84,10 @@ module ActiveStorage
84
84
  end
85
85
 
86
86
  initializer "active_storage.configs" do
87
+ config.before_initialize do |app|
88
+ ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
89
+ end
90
+
87
91
  config.after_initialize do |app|
88
92
  ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
89
93
  ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
@@ -112,7 +116,6 @@ module ActiveStorage
112
116
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
113
117
  ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
114
118
  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
116
119
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
117
120
  ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
118
121
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
@@ -50,7 +50,7 @@ module ActiveStorage
50
50
  # by ActiveSupport::Testing::FileFixtures.file_fixture, and upload
51
51
  # the file to the Service
52
52
  #
53
- # === Examples
53
+ # ==== Examples
54
54
  #
55
55
  # # tests/fixtures/active_storage/blobs.yml
56
56
  # second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
@@ -9,8 +9,8 @@ module ActiveStorage
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 0
12
- TINY = 2
13
- PRE = "1"
12
+ TINY = 3
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -16,6 +16,7 @@ module ActiveStorage
16
16
 
17
17
  def initialize(bucket:, upload: {}, public: false, **options)
18
18
  @client = Aws::S3::Resource.new(**options)
19
+ @transfer_manager = Aws::S3::TransferManager.new(client: @client.client) if defined?(Aws::S3::TransferManager)
19
20
  @bucket = @client.bucket(bucket)
20
21
 
21
22
  @multipart_upload_threshold = upload.delete(:multipart_threshold) || 100.megabytes
@@ -100,7 +101,8 @@ module ActiveStorage
100
101
  def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
101
102
  content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
102
103
 
103
- object_for(destination_key).upload_stream(
104
+ upload_stream(
105
+ key: destination_key,
104
106
  content_type: content_type,
105
107
  content_disposition: content_disposition,
106
108
  part_size: MINIMUM_UPLOAD_PART_SIZE,
@@ -116,6 +118,14 @@ module ActiveStorage
116
118
  end
117
119
 
118
120
  private
121
+ def upload_stream(key:, **options, &block)
122
+ if @transfer_manager
123
+ @transfer_manager.upload_stream(key: key, bucket: bucket.name, **options, &block)
124
+ else
125
+ object_for(key).upload_stream(**options, &block)
126
+ end
127
+ end
128
+
119
129
  def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
120
130
  object_for(key).presigned_url :get, expires_in: expires_in.to_i,
121
131
  response_content_disposition: content_disposition_with(type: disposition, filename: filename),
@@ -126,7 +136,6 @@ module ActiveStorage
126
136
  object_for(key).public_url(**client_opts)
127
137
  end
128
138
 
129
-
130
139
  MAXIMUM_UPLOAD_PARTS_COUNT = 10000
131
140
  MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
132
141
 
@@ -139,12 +148,18 @@ module ActiveStorage
139
148
  def upload_with_multipart(key, io, content_type: nil, content_disposition: nil, custom_metadata: {})
140
149
  part_size = [ io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE ].max
141
150
 
142
- object_for(key).upload_stream(content_type: content_type, content_disposition: content_disposition, part_size: part_size, metadata: custom_metadata, **upload_options) do |out|
151
+ upload_stream(
152
+ key: key,
153
+ content_type: content_type,
154
+ content_disposition: content_disposition,
155
+ part_size: part_size,
156
+ metadata: custom_metadata,
157
+ **upload_options
158
+ ) do |out|
143
159
  IO.copy_stream(io, out)
144
160
  end
145
161
  end
146
162
 
147
-
148
163
  def object_for(key)
149
164
  bucket.object(key)
150
165
  end
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.0.2.1
4
+ version: 8.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,56 +15,56 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.0.2.1
18
+ version: 8.0.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 8.0.2.1
25
+ version: 8.0.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: actionpack
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 8.0.2.1
32
+ version: 8.0.3
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 8.0.2.1
39
+ version: 8.0.3
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: activejob
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 8.0.2.1
46
+ version: 8.0.3
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 8.0.2.1
53
+ version: 8.0.3
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: activerecord
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - '='
59
59
  - !ruby/object:Gem::Version
60
- version: 8.0.2.1
60
+ version: 8.0.3
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - '='
66
66
  - !ruby/object:Gem::Version
67
- version: 8.0.2.1
67
+ version: 8.0.3
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: marcel
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -189,10 +189,10 @@ licenses:
189
189
  - MIT
190
190
  metadata:
191
191
  bug_tracker_uri: https://github.com/rails/rails/issues
192
- changelog_uri: https://github.com/rails/rails/blob/v8.0.2.1/activestorage/CHANGELOG.md
193
- documentation_uri: https://api.rubyonrails.org/v8.0.2.1/
192
+ changelog_uri: https://github.com/rails/rails/blob/v8.0.3/activestorage/CHANGELOG.md
193
+ documentation_uri: https://api.rubyonrails.org/v8.0.3/
194
194
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
195
- source_code_uri: https://github.com/rails/rails/tree/v8.0.2.1/activestorage
195
+ source_code_uri: https://github.com/rails/rails/tree/v8.0.3/activestorage
196
196
  rubygems_mfa_required: 'true'
197
197
  rdoc_options: []
198
198
  require_paths: