activerecord 8.1.0 → 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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -0
  3. data/lib/active_record/associations/join_dependency.rb +2 -2
  4. data/lib/active_record/associations/preloader/batch.rb +7 -1
  5. data/lib/active_record/associations.rb +2 -2
  6. data/lib/active_record/base.rb +1 -0
  7. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
  8. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  9. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
  10. data/lib/active_record/connection_adapters/abstract_adapter.rb +41 -36
  11. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
  12. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -0
  13. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -2
  14. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  15. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
  16. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  17. data/lib/active_record/enum.rb +2 -2
  18. data/lib/active_record/future_result.rb +2 -0
  19. data/lib/active_record/gem_version.rb +1 -1
  20. data/lib/active_record/insert_all.rb +2 -2
  21. data/lib/active_record/migration/command_recorder.rb +1 -1
  22. data/lib/active_record/railtie.rb +1 -1
  23. data/lib/active_record/relation/batches.rb +2 -2
  24. data/lib/active_record/relation/finder_methods.rb +1 -1
  25. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  26. data/lib/active_record/relation/where_clause.rb +1 -1
  27. data/lib/active_record/relation.rb +4 -2
  28. data/lib/active_record/runtime_registry.rb +1 -0
  29. data/lib/active_record/schema_dumper.rb +2 -2
  30. data/lib/active_record/type/json.rb +1 -3
  31. data/lib/active_record/type/serialized.rb +5 -0
  32. data/lib/active_record.rb +1 -1
  33. data/lib/arel/predications.rb +1 -3
  34. metadata +9 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a92548efaf2245d66ddb7383470faf4182ac1d28a65f71824ba6a83c69b753fd
4
- data.tar.gz: e019fc7a41a862fa0f0393ee6d8dd46589af1dd04ee0c9f1ddfde6716401c011
3
+ metadata.gz: bf11d5e6abc99265d39fb3f4b3b0d87f1e2bbdf6099a478306ba3d04ac2a760a
4
+ data.tar.gz: 4d47ba93fb7a68eb7cbadf3a001bb676b71b87a06b00504907717059c39190b5
5
5
  SHA512:
6
- metadata.gz: db83ab09afcb9232bc722208fd8add11064cefc613667592711d7ec21347e0933eddec2a9cdf1f4eb7f07a973a11d54a821b776da0861e320cfece6684e47a3c
7
- data.tar.gz: 4e47cbdb388a3f4e00b55f775bf2d9397b80e1f27478e8ce00b4cb783d3ee8de72088c5303697d1080c2f5f58d6c81c053c980c984f2755d6d8224453fa684f2
6
+ metadata.gz: bcbbfd82d1032400a7f3dfa0784bd5d506107d50d0ada3e1fefd26de665959e1f86cb2aa5f4e7cfbb24e69101e3b71542abd1ecffaa53963823ae47fa4e26475
7
+ data.tar.gz: ce6acb68c3c821dfda84556f5089effcf401cff05b8d819297430b77d51699bf096bc2ed69f78161f9750bd45bbe3ffaa2b591b08d6676dd437e8a9fc06952d4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,141 @@
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
+
59
+ ## Rails 8.1.2.1 (March 23, 2026) ##
60
+
61
+ * No changes.
62
+
63
+
64
+ ## Rails 8.1.2 (January 08, 2026) ##
65
+
66
+ * Fix counting cached queries in `ActiveRecord::RuntimeRegistry`.
67
+
68
+ *fatkodima*
69
+
70
+ * Fix merging relations with arel equality predicates with null relations.
71
+
72
+ *fatkodima*
73
+
74
+ * Fix SQLite3 schema dump for non-autoincrement integer primary keys.
75
+
76
+ Previously, `schema.rb` should incorrectly restore that table with an auto incrementing
77
+ primary key.
78
+
79
+ *Chris Hasiński*
80
+
81
+ * Fix PostgreSQL `schema_search_path` not being reapplied after `reset!` or `reconnect!`.
82
+
83
+ The `schema_search_path` configured in `database.yml` is now correctly
84
+ reapplied instead of falling back to PostgreSQL defaults.
85
+
86
+ *Tobias Egli*
87
+
88
+ * Restore the ability of enum to be foats.
89
+
90
+ ```ruby
91
+ enum :rating, { low: 0.0, medium: 0.5, high: 1.0 },
92
+ ```
93
+
94
+ In Rails 8.1.0, enum values are eagerly validated, and floats weren't expected.
95
+
96
+ *Said Kaldybaev*
97
+
98
+ * Ensure batched preloaded associations accounts for klass when grouping to avoid issues with STI.
99
+
100
+ *zzak*, *Stjepan Hadjic*
101
+
102
+ * Fix `ActiveRecord::SoleRecordExceeded#record` to return the relation.
103
+
104
+ This was the case until Rails 7.2, but starting from 8.0 it
105
+ started mistakenly returning the model class.
106
+
107
+ *Jean Boussier*
108
+
109
+ * Improve PostgreSQLAdapter resilience to Timeout.timeout.
110
+
111
+ Better handle asynchronous exceptions being thrown inside
112
+ the `reconnect!` method.
113
+
114
+ This may fixes some deep errors such as:
115
+
116
+ ```
117
+ undefined method `key?' for nil:NilClass (NoMethodError)
118
+ if !type_map.key?(oid)
119
+ ```
120
+
121
+ *Jean Boussier*
122
+
123
+ * Fix structured events for Active Record was not being emitted.
124
+
125
+ *Yuji Yaginuma*
126
+
127
+ * Fix `eager_load` when loading `has_many` assocations with composite primary keys.
128
+
129
+ This would result in some records being loaded multiple times.
130
+
131
+ *Martin-Alexander*
132
+
133
+
134
+ ## Rails 8.1.1 (October 28, 2025) ##
135
+
136
+ * No changes.
137
+
138
+
1
139
  ## Rails 8.1.0 (October 22, 2025) ##
2
140
 
3
141
  * Fix SQLite3 data loss during table alterations with CASCADE foreign keys.
@@ -103,7 +103,7 @@ module ActiveRecord
103
103
  end
104
104
 
105
105
  def instantiate(result_set, strict_loading_value, &block)
106
- primary_key = aliases.column_alias(join_root, join_root.primary_key)
106
+ primary_key = Array(join_root.primary_key).map { |column| aliases.column_alias(join_root, column) }
107
107
 
108
108
  seen = Hash.new { |i, parent|
109
109
  i[parent] = Hash.new { |j, child_class|
@@ -141,7 +141,7 @@ module ActiveRecord
141
141
 
142
142
  message_bus.instrument("instantiation.active_record", payload) do
143
143
  result_set.each { |row_hash|
144
- parent_key = primary_key ? row_hash[primary_key] : row_hash
144
+ parent_key = primary_key.empty? ? row_hash : row_hash.values_at(*primary_key)
145
145
  parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
146
146
  construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
147
147
  }
@@ -38,7 +38,13 @@ module ActiveRecord
38
38
  attr_reader :loaders
39
39
 
40
40
  def group_and_load_similar(loaders)
41
- loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders|
41
+ non_through = loaders.grep_v(ThroughAssociation)
42
+
43
+ grouped = non_through.group_by do |loader|
44
+ [loader.loader_query, loader.klass]
45
+ end
46
+
47
+ grouped.each do |(query, _klass), similar_loaders|
42
48
  query.load_records_in_batch(similar_loaders)
43
49
  end
44
50
  end
@@ -1030,9 +1030,9 @@ module ActiveRecord
1030
1030
  # associated records themselves, you can always do something along the lines of
1031
1031
  # <tt>person.tasks.each(&:destroy)</tt>.
1032
1032
  #
1033
- # == Deprecated Associations
1033
+ # == Deprecated \Associations
1034
1034
  #
1035
- # Associations can be marked as deprecated by passing <tt>deprecated: true</tt>:
1035
+ # \Associations can be marked as deprecated by passing <tt>deprecated: true</tt>:
1036
1036
  #
1037
1037
  # has_many :posts, deprecated: true
1038
1038
  #
@@ -6,6 +6,7 @@ require "active_support/descendants_tracker"
6
6
  require "active_support/time"
7
7
  require "active_support/core_ext/class/subclasses"
8
8
  require "active_record/log_subscriber"
9
+ require "active_record/structured_event_subscriber"
9
10
  require "active_record/relation/delegation"
10
11
  require "active_record/attributes"
11
12
  require "active_record/type_caster"
@@ -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:
@@ -713,37 +713,36 @@ module ActiveRecord
713
713
  deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
714
714
 
715
715
  @lock.synchronize do
716
- @allow_preconnect = false
716
+ attempt_configure_connection do
717
+ @allow_preconnect = false
717
718
 
718
- reconnect
719
+ reconnect
719
720
 
720
- enable_lazy_transactions!
721
- @raw_connection_dirty = false
722
- @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
723
- @verified = true
724
- @allow_preconnect = true
725
-
726
- reset_transaction(restore: restore_transactions) do
727
- clear_cache!(new_connection: true)
728
- attempt_configure_connection
729
- end
730
- rescue => original_exception
731
- translated_exception = translate_exception_class(original_exception, nil, nil)
732
- retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
721
+ enable_lazy_transactions!
722
+ @raw_connection_dirty = false
723
+ @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
724
+ @verified = true
725
+ @allow_preconnect = true
726
+
727
+ reset_transaction(restore: restore_transactions) do
728
+ clear_cache!(new_connection: true)
729
+ configure_connection
730
+ end
731
+ rescue => original_exception
732
+ translated_exception = translate_exception_class(original_exception, nil, nil)
733
+ retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
733
734
 
734
- if !retry_deadline_exceeded && retries_available > 0
735
- retries_available -= 1
735
+ if !retry_deadline_exceeded && retries_available > 0
736
+ retries_available -= 1
736
737
 
737
- if retryable_connection_error?(translated_exception)
738
- backoff(connection_retries - retries_available)
739
- retry
738
+ if retryable_connection_error?(translated_exception)
739
+ backoff(connection_retries - retries_available)
740
+ retry
741
+ end
740
742
  end
741
- end
742
-
743
- @last_activity = nil
744
- @verified = false
745
743
 
746
- raise translated_exception
744
+ raise translated_exception
745
+ end
747
746
  end
748
747
  end
749
748
 
@@ -755,6 +754,8 @@ module ActiveRecord
755
754
  reset_transaction
756
755
  @raw_connection_dirty = false
757
756
  @connected_since = nil
757
+ @last_activity = nil
758
+ @verified = false
758
759
  end
759
760
  end
760
761
 
@@ -777,9 +778,11 @@ module ActiveRecord
777
778
  # should call super immediately after resetting the connection (and while
778
779
  # still holding @lock).
779
780
  def reset!
780
- clear_cache!(new_connection: true)
781
- reset_transaction
782
- attempt_configure_connection
781
+ attempt_configure_connection do
782
+ clear_cache!(new_connection: true)
783
+ reset_transaction
784
+ configure_connection
785
+ end
783
786
  end
784
787
 
785
788
  # Removes the connection from the pool and disconnect it.
@@ -813,12 +816,14 @@ module ActiveRecord
813
816
  unless active?
814
817
  @lock.synchronize do
815
818
  if @unconfigured_connection
816
- @raw_connection = @unconfigured_connection
817
- @unconfigured_connection = nil
818
- attempt_configure_connection
819
- @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
820
- @verified = true
821
- @allow_preconnect = true
819
+ attempt_configure_connection do
820
+ @raw_connection = @unconfigured_connection
821
+ @unconfigured_connection = nil
822
+ configure_connection
823
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
824
+ @verified = true
825
+ @allow_preconnect = true
826
+ end
822
827
  return
823
828
  end
824
829
 
@@ -1282,7 +1287,7 @@ module ActiveRecord
1282
1287
  end
1283
1288
 
1284
1289
  def attempt_configure_connection
1285
- configure_connection
1290
+ yield
1286
1291
  rescue Exception # Need to handle things such as Timeout::ExitException
1287
1292
  disconnect!
1288
1293
  raise
@@ -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
@@ -395,6 +395,11 @@ module ActiveRecord
395
395
  end
396
396
  end
397
397
 
398
+ def clear_cache!(new_connection: false)
399
+ super
400
+ @schema_search_path = nil if new_connection
401
+ end
402
+
398
403
  # Disconnects from the database if already connected. Otherwise, this
399
404
  # method does nothing.
400
405
  def disconnect!
@@ -677,8 +682,8 @@ module ActiveRecord
677
682
  end
678
683
 
679
684
  def check_version # :nodoc:
680
- if database_version < 9_03_00 # < 9.3
681
- 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."
682
687
  end
683
688
  end
684
689
 
@@ -1009,6 +1014,8 @@ module ActiveRecord
1009
1014
  add_pg_encoders
1010
1015
  add_pg_decoders
1011
1016
 
1017
+ schema_search_path # populate cache
1018
+
1012
1019
  reload_type_map
1013
1020
  end
1014
1021
 
@@ -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."
@@ -19,11 +19,23 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def default_primary_key?(column)
22
- schema_type(column) == :integer
22
+ schema_type(column) == :integer && primary_key_has_autoincrement?
23
23
  end
24
24
 
25
25
  def explicit_primary_key_default?(column)
26
- column.bigint?
26
+ column.bigint? || (column.type == :integer && !primary_key_has_autoincrement?)
27
+ end
28
+
29
+ def primary_key_has_autoincrement?
30
+ return false unless table_name
31
+
32
+ table_sql = @connection.query_value(<<~SQL, "SCHEMA")
33
+ SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
34
+ UNION ALL
35
+ SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
36
+ SQL
37
+
38
+ table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
27
39
  end
28
40
 
29
41
  def prepare_column_options(column)
@@ -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
@@ -349,10 +349,10 @@ module ActiveRecord
349
349
 
350
350
  values.each_value do |value|
351
351
  case value
352
- when String, Integer, true, false, nil
352
+ when String, Integer, Float, true, false, nil
353
353
  # noop
354
354
  else
355
- raise ArgumentError, "Enum values #{values} must be only booleans, integers, symbols or strings, got: #{value.class}"
355
+ raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
356
356
  end
357
357
  end
358
358
 
@@ -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,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 1
12
- TINY = 0
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record"
4
3
  require "rails"
4
+ require "active_record"
5
5
  require "active_support/core_ext/object/try"
6
6
  require "active_model/railtie"
7
7
 
@@ -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.
@@ -148,7 +148,7 @@ module ActiveRecord
148
148
  elsif undesired.nil?
149
149
  found
150
150
  else
151
- raise ActiveRecord::SoleRecordExceeded.new(model)
151
+ raise ActiveRecord::SoleRecordExceeded.new(self)
152
152
  end
153
153
  end
154
154
 
@@ -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
 
@@ -182,7 +182,7 @@ module ActiveRecord
182
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
183
183
 
184
184
  predicates.reject do |node|
185
- if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
185
+ if !non_attrs.empty? && equality_node?(node) && node.left.is_a?(Arel::Predications)
186
186
  non_attrs.include?(node.left)
187
187
  end || Arel.fetch_attribute(node) do |attr|
188
188
  attrs.include?(attr) || columns.include?(attr.name.to_s)
@@ -869,7 +869,9 @@ module ActiveRecord
869
869
  # Active Record's schema_cache.
870
870
  #
871
871
  # [:on_duplicate]
872
- # Configure the SQL update sentence that will be used in case of conflict.
872
+ # Configure the behavior that will be used in case of conflict. Use `:skip`
873
+ # to ignore any conflicts or provide a safe SQL fragment wrapped with
874
+ # `Arel.sql`.
873
875
  #
874
876
  # NOTE: If you use this option you must provide all the columns you want to update
875
877
  # by yourself.
@@ -1020,7 +1022,7 @@ module ActiveRecord
1020
1022
  #
1021
1023
  # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
1022
1024
  #
1023
- # Both calls delete the affected posts all at once with a single DELETE statement.
1025
+ # This call deletes the affected posts all at once with a single DELETE statement.
1024
1026
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
1025
1027
  # +after_destroy+ callbacks, use the #destroy_all method instead.
1026
1028
  #
@@ -33,6 +33,7 @@ module ActiveRecord
33
33
  record(
34
34
  payload[:name],
35
35
  (finish - start) * 1_000.0,
36
+ cached: payload[:cached],
36
37
  async: payload[:async],
37
38
  lock_wait: payload[:lock_wait],
38
39
  )
@@ -232,8 +232,8 @@ module ActiveRecord
232
232
  def indexes(table, stream)
233
233
  if (indexes = @connection.indexes(table)).any?
234
234
  add_index_statements = indexes.map do |index|
235
- table_name = remove_prefix_and_suffix(index.table).inspect
236
- " add_index #{([relation_name(table_name)] + index_parts(index)).join(', ')}"
235
+ table_name = remove_prefix_and_suffix(index.table)
236
+ " add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
237
237
  end
238
238
 
239
239
  stream.puts add_index_statements.sort.join("\n")
@@ -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.0
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.0
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.0
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.0
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.0
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.0/activerecord/CHANGELOG.md
482
- documentation_uri: https://api.rubyonrails.org/v8.1.0/
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.0/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"
@@ -499,7 +499,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
499
499
  - !ruby/object:Gem::Version
500
500
  version: '0'
501
501
  requirements: []
502
- rubygems_version: 3.6.9
502
+ rubygems_version: 4.0.6
503
503
  specification_version: 4
504
504
  summary: Object-relational mapper framework (part of Rails).
505
505
  test_files: []