activestorage 7.2.2.2 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95b4c7b82cf963f9b1d16f4646ad4538829af0a213797ac35621e7a4b6e13609
4
- data.tar.gz: dab322a2424c0c8f7fc30af73b7453d1ab36060a2f905eb31f4d21cfc7847d6a
3
+ metadata.gz: 94213ce5408fd1a4c67c6bf4854d2f2c55289813ef0297dfc33fb3325ec5dfd4
4
+ data.tar.gz: 73de432409f6bb50456de14131e01c649b0172fcd5c0d5178b61b385cc236e37
5
5
  SHA512:
6
- metadata.gz: 1a3ff3b23455a2e472fc46dab1a947dab49032917c59a1844fee4d694445366df9ef2b99085de5d10c6107f0f1085179a303da233d6f953c9a37ddbdba70e5cc
7
- data.tar.gz: a6601fbc244adaf30ba1dac98f3062700195bfa5f728b9425976d2f14c89f6ee27223d25b00c1f7573022677e558f45a06487efba20fda259fb9006014af7a68
6
+ metadata.gz: 48271c643e3a2a6ffbabdc398863f9452d3cdc2c01540b94a28f512fcc828d1792d13fd36ec05c890e3b02dc7d6fc33439c792eebbda2ab720479032307c947a
7
+ data.tar.gz: 65acfc493d0aa029d4b533dd086cdeb9e35c04c6b4b4b6ee678101516bfc9428ffc477be92d5d49c5c1f6f43f6bed29b2c7ccbacf11f650e27fae34a779bb672
data/CHANGELOG.md CHANGED
@@ -1,11 +1,31 @@
1
+ ## Rails 7.2.3 (October 28, 2025) ##
2
+
3
+ * Fix `config.active_storage.touch_attachment_records` to work with eager loading.
4
+
5
+ *fatkodima*
6
+
7
+ * A Blob will no longer autosave associated Attachment.
8
+
9
+ This fixes an issue where a record with an attachment would have
10
+ its dirty attributes reset, preventing your `after commit` callbacks
11
+ on that record to behave as expected.
12
+
13
+ Note that this change doesn't require any changes on your application
14
+ and is supposed to be internal. Active Storage Attachment will continue
15
+ to be autosaved (through a different relation).
16
+
17
+ *Edouard-chin*
18
+
19
+
1
20
  ## Rails 7.2.2.2 (August 13, 2025) ##
2
21
 
3
- Remove dangerous transformations
22
+ * Remove dangerous transformations
4
23
 
5
24
  [CVE-2025-24293]
6
25
 
7
26
  *Zack Deveau*
8
27
 
28
+
9
29
  ## Rails 7.2.2.1 (December 10, 2024) ##
10
30
 
11
31
  * No changes.
data/README.md CHANGED
@@ -203,6 +203,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
203
203
 
204
204
  * https://github.com/rails/rails/issues
205
205
 
206
- Feature requests should be discussed on the rails-core mailing list here:
206
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
207
 
208
208
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -25,13 +25,13 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
25
25
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
26
  head :no_content
27
27
  else
28
- head :unprocessable_entity
28
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
29
  end
30
30
  else
31
31
  head :not_found
32
32
  end
33
33
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
34
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
35
  end
36
36
 
37
37
  private
@@ -31,6 +31,72 @@ module ActiveStorage::Blob::Representable
31
31
  # Raises ActiveStorage::InvariableError if the variant processor cannot
32
32
  # transform the blob. To determine whether a blob is variable, call
33
33
  # ActiveStorage::Blob#variable?.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # Options are defined by the {image_processing gem}[https://github.com/janko/image_processing],
38
+ # and depend on which variant processor you are using:
39
+ # {Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md] or
40
+ # {MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md].
41
+ # However, both variant processors support the following options:
42
+ #
43
+ # [+:resize_to_limit+]
44
+ # Downsizes the image to fit within the specified dimensions while retaining
45
+ # the original aspect ratio. Will only resize the image if it's larger than
46
+ # the specified dimensions.
47
+ #
48
+ # user.avatar.variant(resize_to_limit: [100, 100])
49
+ #
50
+ # [+:resize_to_fit+]
51
+ # Resizes the image to fit within the specified dimensions while retaining
52
+ # the original aspect ratio. Will downsize the image if it's larger than the
53
+ # specified dimensions or upsize if it's smaller.
54
+ #
55
+ # user.avatar.variant(resize_to_fit: [100, 100])
56
+ #
57
+ # [+:resize_to_fill+]
58
+ # Resizes the image to fill the specified dimensions while retaining the
59
+ # original aspect ratio. If necessary, will crop the image in the larger
60
+ # dimension.
61
+ #
62
+ # user.avatar.variant(resize_to_fill: [100, 100])
63
+ #
64
+ # [+:resize_and_pad+]
65
+ # Resizes the image to fit within the specified dimensions while retaining
66
+ # the original aspect ratio. If necessary, will pad the remaining area with
67
+ # transparent color if source image has alpha channel, black otherwise.
68
+ #
69
+ # user.avatar.variant(resize_and_pad: [100, 100])
70
+ #
71
+ # [+:crop+]
72
+ # Extracts an area from an image. The first two arguments are the left and
73
+ # top edges of area to extract, while the last two arguments are the width
74
+ # and height of the area to extract.
75
+ #
76
+ # user.avatar.variant(crop: [20, 50, 300, 300])
77
+ #
78
+ # [+:rotate+]
79
+ # Rotates the image by the specified angle.
80
+ #
81
+ # user.avatar.variant(rotate: 90)
82
+ #
83
+ # Some options, including those listed above, can accept additional
84
+ # processor-specific values which can be passed as a trailing hash:
85
+ #
86
+ # <!-- Vips supports configuring `crop` for many of its transformations -->
87
+ # <%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>
88
+ #
89
+ # If migrating an existing application between MiniMagick and Vips, you will
90
+ # need to update processor-specific options:
91
+ #
92
+ # <!-- MiniMagick -->
93
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
94
+ # sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
95
+ #
96
+ # <!-- Vips -->
97
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
98
+ # saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
99
+ #
34
100
  def variant(transformations)
