Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rubygem-activerecord-8.0 for openSUSE:Factory checked in at 2025-08-22 17:49:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-activerecord-8.0 (Old) and /work/SRC/openSUSE:Factory/.rubygem-activerecord-8.0.new.29662 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-activerecord-8.0" Fri Aug 22 17:49:22 2025 rev:5 rq:1300933 version:8.0.2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-activerecord-8.0/rubygem-activerecord-8.0.changes 2025-08-21 17:00:34.765106206 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-activerecord-8.0.new.29662/rubygem-activerecord-8.0.changes 2025-08-22 17:50:56.627798810 +0200 @@ -1,0 +2,6 @@ +Thu Aug 21 10:20:54 UTC 2025 - Marcus Rueckert <mrueck...@suse.de> + +- Drop CVE-2025-55193.patch: + it is already handled in the version update + +------------------------------------------------------------------- @@ -5,0 +12,6 @@ + +------------------------------------------------------------------- +Thu Aug 14 00:25:11 UTC 2025 - Marcus Rueckert <mrueck...@suse.de> + +- Update to version 8.0.2.1: + https://rubyonrails.org/2025/8/13/Rails-Versions-8-0-2-1-7-2-2-2-and-7-1-5-2-have-been-released Old: ---- CVE-2025-55193.patch activerecord-8.0.1.gem New: ---- activerecord-8.0.2.1.gem ----------(Old B)---------- Old: - Drop CVE-2025-55193.patch: it is already handled in the version update ----------(Old E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-activerecord-8.0.spec ++++++ --- /var/tmp/diff_new_pack.gMLZfF/_old 2025-08-22 17:50:57.363829481 +0200 +++ /var/tmp/diff_new_pack.gMLZfF/_new 2025-08-22 17:50:57.367829648 +0200 @@ -1,7 +1,7 @@ # # spec file for package rubygem-activerecord-8.0 # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,7 +24,7 @@ # Name: rubygem-activerecord-8.0 -Version: 8.0.1 +Version: 8.0.2.1 Release: 0 %define mod_name activerecord %define mod_full_name %{mod_name}-%{version} @@ -36,7 +36,6 @@ URL: https://rubyonrails.org Source: https://rubygems.org/gems/%{mod_full_name}.gem Source1: gem2rpm.yml -Patch0: CVE-2025-55193.patch Summary: Object-relational mapper framework (part of Rails) License: MIT @@ -46,10 +45,6 @@ aggregations, migrations, and testing come baked-in. %prep -%gem_unpack -%patch -P 0 -p1 -find -type f -print0 | xargs -0 touch -r %{S:0} -%gem_build %build ++++++ activerecord-8.0.1.gem -> activerecord-8.0.2.1.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CHANGELOG.md new/CHANGELOG.md --- old/CHANGELOG.md 2024-12-13 21:02:34.000000000 +0100 +++ new/CHANGELOG.md 1980-01-02 01:00:00.000000000 +0100 @@ -1,6 +1,151 @@ +## Rails 8.0.2.1 (August 13, 2025) ## + +* Call inspect on ids in RecordNotFound error + + [CVE-2025-55193] + + *Gannon McGibbon*, *John Hawthorn* + +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* Fix inverting `rename_enum_value` when `:from`/`:to` are provided. + + *fatkodima* + +* Prevent persisting invalid record. + + *Edouard Chin* + +* Fix inverting `drop_table` without options. + + *fatkodima* + +* Fix count with group by qualified name on loaded relation. + + *Ryuta Kamizono* + +* Fix `sum` with qualified name on loaded relation. + + *Chris Gunther* + +* The SQLite3 adapter quotes non-finite Numeric values like "Infinity" and "NaN". + + *Mike Dalessio* + +* Handle libpq returning a database version of 0 on no/bad connection in `PostgreSQLAdapter`. + + Before, this version would be cached and an error would be raised during connection configuration when + comparing it with the minimum required version for the adapter. This meant that the connection could + never be successfully configured on subsequent reconnection attempts. + + Now, this is treated as a connection failure consistent with libpq, raising a `ActiveRecord::ConnectionFailed` + and ensuring the version isn't cached, which allows the version to be retrieved on the next connection attempt. + + *Joshua Young*, *Rian McGuire* + +* Fix error handling during connection configuration. + + Active Record wasn't properly handling errors during the connection configuration phase. + This could lead to a partially configured connection being used, resulting in various exceptions, + the most common being with the PostgreSQLAdapter raising `undefined method `key?' for nil` + or `TypeError: wrong argument type nil (expected PG::TypeMap)`. + + *Jean Boussier* + +* Fix a case where a non-retryable query could be marked retryable. + + *Hartley McGuire* + +* Handle circular references when autosaving associations. + + *zzak* + +* PoolConfig no longer keeps a reference to the connection class. + + Keeping a reference to the class caused subtle issues when combined with reloading in + development. Fixes #54343. + + *Mike Dalessio* + +* Fix SQL notifications sometimes not sent when using async queries. + + ```ruby + Post.async_count + ActiveSupport::Notifications.subscribed(->(*) { "Will never reach here" }) do + Post.count + end + ``` + + In rare circumstances and under the right race condition, Active Support notifications + would no longer be dispatched after using an asynchronous query. + This is now fixed. + + *Edouard Chin* + +* Fix support for PostgreSQL enum types with commas in their name. + + *Arthur Hess* + +* Fix inserts on MySQL with no RETURNING support for a table with multiple auto populated columns. + + *Nikita Vasilevsky* + +* Fix joining on a scoped association with string joins and bind parameters. + + ```ruby + class Instructor < ActiveRecord::Base + has_many :instructor_roles, -> { active } + end + + class InstructorRole < ActiveRecord::Base + scope :active, -> { + joins("JOIN students ON instructor_roles.student_id = students.id") + .where(students { status: 1 }) + } + end + + Instructor.joins(:instructor_roles).first + ``` + + The above example would result in `ActiveRecord::StatementInvalid` because the + `active` scope bind parameters would be lost. + + *Jean Boussier* + +* Fix a potential race condition with system tests and transactional fixtures. + + *Sjoerd Lagarde* + +* Fix autosave associations to no longer validated unmodified associated records. + + Active Record was incorrectly performing validation on associated record that + weren't created nor modified as part of the transaction: + + ```ruby + Post.create!(author: User.find(1)) # Fail if user is invalid + ``` + + *Jean Boussier* + +* Remember when a database connection has recently been verified (for + two seconds, by default), to avoid repeated reverifications during a + single request. + + This should recreate a similar rate of verification as in Rails 7.1, + where connections are leased for the duration of a request, and thus + only verified once. + + *Matthew Draper* + + ## Rails 8.0.1 (December 13, 2024) ## -* Fix removing foreign keys with :restrict action for MySQ +* Fix removing foreign keys with :restrict action for MySQL. *fatkodima* Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/associations/alias_tracker.rb new/lib/active_record/associations/alias_tracker.rb --- old/lib/active_record/associations/alias_tracker.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/associations/alias_tracker.rb 1980-01-02 01:00:00.000000000 +0100 @@ -26,16 +26,18 @@ end def self.initial_count_for(connection, name, table_joins) - quoted_name = nil + quoted_name_escaped = nil + name_escaped = nil counts = table_joins.map do |join| if join.is_a?(Arel::Nodes::StringJoin) - # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase - quoted_name ||= connection.quote_table_name(name) + # quoted_name_escaped should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name_escaped ||= Regexp.escape(connection.quote_table_name(name)) + name_escaped ||= Regexp.escape(name) # Table names + table aliases join.left.scan( - /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name_escaped}|#{name_escaped})\sON/i ).size elsif join.is_a?(Arel::Nodes::Join) join.left.name == name ? 1 : 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/associations/join_dependency/join_association.rb new/lib/active_record/associations/join_dependency/join_association.rb --- old/lib/active_record/associations/join_dependency/join_association.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/associations/join_dependency/join_association.rb 1980-01-02 01:00:00.000000000 +0100 @@ -38,41 +38,39 @@ chain << [reflection, table] end - base_klass.with_connection do |connection| - # The chain starts with the target table, but we want to end with it here (makes - # more sense in this context), so we reverse - chain.reverse_each do |reflection, table| - klass = reflection.klass - - scope = reflection.join_scope(table, foreign_table, foreign_klass) - - unless scope.references_values.empty? - associations = scope.eager_load_values | scope.includes_values - - unless associations.empty? - scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin) - end - end + # The chain starts with the target table, but we want to end with it here (makes + # more sense in this context), so we reverse + chain.reverse_each do |reflection, table| + klass = reflection.klass + + scope = reflection.join_scope(table, foreign_table, foreign_klass) - arel = scope.arel(alias_tracker.aliases) - nodes = arel.constraints.first + unless scope.references_values.empty? + associations = scope.eager_load_values | scope.includes_values - if nodes.is_a?(Arel::Nodes::And) - others = nodes.children.extract! do |node| - !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name } - end + unless associations.empty? + scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin) end + end - joins << join_type.new(table, Arel::Nodes::On.new(nodes)) + arel = scope.arel(alias_tracker.aliases) + nodes = arel.constraints.first - if others && !others.empty? - joins.concat arel.join_sources - append_constraints(connection, joins.last, others) + if nodes.is_a?(Arel::Nodes::And) + others = nodes.children.extract! do |node| + !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name } end + end + + joins << join_type.new(table, Arel::Nodes::On.new(nodes)) - # The current table in this iteration becomes the foreign table in the next - foreign_table, foreign_klass = table, klass + if others && !others.empty? + joins.concat arel.join_sources + append_constraints(joins.last, others) end + + # The current table in this iteration becomes the foreign table in the next + foreign_table, foreign_klass = table, klass end joins @@ -91,10 +89,10 @@ end private - def append_constraints(connection, join, constraints) + def append_constraints(join, constraints) if join.is_a?(Arel::Nodes::StringJoin) join_string = Arel::Nodes::And.new(constraints.unshift join.left) - join.left = Arel.sql(connection.visitor.compile(join_string)) + join.left = join_string else right = join.right right.expr = Arel::Nodes::And.new(constraints.unshift right.expr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/attributes.rb new/lib/active_record/attributes.rb --- old/lib/active_record/attributes.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/attributes.rb 1980-01-02 01:00:00.000000000 +0100 @@ -178,8 +178,8 @@ # @currency_converter = currency_converter # end # - # # value will be the result of +deserialize+ or - # # +cast+. Assumed to be an instance of +Money+ in + # # value will be the result of #deserialize or + # # #cast. Assumed to be an instance of Money in # # this case. # def serialize(value) # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/autosave_association.rb new/lib/active_record/autosave_association.rb --- old/lib/active_record/autosave_association.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/autosave_association.rb 1980-01-02 01:00:00.000000000 +0100 @@ -372,19 +372,29 @@ return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?) context = validation_context if custom_validation_context? + return true if record.valid?(context) - unless valid = record.valid?(context) - if association.options[:autosave] - record.errors.each { |error| - self.errors.objects.append( - Associations::NestedError.new(association, error) - ) - } - else - errors.add(association.reflection.name) - end + if record.changed? || record.new_record? || context + associated_errors = record.errors.objects + else + # If there are existing invalid records in the DB, we should still be able to reference them. + # Unless a record (no matter where in the association chain) is invalid and is being changed. + associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) } end - valid + + if association.options[:autosave] + return if equal?(record) + + associated_errors.each { |error| + errors.objects.append( + Associations::NestedError.new(association, error) + ) + } + elsif associated_errors.any? + errors.add(association.reflection.name) + end + + errors.any? end # Is used as an around_save callback to check while saving a collection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/abstract/connection_handler.rb new/lib/active_record/connection_adapters/abstract/connection_handler.rb --- old/lib/active_record/connection_adapters/abstract/connection_handler.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/abstract/connection_handler.rb 1980-01-02 01:00:00.000000000 +0100 @@ -54,19 +54,22 @@ # about the model. The model needs to pass a connection specification name to the handler, # in order to look up the correct connection pool. class ConnectionHandler - class StringConnectionName # :nodoc: - attr_reader :name - - def initialize(name) + class ConnectionDescriptor # :nodoc: + def initialize(name, primary = false) @name = name + @primary = primary + end + + def name + primary_class? ? "ActiveRecord::Base" : @name end def primary_class? - false + @primary end def current_preventing_writes - false + ActiveRecord::Base.preventing_writes?(@name) end end @@ -115,7 +118,7 @@ pool_config = resolve_pool_config(config, owner_name, role, shard) db_config = pool_config.db_config - pool_manager = set_pool_manager(pool_config.connection_name) + pool_manager = set_pool_manager(pool_config.connection_descriptor) # If there is an existing pool with the same values as the pool_config # don't remove the connection. Connections should only be removed if we are @@ -127,8 +130,8 @@ # Update the pool_config's connection class if it differs. This is used # for ensuring that ActiveRecord::Base and the primary_abstract_class use # the same pool. Without this granular swapping will not work correctly. - if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name) - existing_pool_config.connection_class = owner_name + if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name) + existing_pool_config.connection_descriptor = owner_name end existing_pool_config.pool @@ -137,7 +140,7 @@ pool_manager.set_pool_config(role, shard, pool_config) payload = { - connection_name: pool_config.connection_name, + connection_name: pool_config.connection_descriptor.name, role: role, shard: shard, config: db_config.configuration_hash @@ -242,8 +245,8 @@ end # Get the existing pool manager or initialize and assign a new one. - def set_pool_manager(connection_name) - connection_name_to_pool_manager[connection_name] ||= PoolManager.new + def set_pool_manager(connection_descriptor) + connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new end def pool_managers @@ -278,9 +281,9 @@ def determine_owner_name(owner_name, config) if owner_name.is_a?(String) || owner_name.is_a?(Symbol) - StringConnectionName.new(owner_name.to_s) + ConnectionDescriptor.new(owner_name.to_s) elsif config.is_a?(Symbol) - StringConnectionName.new(config.to_s) + ConnectionDescriptor.new(config.to_s) else owner_name end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/abstract/connection_pool.rb new/lib/active_record/connection_adapters/abstract/connection_pool.rb --- old/lib/active_record/connection_adapters/abstract/connection_pool.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/abstract/connection_pool.rb 1980-01-02 01:00:00.000000000 +0100 @@ -36,7 +36,7 @@ end def schema_cache; end - def connection_class; end + def connection_descriptor; end def checkin(_); end def remove(_); end def async_executor; end @@ -364,8 +364,8 @@ clean end - def connection_class # :nodoc: - pool_config.connection_class + def connection_descriptor # :nodoc: + pool_config.connection_descriptor end # Returns true if there is an open connection being used for the current thread. @@ -545,20 +545,25 @@ # Raises: # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool. def checkout(checkout_timeout = @checkout_timeout) - if @pinned_connection - @pinned_connection.lock.synchronize do - synchronize do + return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection + + @pinned_connection.lock.synchronize do + synchronize do + # The pinned connection may have been cleaned up before we synchronized, so check if it is still present + if @pinned_connection @pinned_connection.verify! + # Any leased connection must be in @connections otherwise # some methods like #connected? won't behave correctly unless @connections.include?(@pinned_connection) @connections << @pinned_connection end + + @pinned_connection + else + checkout_and_verify(acquire_connection(checkout_timeout)) end end - @pinned_connection - else - checkout_and_verify(acquire_connection(checkout_timeout)) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/abstract/schema_definitions.rb new/lib/active_record/connection_adapters/abstract/schema_definitions.rb --- old/lib/active_record/connection_adapters/abstract/schema_definitions.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/abstract/schema_definitions.rb 1980-01-02 01:00:00.000000000 +0100 @@ -434,7 +434,7 @@ # # == Examples # - # # Assuming +td+ is an instance of TableDefinition + # # Assuming `td` is an instance of TableDefinition # td.column(:granted, :boolean, index: true) # # == Short-hand examples diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/abstract_adapter.rb new/lib/active_record/connection_adapters/abstract_adapter.rb --- old/lib/active_record/connection_adapters/abstract_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/abstract_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -150,7 +150,6 @@ end @owner = nil - @instrumenter = ActiveSupport::Notifications.instrumenter @pool = ActiveRecord::ConnectionAdapters::NullPool.new @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) @visitor = arel_visitor @@ -168,6 +167,7 @@ @default_timezone = self.class.validate_default_timezone(@config[:default_timezone]) @raw_connection_dirty = false + @last_activity = nil @verified = false end @@ -190,19 +190,6 @@ end end - EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc: - EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc: - private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE - def with_instrumenter(instrumenter, &block) # :nodoc: - Thread.handle_interrupt(EXCEPTION_NEVER) do - previous_instrumenter = @instrumenter - @instrumenter = instrumenter - Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block) - ensure - @instrumenter = previous_instrumenter - end - end - def check_if_write_query(sql) # :nodoc: if preventing_writes? && write_query?(sql) raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" @@ -217,6 +204,10 @@ (@config[:connection_retries] || 1).to_i end + def verify_timeout + (@config[:verify_timeout] || 2).to_i + end + def retry_deadline if @config[:retry_deadline] @config[:retry_deadline].to_f @@ -235,9 +226,9 @@ # the value of +current_preventing_writes+. def preventing_writes? return true if replica? - return false if connection_class.nil? + return false if connection_descriptor.nil? - connection_class.current_preventing_writes + connection_descriptor.current_preventing_writes end def prepared_statements? @@ -288,8 +279,8 @@ @owner = ActiveSupport::IsolatedExecutionState.context end - def connection_class # :nodoc: - @pool.connection_class + def connection_descriptor # :nodoc: + @pool.connection_descriptor end # The role (e.g. +:writing+) for the current connection. In a @@ -343,6 +334,13 @@ Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since end + # Seconds since this connection last communicated with the server + def seconds_since_last_activity # :nodoc: + if @raw_connection && @last_activity + Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity + end + end + def unprepared_statement cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements yield @@ -670,11 +668,12 @@ enable_lazy_transactions! @raw_connection_dirty = false + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) @verified = true reset_transaction(restore: restore_transactions) do clear_cache!(new_connection: true) - configure_connection + attempt_configure_connection end rescue => original_exception translated_exception = translate_exception_class(original_exception, nil, nil) @@ -689,6 +688,7 @@ end end + @last_activity = nil @verified = false raise translated_exception @@ -726,7 +726,7 @@ def reset! clear_cache!(new_connection: true) reset_transaction - configure_connection + attempt_configure_connection end # Removes the connection from the pool and disconnect it. @@ -762,7 +762,8 @@ if @unconfigured_connection @raw_connection = @unconfigured_connection @unconfigured_connection = nil - configure_connection + attempt_configure_connection + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) @verified = true return end @@ -992,6 +993,9 @@ if @verified # Cool, we're confident the connection's ready to use. (Note this might have # become true during the above #materialize_transactions.) + elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout + # We haven't actually verified the connection since we acquired it, but it + # has been used very recently. We're going to assume it's still okay. elsif reconnectable if allow_retry # Not sure about the connection yet, but if anything goes wrong we can @@ -1033,6 +1037,7 @@ # Barring a known-retryable error inside the query (regardless of # whether we were in a _position_ to retry it), we should infer that # there's likely a real problem with the connection. + @last_activity = nil @verified = false end @@ -1047,6 +1052,7 @@ # `with_raw_connection` block only when the block is guaranteed to # exercise the raw connection. def verified! + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) @verified = true end @@ -1126,7 +1132,7 @@ end def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc: - @instrumenter.instrument( + instrumenter.instrument( "sql.active_record", sql: sql, name: name, @@ -1142,6 +1148,10 @@ raise ex.set_query(sql, binds) end + def instrumenter # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter + end + def translate_exception(exception, message:, sql:, binds:) # override in derived class case exception @@ -1203,6 +1213,13 @@ check_version end + def attempt_configure_connection + configure_connection + rescue + disconnect! + raise + end + def default_prepared_statements true end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new/lib/active_record/connection_adapters/abstract_mysql_adapter.rb --- old/lib/active_record/connection_adapters/abstract_mysql_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/abstract_mysql_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -174,6 +174,10 @@ mariadb? && database_version >= "10.5.0" end + def return_value_after_insert?(column) # :nodoc: + supports_insert_returning? ? column.auto_populated? : column.auto_increment? + end + def get_advisory_lock(lock_name, timeout = 0) # :nodoc: query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1 end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/mysql2_adapter.rb new/lib/active_record/connection_adapters/mysql2_adapter.rb --- old/lib/active_record/connection_adapters/mysql2_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/mysql2_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -106,7 +106,14 @@ end def active? - connected? && @lock.synchronize { @raw_connection&.ping } || false + if connected? + @lock.synchronize do + if @raw_connection&.ping + verified! + true + end + end + end || false end alias :reset! :reconnect! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/pool_config.rb new/lib/active_record/connection_adapters/pool_config.rb --- old/lib/active_record/connection_adapters/pool_config.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/pool_config.rb 1980-01-02 01:00:00.000000000 +0100 @@ -5,9 +5,8 @@ class PoolConfig # :nodoc: include MonitorMixin - attr_reader :db_config, :role, :shard + attr_reader :db_config, :role, :shard, :connection_descriptor attr_writer :schema_reflection, :server_version - attr_accessor :connection_class def schema_reflection @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path) @@ -29,7 +28,7 @@ def initialize(connection_class, db_config, role, shard) super() @server_version = nil - @connection_class = connection_class + self.connection_descriptor = connection_class @db_config = db_config @role = role @shard = shard @@ -41,11 +40,12 @@ @server_version || synchronize { @server_version ||= connection.get_database_version } end - def connection_name - if connection_class.primary_class? - "ActiveRecord::Base" + def connection_descriptor=(connection_descriptor) + case connection_descriptor + when ConnectionHandler::ConnectionDescriptor + @connection_descriptor = connection_descriptor else - connection_class.name + @connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new/lib/active_record/connection_adapters/postgresql/schema_definitions.rb --- old/lib/active_record/connection_adapters/postgresql/schema_definitions.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/postgresql/schema_definitions.rb 1980-01-02 01:00:00.000000000 +0100 @@ -308,8 +308,8 @@ # t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check") # # See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint] - def exclusion_constraint(*args) - @base.add_exclusion_constraint(name, *args) + def exclusion_constraint(...) + @base.add_exclusion_constraint(name, ...) end # Removes the given exclusion constraint from the table. @@ -317,8 +317,8 @@ # t.remove_exclusion_constraint(name: "price_check") # # See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint] - def remove_exclusion_constraint(*args) - @base.remove_exclusion_constraint(name, *args) + def remove_exclusion_constraint(...) + @base.remove_exclusion_constraint(name, ...) end # Adds a unique constraint. @@ -326,8 +326,8 @@ # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true) # # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint] - def unique_constraint(*args) - @base.add_unique_constraint(name, *args) + def unique_constraint(...) + @base.add_unique_constraint(name, ...) end # Removes the given unique constraint from the table. @@ -335,8 +335,8 @@ # t.remove_unique_constraint(name: "unique_position") # # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint] - def remove_unique_constraint(*args) - @base.remove_unique_constraint(name, *args) + def remove_unique_constraint(...) + @base.remove_unique_constraint(name, ...) end # Validates the given constraint on the table. @@ -345,8 +345,8 @@ # t.validate_constraint "price_check" # # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint] - def validate_constraint(*args) - @base.validate_constraint(name, *args) + def validate_constraint(...) + @base.validate_constraint(name, ...) end # Validates the given check constraint on the table @@ -355,8 +355,8 @@ # t.validate_check_constraint name: "price_check" # # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint] - def validate_check_constraint(*args) - @base.validate_check_constraint(name, *args) + def validate_check_constraint(...) + @base.validate_check_constraint(name, ...) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/postgresql/schema_dumper.rb new/lib/active_record/connection_adapters/postgresql/schema_dumper.rb --- old/lib/active_record/connection_adapters/postgresql/schema_dumper.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/postgresql/schema_dumper.rb 1980-01-02 01:00:00.000000000 +0100 @@ -22,7 +22,7 @@ stream.puts " # Custom types defined in this database." stream.puts " # Note that some types may not work with other database engines. Be careful if changing database." types.sort.each do |name, values| - stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}" + stream.puts " create_enum #{name.inspect}, #{values.inspect}" end stream.puts end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/postgresql_adapter.rb new/lib/active_record/connection_adapters/postgresql_adapter.rb --- old/lib/active_record/connection_adapters/postgresql_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/postgresql_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -349,6 +349,7 @@ @lock.synchronize do return false unless @raw_connection @raw_connection.query ";" + verified! end true rescue PG::Error @@ -520,7 +521,7 @@ type.typname AS name, type.OID AS oid, n.nspname AS schema, - string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value + array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value FROM pg_enum AS enum JOIN pg_type AS type ON (type.oid = enum.enumtypid) JOIN pg_namespace n ON type.typnamespace = n.oid @@ -633,7 +634,11 @@ # Returns the version of the connected PostgreSQL server. def get_database_version # :nodoc: with_raw_connection do |conn| - conn.server_version + version = conn.server_version + if version == 0 + raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version" + end + version end end alias :postgresql_version :database_version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/sqlite3/quoting.rb new/lib/active_record/connection_adapters/sqlite3/quoting.rb --- old/lib/active_record/connection_adapters/sqlite3/quoting.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/sqlite3/quoting.rb 1980-01-02 01:00:00.000000000 +0100 @@ -50,6 +50,19 @@ end end + def quote(value) # :nodoc: + case value + when Numeric + if value.finite? + super + else + "'#{value}'" + end + else + super + end + end + def quote_string(s) ::SQLite3::Database.quote(s) end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/sqlite3_adapter.rb new/lib/active_record/connection_adapters/sqlite3_adapter.rb --- old/lib/active_record/connection_adapters/sqlite3_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/sqlite3_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -207,7 +207,12 @@ !(@raw_connection.nil? || @raw_connection.closed?) end - alias_method :active?, :connected? + def active? + if connected? + verified! + true + end + end alias :reset! :reconnect! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/connection_adapters/trilogy_adapter.rb new/lib/active_record/connection_adapters/trilogy_adapter.rb --- old/lib/active_record/connection_adapters/trilogy_adapter.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/connection_adapters/trilogy_adapter.rb 1980-01-02 01:00:00.000000000 +0100 @@ -121,7 +121,7 @@ end def active? - connected? && @lock.synchronize { @raw_connection&.ping } || false + connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false rescue ::Trilogy::Error false end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/core.rb new/lib/active_record/core.rb --- old/lib/active_record/core.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/core.rb 1980-01-02 01:00:00.000000000 +0100 @@ -202,6 +202,17 @@ false end + # Intended to behave like `.current_preventing_writes` given the class name as input. + # See PoolConfig and ConnectionHandler::ConnectionDescriptor. + def self.preventing_writes?(class_name) # :nodoc: + connected_to_stack.reverse_each do |hash| + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base) + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name } + end + + false + end + def self.connected_to_stack # :nodoc: if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] connected_to_stack @@ -266,7 +277,7 @@ return super if StatementCache.unsupported_value?(id) cached_find_by([primary_key], [id]) || - raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id)) + raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id)) end def find_by(*args) # :nodoc: @@ -727,11 +738,29 @@ @strict_loading_mode == :all end - # Marks this record as read only. + # Prevents records from being written to the database: + # + # customer = Customer.new + # customer.readonly! + # customer.save # raises ActiveRecord::ReadOnlyRecord # # customer = Customer.first # customer.readonly! - # customer.save # Raises an ActiveRecord::ReadOnlyRecord + # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord + # + # Read-only records cannot be deleted from the database either: + # + # customer = Customer.first + # customer.readonly! + # customer.destroy # raises ActiveRecord::ReadOnlyRecord + # + # Please, note that the objects themselves are still mutable in memory: + # + # customer = Customer.new + # customer.readonly! + # customer.name = 'New Name' # OK + # + # but you won't be able to persist the changes. def readonly! @readonly = true end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/counter_cache.rb new/lib/active_record/counter_cache.rb --- old/lib/active_record/counter_cache.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/counter_cache.rb 1980-01-02 01:00:00.000000000 +0100 @@ -28,7 +28,7 @@ # # For the Post with id #1, reset the comments_count # Post.reset_counters(1, :comments) # - # # Like above, but also touch the +updated_at+ and/or +updated_on+ + # # Like above, but also touch the updated_at and/or updated_on # # attributes. # Post.reset_counters(1, :comments, touch: true) def reset_counters(id, *counters, touch: nil) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/delegated_type.rb new/lib/active_record/delegated_type.rb --- old/lib/active_record/delegated_type.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/delegated_type.rb 1980-01-02 01:00:00.000000000 +0100 @@ -181,16 +181,16 @@ # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy # end # - # Entry#entryable_class # => +Message+ or +Comment+ - # Entry#entryable_name # => "message" or "comment" - # Entry.messages # => Entry.where(entryable_type: "Message") - # Entry#message? # => true when entryable_type == "Message" - # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil - # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil - # Entry.comments # => Entry.where(entryable_type: "Comment") - # Entry#comment? # => true when entryable_type == "Comment" - # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil - # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil + # @entry.entryable_class # => Message or Comment + # @entry.entryable_name # => "message" or "comment" + # Entry.messages # => Entry.where(entryable_type: "Message") + # @entry.message? # => true when entryable_type == "Message" + # @entry.message # => returns the message record, when entryable_type == "Message", otherwise nil + # @entry.message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil + # Entry.comments # => Entry.where(entryable_type: "Comment") + # @entry.comment? # => true when entryable_type == "Comment" + # @entry.comment # => returns the comment record, when entryable_type == "Comment", otherwise nil + # @entry.comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil # # You can also declare namespaced types: # @@ -199,25 +199,25 @@ # end # # Entry.access_notice_messages - # entry.access_notice_message - # entry.access_notice_message? + # @entry.access_notice_message + # @entry.access_notice_message? # # === Options # # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc. # The following options can be included to specialize the behavior of the delegated type convenience methods. # - # [:foreign_key] + # [+:foreign_key+] # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed # +role+ with an "_id" suffix. So a class that defines a # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as # the default <tt>:foreign_key</tt>. - # [:foreign_type] + # [+:foreign_type+] # Specify the column used to store the associated object's type. By default this is inferred to be the passed # +role+ with a "_type" suffix. A class that defines a # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as # the default <tt>:foreign_type</tt>. - # [:primary_key] + # [+:primary_key+] # Specify the method that returns the primary key of associated object used for the convenience methods. # By default this is +id+. # @@ -226,8 +226,8 @@ # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid # end # - # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil - # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil + # @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil + # @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil def delegated_type(role, types:, **options) belongs_to role, options.delete(:scope), **options.merge(polymorphic: true) define_delegated_type_methods role, types: types, options: options diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/future_result.rb new/lib/active_record/future_result.rb --- old/lib/active_record/future_result.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/future_result.rb 1980-01-02 01:00:00.000000000 +0100 @@ -108,9 +108,9 @@ begin if pending? @event_buffer = EventBuffer.new(self, @instrumenter) - connection.with_instrumenter(@event_buffer) do - execute_query(connection, async: true) - end + ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer + + execute_query(connection, async: true) end ensure @mutex.unlock diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/gem_version.rb new/lib/active_record/gem_version.rb --- old/lib/active_record/gem_version.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/gem_version.rb 1980-01-02 01:00:00.000000000 +0100 @@ -9,8 +9,8 @@ module VERSION MAJOR = 8 MINOR = 0 - TINY = 1 - PRE = nil + TINY = 2 + PRE = "1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/migration/command_recorder.rb new/lib/active_record/migration/command_recorder.rb --- old/lib/active_record/migration/command_recorder.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/migration/command_recorder.rb 1980-01-02 01:00:00.000000000 +0100 @@ -213,7 +213,9 @@ raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." end - super(args.push(options), &block) + args << options unless options.empty? + + super(args, &block) end def invert_rename_table(args) @@ -380,7 +382,8 @@ raise ActiveRecord::IrreversibleMigration, "rename_enum_value is only reversible if given a :from and :to option." end - [:rename_enum_value, [type_name, from: options[:to], to: options[:from]]] + options[:to], options[:from] = options[:from], options[:to] + [:rename_enum_value, [type_name, options]] end def invert_drop_virtual_table(args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/railties/databases.rake new/lib/active_record/railties/databases.rake --- old/lib/active_record/railties/databases.rake 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/railties/databases.rake 1980-01-02 01:00:00.000000000 +0100 @@ -160,7 +160,7 @@ end end - # desc 'Resets your database using your migrations for the current environment' + desc "Resets your database using your migrations for the current environment" task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"] desc 'Run the "up" for a given migration VERSION.' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/relation/calculations.rb new/lib/active_record/relation/calculations.rb --- old/lib/active_record/relation/calculations.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/relation/calculations.rb 1980-01-02 01:00:00.000000000 +0100 @@ -410,6 +410,18 @@ async.ids end + protected + def aggregate_column(column_name) + case column_name + when Arel::Expressions + column_name + when :all + Arel.star + else + arel_column(column_name) + end + end + private def all_attributes?(column_names) (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty? @@ -450,17 +462,6 @@ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name) end - def aggregate_column(column_name) - case column_name - when Arel::Expressions - column_name - when :all - Arel.star - else - arel_column(column_name) - end - end - def operation_over_aggregate_column(column, operation, distinct) operation == "count" ? column.count(distinct) : column.public_send(operation) end @@ -476,7 +477,7 @@ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY relation = unscope(:order).distinct!(false) - column = aggregate_column(column_name) + column = relation.aggregate_column(column_name) select_value = operation_over_aggregate_column(column, operation, distinct) select_value.distinct = true if operation == "sum" && distinct @@ -486,7 +487,11 @@ end query_result = if relation.where_clause.contradiction? - ActiveRecord::Result.empty + if @async + FutureResult.wrap(ActiveRecord::Result.empty) + else + ActiveRecord::Result.empty + end else skip_query_cache_if_necessary do model.with_connection do |c| @@ -515,7 +520,9 @@ associated = association && association.belongs_to? # only count belongs_to associations group_fields = Array(association.foreign_key) if associated end - group_fields = arel_columns(group_fields) + + relation = except(:group).distinct!(false) + group_fields = relation.arel_columns(group_fields) model.with_connection do |connection| column_alias_tracker = ColumnAliasTracker.new(connection) @@ -526,7 +533,7 @@ } group_columns = group_aliases.zip(group_fields) - column = aggregate_column(column_name) + column = relation.aggregate_column(column_name) column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}") select_value = operation_over_aggregate_column(column, operation, distinct) select_value.as(model.adapter_class.quote_column_name(column_alias)) @@ -543,7 +550,6 @@ end } - relation = except(:group).distinct!(false) relation.group_values = group_fields relation.select_values = select_values @@ -659,7 +665,7 @@ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct else column_alias = Arel.sql("count_column") - relation.select_values = [ aggregate_column(column_name).as(column_alias) ] + relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ] end subquery_alias = Arel.sql("subquery_for_count", retryable: true) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/relation/finder_methods.rb new/lib/active_record/relation/finder_methods.rb --- old/lib/active_record/relation/finder_methods.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/relation/finder_methods.rb 1980-01-02 01:00:00.000000000 +0100 @@ -424,12 +424,13 @@ error << " with#{conditions}" if conditions raise RecordNotFound.new(error, name, key) elsif Array.wrap(ids).size == 1 - error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" + id = Array.wrap(ids)[0] + error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}" raise RecordNotFound.new(error, name, key, ids) else error = +"Couldn't find all #{name.pluralize} with '#{key}': " - error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." - error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids + error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." + error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids raise RecordNotFound.new(error, name, key, ids) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/relation.rb new/lib/active_record/relation.rb --- old/lib/active_record/relation.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/relation.rb 1980-01-02 01:00:00.000000000 +0100 @@ -820,7 +820,7 @@ # # [:returning] # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully - # inserted records, which by default is the primary key. + # upserted records, which by default is the primary key. # Pass <tt>returning: %w[ id name ]</tt> for both id and name # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL # clause entirely. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/signed_id.rb new/lib/active_record/signed_id.rb --- old/lib/active_record/signed_id.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/signed_id.rb 1980-01-02 01:00:00.000000000 +0100 @@ -76,8 +76,9 @@ end # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized - # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the - # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization. + # with the class-level +signed_id_verifier_secret+, which within Rails comes from + # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator]. + # By default, it's SHA256 for the digest and JSON for the serialization. def signed_id_verifier @signed_id_verifier ||= begin secret = signed_id_verifier_secret @@ -93,7 +94,7 @@ # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare - # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details. + # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details. def signed_id_verifier=(verifier) @signed_id_verifier = verifier end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/statement_cache.rb new/lib/active_record/statement_cache.rb --- old/lib/active_record/statement_cache.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/statement_cache.rb 1980-01-02 01:00:00.000000000 +0100 @@ -74,13 +74,13 @@ self end - def add_bind(obj) + def add_bind(obj, &) @binds << obj @parts << Substitute.new self end - def add_binds(binds, proc_for_binds = nil) + def add_binds(binds, proc_for_binds = nil, &) @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds binds.size.times do |i| @parts << ", " unless i == 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/active_record/transactions.rb new/lib/active_record/transactions.rb --- old/lib/active_record/transactions.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/active_record/transactions.rb 1980-01-02 01:00:00.000000000 +0100 @@ -219,12 +219,11 @@ # database error will occur because the savepoint has already been # automatically released. The following example demonstrates the problem: # - # Model.lease_connection.transaction do # BEGIN - # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 - # Model.lease_connection.create_table(...) # active_record_1 now automatically released - # end # RELEASE SAVEPOINT active_record_1 - # # ^^^^ BOOM! database error! - # end + # Model.transaction do # BEGIN + # Model.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.lease_connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 + # end # ^^^^ BOOM! database error! # # Note that "TRUNCATE" is also a MySQL DDL statement! module ClassMethods diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/collectors/bind.rb new/lib/arel/collectors/bind.rb --- old/lib/arel/collectors/bind.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/collectors/bind.rb 1980-01-02 01:00:00.000000000 +0100 @@ -18,7 +18,7 @@ self end - def add_binds(binds, proc_for_binds = nil) + def add_binds(binds, proc_for_binds = nil, &) @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds self end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/collectors/sql_string.rb new/lib/arel/collectors/sql_string.rb --- old/lib/arel/collectors/sql_string.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/collectors/sql_string.rb 1980-01-02 01:00:00.000000000 +0100 @@ -12,7 +12,7 @@ @bind_index = 1 end - def add_bind(bind) + def add_bind(bind, &) self << yield(@bind_index) @bind_index += 1 self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/collectors/substitute_binds.rb new/lib/arel/collectors/substitute_binds.rb --- old/lib/arel/collectors/substitute_binds.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/collectors/substitute_binds.rb 1980-01-02 01:00:00.000000000 +0100 @@ -15,12 +15,12 @@ self end - def add_bind(bind) + def add_bind(bind, &) bind = bind.value_for_database if bind.respond_to?(:value_for_database) self << quoter.quote(bind) end - def add_binds(binds, proc_for_binds = nil) + def add_binds(binds, proc_for_binds = nil, &) self << binds.map { |bind| quoter.quote(bind) }.join(", ") end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/nodes/binary.rb new/lib/arel/nodes/binary.rb --- old/lib/arel/nodes/binary.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/nodes/binary.rb 1980-01-02 01:00:00.000000000 +0100 @@ -30,7 +30,7 @@ end module FetchAttribute - def fetch_attribute + def fetch_attribute(&) if left.is_a?(Arel::Attributes::Attribute) yield left elsif right.is_a?(Arel::Attributes::Attribute) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/nodes/node.rb new/lib/arel/nodes/node.rb --- old/lib/arel/nodes/node.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/nodes/node.rb 1980-01-02 01:00:00.000000000 +0100 @@ -152,7 +152,7 @@ end end - def fetch_attribute + def fetch_attribute(&) end def equality?; false; end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/nodes/sql_literal.rb new/lib/arel/nodes/sql_literal.rb --- old/lib/arel/nodes/sql_literal.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/nodes/sql_literal.rb 1980-01-02 01:00:00.000000000 +0100 @@ -19,7 +19,7 @@ coder.scalar = self.to_s end - def fetch_attribute + def fetch_attribute(&) end def +(other) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/arel/visitors/to_sql.rb new/lib/arel/visitors/to_sql.rb --- old/lib/arel/visitors/to_sql.rb 2024-12-13 21:02:34.000000000 +0100 +++ new/lib/arel/visitors/to_sql.rb 1980-01-02 01:00:00.000000000 +0100 @@ -763,7 +763,7 @@ def visit_Arel_Nodes_SqlLiteral(o, collector) collector.preparable = false - collector.retryable = o.retryable + collector.retryable &&= o.retryable collector << o.to_s end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2024-12-13 21:02:34.000000000 +0100 +++ new/metadata 1980-01-02 01:00:00.000000000 +0100 @@ -1,14 +1,13 @@ --- !ruby/object:Gem::Specification name: activerecord version: !ruby/object:Gem::Version - version: 8.0.1 + version: 8.0.2.1 platform: ruby authors: - David Heinemeier Hansson -autorequire: bindir: bin cert_chain: [] -date: 2024-12-13 00:00:00.000000000 Z +date: 1980-01-02 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport @@ -16,28 +15,28 @@ requirements: - - '=' - !ruby/object:Gem::Version - version: 8.0.1 + version: 8.0.2.1 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '=' - !ruby/object:Gem::Version - version: 8.0.1 + version: 8.0.2.1 - !ruby/object:Gem::Dependency name: activemodel requirement: !ruby/object:Gem::Requirement requirements: - - '=' - !ruby/object:Gem::Version - version: 8.0.1 + version: 8.0.2.1 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '=' - !ruby/object:Gem::Version - version: 8.0.1 + version: 8.0.2.1 - !ruby/object:Gem::Dependency name: timeout requirement: !ruby/object:Gem::Requirement @@ -475,12 +474,11 @@ - MIT metadata: bug_tracker_uri: https://github.com/rails/rails/issues - changelog_uri: https://github.com/rails/rails/blob/v8.0.1/activerecord/CHANGELOG.md - documentation_uri: https://api.rubyonrails.org/v8.0.1/ + changelog_uri: https://github.com/rails/rails/blob/v8.0.2.1/activerecord/CHANGELOG.md + documentation_uri: https://api.rubyonrails.org/v8.0.2.1/ mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk - source_code_uri: https://github.com/rails/rails/tree/v8.0.1/activerecord + source_code_uri: https://github.com/rails/rails/tree/v8.0.2.1/activerecord rubygems_mfa_required: 'true' -post_install_message: rdoc_options: - "--main" - README.rdoc @@ -497,8 +495,7 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.5.22 -signing_key: +rubygems_version: 3.6.9 specification_version: 4 summary: Object-relational mapper framework (part of Rails). test_files: []