activerecord 8.1.2.1 → 8.1.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: 945d807c061f39c5efbea228d4c29e8aa1166766b100cec185ee57a59de91356
4
- data.tar.gz: 0ced7653045c89cbcdcd7b91cd15f3dbeb15bc71890505ddb6988c9006039ae7
3
+ metadata.gz: bf11d5e6abc99265d39fb3f4b3b0d87f1e2bbdf6099a478306ba3d04ac2a760a
4
+ data.tar.gz: 4d47ba93fb7a68eb7cbadf3a001bb676b71b87a06b00504907717059c39190b5
5
5
  SHA512:
6
- metadata.gz: 92ed614237870ed7e019e74b1c7db3314772b9a67ae5a2fac4e5857b1b78c1bf14ff3bf9e52bd350dfc27402de5dd76f5492c13b33e57aa5579a1a5a047f2cea
7
- data.tar.gz: 64dd7694f2a9c85ba08bea7506d84a937b84ec6d235df05c773a50c59401830123d22c499f455318b685cd050796fd8f1fa319fb42b4a812b27a513c56b0c166
6
+ metadata.gz: bcbbfd82d1032400a7f3dfa0784bd5d506107d50d0ada3e1fefd26de665959e1f86cb2aa5f4e7cfbb24e69101e3b71542abd1ecffaa53963823ae47fa4e26475
7
+ data.tar.gz: ce6acb68c3c821dfda84556f5089effcf401cff05b8d819297430b77d51699bf096bc2ed69f78161f9750bd45bbe3ffaa2b591b08d6676dd437e8a9fc06952d4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,61 @@
1
+ ## Rails 8.1.3 (March 24, 2026) ##
2
+
3
+ * Fix `insert_all` and `upsert_all` log message when called on anonymous classes.
4
+
5
+ *Gabriel Sobrinho*
6
+
7
+ * Respect `ActiveRecord::SchemaDumper.ignore_tables` when dumping SQLite virtual tables.
8
+
9
+ *Hans Schnedlitz*
10
+
11
+ * Restore previous instrumenter after `execute_or_skip`
12
+
13
+ `FutureResult#execute_or_skip` replaces the thread's instrumenter with an
14
+ `EventBuffer` to collect events published during async query execution.
15
+ If the global async executor is saturated and the `caller_runs` fallback
16
+ executes the task on the calling thread, we need to make sure the previous
17
+ instrumenter is restored or the stale `EventBuffer` would stay in place and
18
+ permanently swallow all subsequent `sql.active_record` notifications on
19
+ that thread.
20
+
21
+ *Rosa Gutierrez*
22
+
23
+ * Bump the minimum PostgreSQL version to 9.5, due to usage of `array_position` function.
24
+
25
+ *Ivan Kuchin*
26
+
27
+ * Fix Ruby 4.0 delegator warning when calling inspect on ActiveRecord::Type::Serialized.
28
+
29
+ *Hammad Khan*
30
+
31
+ * Fix support for table names containing hyphens.
32
+
33
+ *Evgeniy Demin*
34
+
35
+ * Fix column deduplication for SQLite3 and PostgreSQL virtual (generated) columns.
36
+
37
+ `Column#==` and `Column#hash` now account for `virtual?` so that the
38
+ `Deduplicable` registry does not treat a generated column and a regular
39
+ column with the same name and type as identical. Previously, if a
40
+ generated column was registered first, a regular column on a different
41
+ table could be deduplicated to the generated instance, silently
42
+ excluding it from INSERT/UPDATE statements.
43
+
44
+ *Jay Huber*
45
+
46
+ * Fix PostgreSQL schema dumping to handle schema-qualified table names in foreign_key references that span different schemas.
47
+
48
+ # before
49
+ add_foreign_key "hst.event_log_attributes", "hst.event_logs" # emits correctly because they're in the same schema (hst)
50
+ add_foreign_key "hst.event_log_attributes", "hst.usr.user_profiles", column: "created_by_id" # emits hst.user.* when user.* is expected
51
+
52
+ # after
53
+ add_foreign_key "hst.event_log_attributes", "hst.event_logs"
54
+ add_foreign_key "hst.event_log_attributes", "usr.user_profiles", column: "created_by_id"
55
+
56
+ *Chiperific*
57
+
58
+
1
59
  ## Rails 8.1.2.1 (March 23, 2026) ##
2
60
 
3
61
  * No changes.
@@ -129,7 +129,7 @@ module ActiveRecord
129
129
  class ConnectionPool
130
130
  # Prior to 3.3.5, WeakKeyMap had a use after free bug
131
131
  # https://bugs.ruby-lang.org/issues/20688
132
- if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
132
+ if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= "3.3.5"
133
133
  WeakThreadKeyMap = ObjectSpace::WeakKeyMap
134
134
  else
135
135
  class WeakThreadKeyMap # :nodoc:
@@ -526,7 +526,7 @@ module ActiveRecord
526
526
  end
527
527
  conn.disconnect!
528
528
  end
529
- @connections = []
529
+ @connections = @pinned_connection ? [@pinned_connection] : []
530
530
  @leases.clear
531
531
  @available.clear
532
532
 
@@ -753,7 +753,7 @@ module ActiveRecord
753
753
  end
754
754
 
755
755
  def extract_table_ref_from_insert_sql(sql)
756
- if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
756
+ if sql =~ /into\s("[-A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
757
757
  $1.delete('"').strip
758
758
  end
759
759
  end
@@ -1604,11 +1604,11 @@ module ActiveRecord
1604
1604
  non_combinable_operations = []
1605
1605
 
1606
1606
  operations.each do |command, args|
1607
- table, arguments = args.shift, args
1607
+ args.shift # remove table_name
1608
1608
  method = :"#{command}_for_alter"
1609
1609
 
1610
1610
  if respond_to?(method, true)
1611
- sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1611
+ sqls, procs = Array(send(method, table_name, *args)).partition { |v| v.is_a?(String) }
1612
1612
  sql_fragments.concat(sqls)
1613
1613
  non_combinable_operations.concat(procs)
1614
1614
  else
@@ -1616,7 +1616,7 @@ module ActiveRecord
1616
1616
  non_combinable_operations.each(&:call)
1617
1617
  sql_fragments = []
1618
1618
  non_combinable_operations = []
1619
- send(command, table, *arguments)
1619
+ send(command, table_name, *args)
1620
1620
  end
1621
1621
  end
1622
1622
 
@@ -126,7 +126,7 @@ module ActiveRecord
126
126
 
127
127
  # Opens a database console session.
128
128
  def self.dbconsole(config, options = {})
129
- raise NotImplementedError.new("#{self.class} should define `dbconsole` that accepts a db config and options to implement connecting to the db console")
129
+ raise NotImplementedError.new("#{self} should define `dbconsole` that accepts a db config and options to implement connecting to the db console")
130
130
  end
131
131
 
132
132
  def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
@@ -69,7 +69,8 @@ module ActiveRecord
69
69
  other.is_a?(Column) &&
70
70
  super &&
71
71
  identity? == other.identity? &&
72
- serial? == other.serial?
72
+ serial? == other.serial? &&
73
+ virtual? == other.virtual?
73
74
  end
74
75
  alias :eql? :==
75
76
 
@@ -77,7 +78,8 @@ module ActiveRecord
77
78
  Column.hash ^
78
79
  super.hash ^
79
80
  identity?.hash ^
80
- serial?.hash
81
+ serial?.hash ^
82
+ virtual?.hash
81
83
  end
82
84
  end
83
85
  end
@@ -154,6 +154,8 @@ module ActiveRecord
154
154
  def relation_name(name)
155
155
  if @dump_schemas.size == 1
156
156
  name
157
+ elsif name.include?(".")
158
+ name # Already schema-qualified, don't add another prefix
157
159
  else
158
160
  "#{schema_name}.#{name}"
159
161
  end
@@ -682,8 +682,8 @@ module ActiveRecord
682
682
  end
683
683
 
684
684
  def check_version # :nodoc:
685
- if database_version < 9_03_00 # < 9.3
686
- raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
685
+ if database_version < 9_05_00 # < 9.5
686
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.5."
687
687
  end
688
688
  end
689
689
 
@@ -46,7 +46,9 @@ module ActiveRecord
46
46
  def ==(other)
47
47
  other.is_a?(Column) &&
48
48
  super &&
49
- auto_increment? == other.auto_increment?
49
+ auto_increment? == other.auto_increment? &&
50
+ rowid == other.rowid &&
51
+ generated_type == other.generated_type
50
52
  end
51
53
  alias :eql? :==
52
54
 
@@ -54,8 +56,12 @@ module ActiveRecord
54
56
  Column.hash ^
55
57
  super.hash ^
56
58
  auto_increment?.hash ^
57
- rowid.hash
59
+ rowid.hash ^
60
+ virtual?.hash
58
61
  end
62
+
63
+ protected
64
+ attr_reader :generated_type
59
65
  end
60
66
  end
61
67
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
8
  def virtual_tables(stream)
9
- virtual_tables = @connection.virtual_tables
9
+ virtual_tables = @connection.virtual_tables.reject { |name, _| ignored?(name) }
10
10
  if virtual_tables.any?
11
11
  stream.puts
12
12
  stream.puts " # Virtual tables defined in this database."
@@ -319,7 +319,7 @@ module ActiveRecord
319
319
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
320
320
  end
321
321
 
322
- VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
322
+ VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.*)\)/i
323
323
 
324
324
  # Returns a list of defined virtual tables
325
325
  def virtual_tables
@@ -105,6 +105,7 @@ module ActiveRecord
105
105
 
106
106
  @pool.with_connection do |connection|
107
107
  return unless @mutex.try_lock
108
+ previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
108
109
  begin
109
110
  if pending?
110
111
  @event_buffer = EventBuffer.new(self, @instrumenter)
@@ -113,6 +114,7 @@ module ActiveRecord
113
114
  execute_query(connection, async: true)
114
115
  end
115
116
  ensure
117
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
116
118
  @mutex.unlock
117
119
  end
118
120
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 1
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
@@ -39,7 +39,7 @@ module ActiveRecord
39
39
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
40
40
  @returning = false if @returning == []
41
41
 
42
- @unique_by = find_unique_index_for(@unique_by)
42
+ @unique_by = find_unique_index_for(@unique_by) if @on_duplicate != :raise
43
43
 
44
44
  configure_on_duplicate_update_logic
45
45
  ensure_valid_options_for_connection!
@@ -48,7 +48,7 @@ module ActiveRecord
48
48
  def execute
49
49
  return ActiveRecord::Result.empty if inserts.empty?
50
50
 
51
- message = +"#{model} "
51
+ message = +"#{model.name} "
52
52
  message << "Bulk " if inserts.many?
53
53
  message << (on_duplicate == :update ? "Upsert" : "Insert")
54
54
  connection.exec_insert_all to_sql, message
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
  recorder.reverting = @reverting
143
143
  yield recorder.delegate.update_table_definition(table_name, recorder)
144
144
  commands = recorder.commands
145
- @commands << [:change_table, [table_name], -> t { bulk_change_table(table_name, commands) }]
145
+ @commands << [:change_table, [table_name], -> t { bulk_change_table(t.name, commands.reverse) }]
146
146
  else
147
147
  yield delegate.update_table_definition(table_name, self)
148
148
  end
@@ -441,8 +441,8 @@ module ActiveRecord
441
441
  yielded_relation.load_records(records)
442
442
  elsif (empty_scope && use_ranges != false) || use_ranges
443
443
  # Efficiently peak at the last value for the next batch using offset and limit.
444
- values_size = batch_limit
445
- values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
444
+ values_size = remaining ? [batch_limit, remaining].min : batch_limit
445
+ values_last = batch_relation.offset(values_size - 1).pick(*cursor)
446
446
 
447
447
  # If the last value is not found using offset, there is at most one more batch of size < batch_limit.
448
448
  # Retry by getting the whole list of remaining values so that we have the exact size and last value.
@@ -31,7 +31,9 @@ module ActiveRecord
31
31
  values_predicate
32
32
  else
33
33
  array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
- array_predicates.inject(values_predicate, &:or)
34
+ values_predicate.or(
35
+ Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
36
+ )
35
37
  end
36
38
  end
37
39
 
@@ -25,10 +25,8 @@ module ActiveRecord
25
25
  end
26
26
  end
27
27
 
28
- JSON_ENCODER = ActiveSupport::JSON::Encoding.json_encoder.new(escape: false)
29
-
30
28
  def serialize(value)
31
- JSON_ENCODER.encode(value) unless value.nil?
29
+ ActiveSupport::JSON::Encoding.encode_without_escape(value) unless value.nil?
32
30
  end
33
31
 
34
32
  def changed_in_place?(raw_old_value, new_value)
@@ -65,6 +65,11 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  private
68
+ # Prevent Ruby 4.0 "delegator does not forward private method" warning.
69
+ # Kernel#inspect calls instance_variables_to_inspect which, without this,
70
+ # triggers Delegator#respond_to_missing? for a private method.
71
+ define_method(:instance_variables_to_inspect, Kernel.instance_method(:instance_variables))
72
+
68
73
  def default_value?(value)
69
74
  value == coder.load(nil)
70
75
  end
data/lib/active_record.rb CHANGED
@@ -358,7 +358,7 @@ module ActiveRecord
358
358
  self.run_after_transaction_callbacks_in_order_defined = false
359
359
 
360
360
  singleton_class.attr_accessor :raise_on_missing_required_finder_order_columns
361
- self.run_after_transaction_callbacks_in_order_defined = false
361
+ self.raise_on_missing_required_finder_order_columns = false
362
362
 
363
363
  singleton_class.attr_accessor :application_record_class
364
364
  self.application_record_class = nil
@@ -231,9 +231,7 @@ module Arel # :nodoc: all
231
231
  private
232
232
  def grouping_any(method_id, others, *extras)
233
233
  nodes = others.map { |expr| send(method_id, expr, *extras) }
234
- Nodes::Grouping.new nodes.inject { |memo, node|
235
- Nodes::Or.new([memo, node])
236
- }
234
+ Nodes::Grouping.new Nodes::Or.new(nodes)
237
235
  end
238
236
 
239
237
  def grouping_all(method_id, others, *extras)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.2.1
4
+ version: 8.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.1.2.1
18
+ version: 8.1.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.1.2.1
25
+ version: 8.1.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 8.1.2.1
32
+ version: 8.1.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.1.2.1
39
+ version: 8.1.3
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: timeout
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -478,10 +478,10 @@ licenses:
478
478
  - MIT
479
479
  metadata:
480
480
  bug_tracker_uri: https://github.com/rails/rails/issues
481
- changelog_uri: https://github.com/rails/rails/blob/v8.1.2.1/activerecord/CHANGELOG.md
482
- documentation_uri: https://api.rubyonrails.org/v8.1.2.1/
481
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.3/activerecord/CHANGELOG.md
482
+ documentation_uri: https://api.rubyonrails.org/v8.1.3/
483
483
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
484
- source_code_uri: https://github.com/rails/rails/tree/v8.1.2.1/activerecord
484
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.3/activerecord
485
485
  rubygems_mfa_required: 'true'
486
486
  rdoc_options:
487
487
  - "--main"