35
101
  if variable?
36
102
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
@@ -29,7 +29,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
29
29
  # :method:
30
30
  #
31
31
  # Returns the associated ActiveStorage::Attachment instances.
32
- has_many :attachments
32
+ has_many :attachments, autosave: false
33
33
 
34
34
  ##
35
35
  # :singleton-method:
@@ -152,22 +152,6 @@ class ActiveStorage::Blob < ActiveStorage::Record
152
152
  combined_blob.save!
153
153
  end
154
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
171
155
  end
172
156
 
173
157
  include Analyzable
@@ -121,7 +121,7 @@ module ActiveStorage
121
121
  service_name = record.attachment_reflections[name].options[:service_name]
122
122
  if service_name.is_a?(Proc)
123
123
  service_name = service_name.call(record)
124
- ActiveStorage::Blob.validate_service_configuration(service_name, record.class, name)
124
+ Attached::Model.validate_service_configuration(service_name, record.class, name)
125
125
  end
126
126
  service_name
127
127
  end
@@ -104,7 +104,7 @@ module ActiveStorage
104
104
  # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
105
105
  # the corresponding rows.
106
106
  def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
107
- ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
107
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
108
108
 
109
109
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
110
110
  # frozen_string_literal: true
@@ -204,7 +204,7 @@ module ActiveStorage
204
204
  # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
205
205
  # the corresponding rows.
206
206
  def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
207
- ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
207
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
208
208
 
209
209
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
210
210
  # frozen_string_literal: true
@@ -255,6 +255,25 @@ module ActiveStorage
255
255
  end
256
256
  end
257
257
 
258
+ class << self
259
+ def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
260
+ if service_name
261
+ ActiveStorage::Blob.services.fetch(service_name) do
262
+ raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
263
+ end
264
+ else
265
+ validate_global_service_configuration(model_class)
266
+ end
267
+ end
268
+
269
+ private
270
+ def validate_global_service_configuration(model_class)
271
+ if model_class.connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
272
+ raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
273
+ end
274
+ end
275
+ end
276
+
258
277
  def attachment_changes # :nodoc:
259
278
  @attachment_changes ||= {}
260
279
  end
@@ -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 = 7
11
11
  MINOR = 2
12
- TINY = 2
13
- PRE = "2"
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: 7.2.2.2
4
+ version: 7.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,56 +15,56 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 7.2.2.2
18
+ version: 7.2.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 7.2.2.2
25
+ version: 7.2.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: actionpack
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 7.2.2.2
32
+ version: 7.2.3
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 7.2.2.2
39
+ version: 7.2.3
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: activejob
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 7.2.2.2
46
+ version: 7.2.3
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 7.2.2.2
53
+ version: 7.2.3
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: activerecord
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - '='
59
59
  - !ruby/object:Gem::Version
60
- version: 7.2.2.2
60
+ version: 7.2.3
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - '='
66
66
  - !ruby/object:Gem::Version
67
- version: 7.2.2.2
67
+ version: 7.2.3
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: marcel
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -189,10 +189,10 @@ licenses:
189
189
  - MIT
190
190
  metadata:
191
191
  bug_tracker_uri: https://github.com/rails/rails/issues
192
- changelog_uri: https://github.com/rails/rails/blob/v7.2.2.2/activestorage/CHANGELOG.md
193
- documentation_uri: https://api.rubyonrails.org/v7.2.2.2/
192
+ changelog_uri: https://github.com/rails/rails/blob/v7.2.3/activestorage/CHANGELOG.md
193
+ documentation_uri: https://api.rubyonrails.org/v7.2.3/
194
194
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
195
- source_code_uri: https://github.com/rails/rails/tree/v7.2.2.2/activestorage
195
+ source_code_uri: https://github.com/rails/rails/tree/v7.2.3/activestorage
196
196
  rubygems_mfa_required: 'true'
197
197
  rdoc_options: []
198
198
  require_paths: