activerecord 7.2.0 → 7.2.1

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: 9ec33405aa24cc15f0f8a4092bc3a04acafc8191a4df5b41896ce648a5937687
4
- data.tar.gz: 89efd888d4ed72e2d3ff6e75297c1dc4d0dcb19c15ed32c39fd53a2947a51b0b
3
+ metadata.gz: 4b72f5d48ed3098cb5d20e0f33ed2b082cccdb963338accb19f89c982a790ff1
4
+ data.tar.gz: 313c950f13b265ab63f2b5882f8fe9b20642e4f8702f1862f55abd030862a450
5
5
  SHA512:
6
- metadata.gz: 84757d9eab7d9709fcd86d0e39d111f6fabf6dc09f15167c432c1ce097d87524e8839be9843eaf7b0be0acc53f92c92dafdae3e21df3c996a34eb01ca6f5703c
7
- data.tar.gz: 83c46935ce453cec3e536ba27caa6b76fd5cdf59d8ca762e6eba91d2137a616d3a1e01cbfe049c945363130e55998e2afde0a8ff1d975ee2359ad38b560980ed
6
+ metadata.gz: 99f658cc51d0fc5d92b0869e6479f7569a6ffb74c075c514abefa2af4e248a9a3bfda7e01a4270c09c2aca91b99b01337939b1b40bc727a8c556d3aa47e90e99
7
+ data.tar.gz: 95a70341ef2dde997393b143c2beb7d25c74494f76159a888c591978c229e6c9de567183fbc4824290bd466bca3ea50e6f323c00596971af10eb53f21c3eb906
data/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ ## Rails 7.2.1 (August 22, 2024) ##
2
+
3
+ * Fix detection for `enum` columns with parallelized tests and PostgreSQL.
4
+
5
+ *Rafael Mendonça França*
6
+
7
+ * Allow to eager load nested nil associations.
8
+
9
+ *fatkodima*
10
+
11
+ * Fix swallowing ignore order warning when batching using `BatchEnumerator`.
12
+
13
+ *fatkodima*
14
+
15
+ * Fix memory bloat on the connection pool when using the Fiber `IsolatedExecutionState`.
16
+
17
+ *Jean Boussier*
18
+
19
+ * Restore inferred association class with the same modularized name.
20
+
21
+ *Justin Ko*
22
+
23
+ * Fix `ActiveRecord::Base.inspect` to properly explain how to load schema information.
24
+
25
+ *Jean Boussier*
26
+
27
+ * Check invalid `enum` options for the new syntax.
28
+
29
+ The options using `_` prefix in the old syntax are invalid in the new syntax.
30
+
31
+ *Rafael Mendonça França*
32
+
33
+ * Fix `ActiveRecord::Encryption::EncryptedAttributeType#type` to return
34
+ actual cast type.
35
+
36
+ *Vasiliy Ermolovich*
37
+
38
+ * Fix `create_table` with `:auto_increment` option for MySQL adapter.
39
+
40
+ *fatkodima*
41
+
42
+
1
43
  ## Rails 7.2.0 (August 09, 2024) ##
2
44
 
3
45
  * Handle commas in Sqlite3 default function definitions.
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
  when Hash
62
62
  associations.each do |k, v|
63
63
  cache = hash[k] ||= {}
64
- walk_tree v, cache
64
+ walk_tree v, cache if v
65
65
  end
66
66
  else
67
67
  raise ConfigurationError, associations.inspect
@@ -25,15 +25,17 @@ module ActiveRecord
25
25
  # column which this will persist to.
26
26
  #
27
27
  # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
28
- # to be used for this attribute. See the examples below for more
29
- # information about providing custom type objects.
28
+ # to be used for this attribute. If this parameter is not passed, the previously
29
+ # defined type (if any) will be used.
30
+ # Otherwise, the type will be ActiveModel::Type::Value.
31
+ # See the examples below for more information about providing custom type objects.
30
32
  #
31
33
  # ==== Options
32
34
  #
33
35
  # The following options are accepted:
34
36
  #
35
37
  # +default+ The default value to use when no value is provided. If this option
36
- # is not passed, the previous default value (if any) will be used.
38
+ # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
37
39
  # Otherwise, the default will be +nil+.
38
40
  #
39
41
  # +array+ (PostgreSQL only) specifies that the type should be an array (see the
@@ -118,6 +118,27 @@ module ActiveRecord
118
118
  # * private methods that require being called in a +synchronize+ blocks
119
119
  # are now explicitly documented
120
120
  class ConnectionPool
121
+ class WeakThreadKeyMap # :nodoc:
122
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
123
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
124
+ def initialize
125
+ @map = {}
126
+ end
127
+
128
+ def clear
129
+ @map.clear
130
+ end
131
+
132
+ def [](key)
133
+ @map[key]
134
+ end
135
+
136
+ def []=(key, value)
137
+ @map.select! { |c, _| c.alive? }
138
+ @map[key] = value
139
+ end
140
+ end
141
+
121
142
  class Lease # :nodoc:
122
143
  attr_accessor :connection, :sticky
123
144
 
@@ -145,48 +166,9 @@ module ActiveRecord
145
166
  end
146
167
 
147
168
  class LeaseRegistry # :nodoc:
148
- if ObjectSpace.const_defined?(:WeakKeyMap) # RUBY_VERSION >= 3.3
149
- WeakKeyMap = ::ObjectSpace::WeakKeyMap # :nodoc:
150
- else
151
- class WeakKeyMap # :nodoc:
152
- def initialize
153
- @map = ObjectSpace::WeakMap.new
154
- @values = nil
155
- @size = 0
156
- end
157
-
158
- alias_method :clear, :initialize
159
-
160
- def [](key)
161
- prune if @map.size != @size
162
- @map[key]
163
- end
164
-
165
- def []=(key, value)
166
- @map[key] = value
167
- prune if @map.size != @size
168
- value
169
- end
170
-
171
- def delete(key)
172
- if value = self[key]
173
- self[key] = nil
174
- prune
175
- end
176
- value
177
- end
178
-
179
- private
180
- def prune(force = false)
181
- @values = @map.values
182
- @size = @map.size
183
- end
184
- end
185
- end
186
-
187
169
  def initialize
188
170
  @mutex = Mutex.new
189
- @map = WeakKeyMap.new
171
+ @map = WeakThreadKeyMap.new
190
172
  end
191
173
 
192
174
  def [](context)
@@ -197,7 +179,7 @@ module ActiveRecord
197
179
 
198
180
  def clear
199
181
  @mutex.synchronize do
200
- @map = WeakKeyMap.new
182
+ @map.clear
201
183
  end
202
184
  end
203
185
  end
@@ -630,8 +612,6 @@ module ActiveRecord
630
612
  remove conn
631
613
  end
632
614
  end
633
-
634
- prune_thread_cache
635
615
  end
636
616
 
637
617
  # Disconnect all connections that have been idle for at least
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
+ require "concurrent/atomic/atomic_fixnum"
4
5
 
5
6
  module ActiveRecord
6
7
  module ConnectionAdapters # :nodoc:
@@ -35,7 +36,9 @@ module ActiveRecord
35
36
  alias_method :enabled?, :enabled
36
37
  alias_method :dirties?, :dirties
37
38
 
38
- def initialize(max_size)
39
+ def initialize(version, max_size)
40
+ @version = version
41
+ @current_version = version.value
39
42
  @map = {}
40
43
  @max_size = max_size
41
44
  @enabled = false
@@ -43,14 +46,17 @@ module ActiveRecord
43
46
  end
44
47
 
45
48
  def size
49
+ check_version
46
50
  @map.size
47
51
  end
48
52
 
49
53
  def empty?
54
+ check_version
50
55
  @map.empty?
51
56
  end
52
57
 
53
58
  def [](key)
59
+ check_version
54
60
  return unless @enabled
55
61
 
56
62
  if entry = @map.delete(key)
@@ -59,6 +65,8 @@ module ActiveRecord
59
65
  end
60
66
 
61
67
  def compute_if_absent(key)
68
+ check_version
69
+
62
70
  return yield unless @enabled
63
71
 
64
72
  if entry = @map.delete(key)
@@ -76,12 +84,40 @@ module ActiveRecord
76
84
  @map.clear
77
85
  self
78
86
  end
87
+
88
+ private
89
+ def check_version
90
+ if @current_version != @version.value
91
+ @map.clear
92
+ @current_version = @version.value
93
+ end
94
+ end
95
+ end
96
+
97
+ class QueryCacheRegistry # :nodoc:
98
+ def initialize
99
+ @mutex = Mutex.new
100
+ @map = ConnectionPool::WeakThreadKeyMap.new
101
+ end
102
+
103
+ def compute_if_absent(context)
104
+ @map[context] || @mutex.synchronize do
105
+ @map[context] ||= yield
106
+ end
107
+ end
108
+
109
+ def clear
110
+ @map.synchronize do
111
+ @map.clear
112
+ end
113
+ end
79
114
  end
80
115
 
81
116
  module ConnectionPoolConfiguration # :nodoc:
82
117
  def initialize(...)
83
118
  super
84
- @thread_query_caches = Concurrent::Map.new(initial_capacity: @size)
119
+ @query_cache_version = Concurrent::AtomicFixnum.new
120
+ @thread_query_caches = QueryCacheRegistry.new
85
121
  @query_cache_max_size = \
86
122
  case query_cache = db_config&.query_cache
87
123
  when 0, false
@@ -141,25 +177,16 @@ module ActiveRecord
141
177
  # With transactional fixtures, and especially systems test
142
178
  # another thread may use the same connection, but with a different
143
179
  # query cache. So we must clear them all.
144
- @thread_query_caches.each_value(&:clear)
145
- else
146
- query_cache.clear
180
+ @query_cache_version.increment
147
181
  end
182
+ query_cache.clear
148
183
  end
149
184
 
150
185
  def query_cache
151
186
  @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
152
- Store.new(@query_cache_max_size)
187
+ Store.new(@query_cache_version, @query_cache_max_size)
153
188
  end
154
189
  end
155
-
156
- private
157
- def prune_thread_cache
158
- dead_threads = @thread_query_caches.keys.reject(&:alive?)
159
- dead_threads.each do |dead_thread|
160
- @thread_query_caches.delete(dead_thread)
161
- end
162
- end
163
190
  end
164
191
 
165
192
  attr_accessor :query_cache
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  end
162
162
 
163
163
  def valid_primary_key_options
164
- super + [:unsigned]
164
+ super + [:unsigned, :auto_increment]
165
165
  end
166
166
 
167
167
  def create_table_definition(name, **options)
@@ -353,8 +353,8 @@ module ActiveRecord
353
353
  super
354
354
  elsif abstract_class?
355
355
  "#{super}(abstract)"
356
- elsif !connected?
357
- "#{super} (call '#{super}.lease_connection' to establish a connection)"
356
+ elsif !schema_loaded? && !connected?
357
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
358
358
  elsif table_exists?
359
359
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
360
360
  "#{super}(#{attr_list})"
@@ -123,7 +123,7 @@ module ActiveRecord
123
123
  end)
124
124
  end
125
125
 
126
- def load_schema!
126
+ def load_schema! # :nodoc:
127
127
  super
128
128
 
129
129
  add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
@@ -7,13 +7,13 @@ module ActiveRecord
7
7
  # This is the central piece that connects the encryption system with +encrypts+ declarations in the
8
8
  # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
9
9
  # for that attribute.
10
- class EncryptedAttributeType < ::ActiveRecord::Type::Text
10
+ class EncryptedAttributeType < ::ActiveModel::Type::Value
11
11
  include ActiveModel::Type::Helpers::Mutable
12
12
 
13
13
  attr_reader :scheme, :cast_type
14
14
 
15
15
  delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
16
- delegate :accessor, to: :cast_type
16
+ delegate :accessor, :type, to: :cast_type
17
17
 
18
18
  # === Options
19
19
  #
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
47
47
  end
48
48
 
49
- # Decrypts a +clean_text+ and returns the result as clean text
49
+ # Decrypts an +encrypted_text+ and returns the result as clean text
50
50
  #
51
51
  # === Options
52
52
  #
@@ -167,6 +167,15 @@ module ActiveRecord
167
167
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
168
168
  end
169
169
 
170
+ def load_schema! # :nodoc:
171
+ defined_enums.each_key do |name|
172
+ unless columns_hash.key?(resolve_attribute_name(name))
173
+ raise "Unknown enum attribute '#{name}' for #{self.name}. Enums must be" \
174
+ " backed by a database column."
175
+ end
176
+ end
177
+ end
178
+
170
179
  class EnumType < Type::Value # :nodoc:
171
180
  delegate :type, to: :subtype
172
181
 
@@ -240,6 +249,7 @@ module ActiveRecord
240
249
 
241
250
  def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
242
251
  assert_valid_enum_definition_values(values)
252
+ assert_valid_enum_options(options)
243
253
  # statuses = { }
244
254
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
245
255
  name = name.to_s
@@ -254,13 +264,7 @@ module ActiveRecord
254
264
 
255
265
  attribute(name, **options)
256
266
 
257
- decorate_attributes([name]) do |_name, subtype|
258
- if subtype == ActiveModel::Type.default_value
259
- raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
260
- " backed by a database column or declared with an explicit type" \
261
- " via `attribute`."
262
- end
263
-
267
+ decorate_attributes([name]) do |name, subtype|
264
268
  subtype = subtype.subtype if EnumType === subtype
265
269
  EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
266
270
  end
@@ -370,6 +374,13 @@ module ActiveRecord
370
374
  end
371
375
  end
372
376
 
377
+ def assert_valid_enum_options(options)
378
+ invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods]
379
+ unless invalid_keys.empty?
380
+ raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate."
381
+ end
382
+ end
383
+
373
384
  ENUM_CONFLICT_MESSAGE = \
374
385
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
375
386
  "this will generate a %{type} method \"%{method}\", which is already defined " \
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 2
12
- TINY = 0
12
+ TINY = 1
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -531,7 +531,9 @@ module ActiveRecord
531
531
  initialize_find_by_cache
532
532
  end
533
533
 
534
- def load_schema # :nodoc:
534
+ # Load the model's schema information either from the schema cache
535
+ # or directly from the database.
536
+ def load_schema
535
537
  return if schema_loaded?
536
538
  @load_schema_monitor.synchronize do
537
539
  return if schema_loaded?
@@ -592,6 +594,8 @@ module ActiveRecord
592
594
  columns_hash = schema_cache.columns_hash(table_name)
593
595
  columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
594
596
  @columns_hash = columns_hash.freeze
597
+
598
+ super
595
599
  end
596
600
 
597
601
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -428,15 +428,19 @@ module ActiveRecord
428
428
  # a new association object. Use +build_association+ or +create_association+
429
429
  # instead. This allows plugins to hook into association object creation.
430
430
  def klass
431
- @klass ||= compute_class(compute_name(class_name))
431
+ @klass ||= _klass(class_name)
432
432
  end
433
433
 
434
- def compute_class(name)
435
- name.constantize
434
+ def _klass(class_name) # :nodoc:
435
+ if active_record.name.demodulize == class_name
436
+ return compute_class("::#{class_name}") rescue NameError
437
+ end
438
+
439
+ compute_class(class_name)
436
440
  end
437
441
 
438
- def compute_name(name) # :nodoc:
439
- active_record.name.demodulize == name ? "::#{name}" : name
442
+ def compute_class(name)
443
+ name.constantize
440
444
  end
441
445
 
442
446
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -986,7 +990,7 @@ module ActiveRecord
986
990
  end
987
991
 
988
992
  def klass
989
- @klass ||= delegate_reflection.compute_class(compute_name(class_name))
993
+ @klass ||= delegate_reflection._klass(class_name)
990
994
  end
991
995
 
992
996
  # Returns the source of the through reflection. It checks both a singularized
@@ -241,14 +241,14 @@ module ActiveRecord
241
241
  raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
242
242
  end
243
243
 
244
- unless block
245
- return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
246
- end
247
-
248
244
  if arel.orders.present?
249
245
  act_on_ignored_order(error_on_ignore)
250
246
  end
251
247
 
248
+ unless block
249
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
250
+ end
251
+
252
252
  batch_limit = of
253
253
 
254
254
  if limit_value
@@ -190,6 +190,7 @@ module ActiveRecord
190
190
 
191
191
  private
192
192
  def singleton_method_added(name)
193
+ super
193
194
  generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
194
195
  end
195
196
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.0
4
+ version: 7.2.1
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-08-09 00:00:00.000000000 Z
11
+ date: 2024-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.2.0
19
+ version: 7.2.1
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.2.0
26
+ version: 7.2.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 7.2.0
33
+ version: 7.2.1
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.2.0
40
+ version: 7.2.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: timeout
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -476,10 +476,10 @@ licenses:
476
476
  - MIT
477
477
  metadata:
478
478
  bug_tracker_uri: https://github.com/rails/rails/issues
479
- changelog_uri: https://github.com/rails/rails/blob/v7.2.0/activerecord/CHANGELOG.md
480
- documentation_uri: https://api.rubyonrails.org/v7.2.0/
479
+ changelog_uri: https://github.com/rails/rails/blob/v7.2.1/activerecord/CHANGELOG.md
480
+ documentation_uri: https://api.rubyonrails.org/v7.2.1/
481
481
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
482
- source_code_uri: https://github.com/rails/rails/tree/v7.2.0/activerecord
482
+ source_code_uri: https://github.com/rails/rails/tree/v7.2.1/activerecord
483
483
  rubygems_mfa_required: 'true'
484
484
  post_install_message:
485
485
  rdoc_options: