🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

activerecord

Package Overview
Dependencies
Maintainers
1
Versions
519
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

activerecord - rubygems Package Compare versions

Comparing version
8.0.4.1
to
8.0.5
+91
-0
CHANGELOG.md

@@ -0,1 +1,92 @@

## Rails 8.0.5 (March 24, 2026) ##
* Fix `insert_all` and `upsert_all` log message when called on anonymous classes.
*Gabriel Sobrinho*
* Respect `ActiveRecord::SchemaDumper.ignore_tables` when dumping SQLite virtual tables.
*Hans Schnedlitz*
* Restore previous instrumenter after `execute_or_skip`
`FutureResult#execute_or_skip` replaces the thread's instrumenter with an
`EventBuffer` to collect events published during async query execution.
If the global async executor is saturated and the `caller_runs` fallback
executes the task on the calling thread, we need to make sure the previous
instrumenter is restored or the stale `EventBuffer` would stay in place and
permanently swallow all subsequent `sql.active_record` notifications on
that thread.
*Rosa Gutierrez*
* Fix Ruby 4.0 delegator warning when calling inspect on ActiveRecord::Type::Serialized.
*Hammad Khan*
* Fix support for table names containing hyphens.
*Evgeniy Demin*
* Fix column deduplication for SQLite3 and PostgreSQL virtual (generated) columns.
`Column#==` and `Column#hash` now account for `virtual?` so that the
`Deduplicable` registry does not treat a generated column and a regular
column with the same name and type as identical. Previously, if a
generated column was registered first, a regular column on a different
table could be deduplicated to the generated instance, silently
excluding it from INSERT/UPDATE statements.
*Jay Huber*
* Fix merging relations with arel equality predicates with null relations.
*fatkodima*
* Fix SQLite3 schema dump for non-autoincrement integer primary keys.
Previously, `schema.rb` should incorrectly restore that table with an auto incrementing
primary key.
*Chris Hasiński*
* Fix PostgreSQL `schema_search_path` not being reapplied after `reset!` or `reconnect!`.
The `schema_search_path` configured in `database.yml` is now correctly
reapplied instead of falling back to PostgreSQL defaults.
*Tobias Egli*
* Ensure batched preloaded associations accounts for klass when grouping to avoid issues with STI.
*zzak*, *Stjepan Hadjic*
* Fix `ActiveRecord::SoleRecordExceeded#record` to return the relation.
This was the case until Rails 7.2, but starting from 8.0 it
started mistakenly returning the model class.
*Jean Boussier*
* Improve PostgreSQLAdapter resilience to Timeout.timeout.
Better handle asynchronous exceptions being thrown inside
the `reconnect!` method.
This may fixes some deep errors such as:
```
undefined method `key?' for nil:NilClass (NoMethodError)
if !type_map.key?(oid)
```
*Jean Boussier*
* Fix `eager_load` when loading `has_many` assocations with composite primary keys.
This would result in some records being loaded multiple times.
*Martin-Alexander*
## Rails 8.0.4.1 (March 23, 2026) ##

@@ -2,0 +93,0 @@

+2
-2

@@ -106,3 +106,3 @@ # frozen_string_literal: true

def instantiate(result_set, strict_loading_value, &block)
primary_key = aliases.column_alias(join_root, join_root.primary_key)
primary_key = Array(join_root.primary_key).map { |column| aliases.column_alias(join_root, column) }

@@ -145,3 +145,3 @@ seen = Hash.new { |i, parent|

result_set.each { |row_hash|
parent_key = primary_key ? row_hash[primary_key] : row_hash
parent_key = primary_key.empty? ? row_hash : row_hash.values_at(*primary_key)
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)

@@ -148,0 +148,0 @@ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)

@@ -41,3 +41,9 @@ # frozen_string_literal: true

def group_and_load_similar(loaders)
loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders|
non_through = loaders.grep_v(ThroughAssociation)
grouped = non_through.group_by do |loader|
[loader.loader_query, loader.klass]
end
grouped.each do |(query, _klass), similar_loaders|
query.load_records_in_batch(similar_loaders)

@@ -44,0 +50,0 @@ end

@@ -669,30 +669,29 @@ # frozen_string_literal: true

@lock.synchronize do
reconnect
attempt_configure_connection do
reconnect
enable_lazy_transactions!
@raw_connection_dirty = false
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@verified = true
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)
attempt_configure_connection
end
rescue => original_exception
translated_exception = translate_exception_class(original_exception, nil, nil)
retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
reset_transaction(restore: restore_transactions) do
clear_cache!(new_connection: true)
configure_connection
end
rescue => original_exception
translated_exception = translate_exception_class(original_exception, nil, nil)
retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
if !retry_deadline_exceeded && retries_available > 0
retries_available -= 1
if !retry_deadline_exceeded && retries_available > 0
retries_available -= 1
if retryable_connection_error?(translated_exception)
backoff(connection_retries - retries_available)
retry
if retryable_connection_error?(translated_exception)
backoff(connection_retries - retries_available)
retry
end
end
raise translated_exception
end
@last_activity = nil
@verified = false
raise translated_exception
end

@@ -705,2 +704,4 @@ end

@lock.synchronize do
@last_activity = nil
@verified = false
clear_cache!(new_connection: true)

@@ -731,5 +732,7 @@ reset_transaction

def reset!
clear_cache!(new_connection: true)
reset_transaction
attempt_configure_connection
attempt_configure_connection do
clear_cache!(new_connection: true)
reset_transaction
configure_connection
end
end

@@ -768,7 +771,9 @@

if @unconfigured_connection
@raw_connection = @unconfigured_connection
@unconfigured_connection = nil
attempt_configure_connection
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@verified = true
attempt_configure_connection do
@raw_connection = @unconfigured_connection
@unconfigured_connection = nil
configure_connection
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@verified = true
end
return

@@ -1223,3 +1228,3 @@ end

def attempt_configure_connection
configure_connection
yield
rescue Exception # Need to handle things such as Timeout::ExitException

@@ -1226,0 +1231,0 @@ disconnect!

@@ -123,3 +123,3 @@ # frozen_string_literal: true

# https://bugs.ruby-lang.org/issues/20688
if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= "3.3.5"
WeakThreadKeyMap = ObjectSpace::WeakKeyMap

@@ -467,3 +467,3 @@ else

end
@connections = []
@connections = @pinned_connection ? [@pinned_connection] : []
@leases.clear

@@ -470,0 +470,0 @@ @available.clear

@@ -741,3 +741,3 @@ # frozen_string_literal: true

def extract_table_ref_from_insert_sql(sql)
if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
if sql =~ /into\s("[-A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
$1.delete('"').strip

@@ -744,0 +744,0 @@ end

@@ -384,2 +384,7 @@ # frozen_string_literal: true

def clear_cache!(new_connection: false)
super
@schema_search_path = nil if new_connection
end
# Disconnects from the database if already connected. Otherwise, this

@@ -997,2 +1002,4 @@ # method does nothing.

schema_search_path # populate cache
reload_type_map

@@ -999,0 +1006,0 @@ end

@@ -68,3 +68,4 @@ # frozen_string_literal: true

identity? == other.identity? &&
serial? == other.serial?
serial? == other.serial? &&
virtual? == other.virtual?
end

@@ -77,3 +78,4 @@ alias :eql? :==

identity?.hash ^
serial?.hash
serial?.hash ^
virtual?.hash
end

@@ -80,0 +82,0 @@ end

@@ -294,3 +294,3 @@ # frozen_string_literal: true

VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.*)\)/i

@@ -297,0 +297,0 @@ # Returns a list of defined virtual tables

@@ -49,3 +49,5 @@ # frozen_string_literal: true

super &&
auto_increment? == other.auto_increment?
auto_increment? == other.auto_increment? &&
rowid == other.rowid &&
generated_type == other.generated_type
end

@@ -58,4 +60,8 @@ alias :eql? :==

auto_increment?.hash ^
rowid.hash
rowid.hash ^
virtual?.hash
end
protected
attr_reader :generated_type
end

@@ -62,0 +68,0 @@ end

@@ -9,3 +9,3 @@ # frozen_string_literal: true

def virtual_tables(stream)
virtual_tables = @connection.virtual_tables
virtual_tables = @connection.virtual_tables.reject { |name, _| ignored?(name) }
if virtual_tables.any?

@@ -23,9 +23,21 @@ stream.puts

def default_primary_key?(column)
schema_type(column) == :integer
schema_type(column) == :integer && primary_key_has_autoincrement?
end
def explicit_primary_key_default?(column)
column.bigint?
column.bigint? || (column.type == :integer && !primary_key_has_autoincrement?)
end
def primary_key_has_autoincrement?
return false unless table_name
table_sql = @connection.query_value(<<~SQL, "SCHEMA")
SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
UNION ALL
SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
SQL
table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
end
def prepare_column_options(column)

@@ -32,0 +44,0 @@ spec = super

@@ -108,2 +108,3 @@ # frozen_string_literal: true

return unless @mutex.try_lock
previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
begin

@@ -117,2 +118,3 @@ if pending?

ensure
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
@mutex.unlock

@@ -119,0 +121,0 @@ end

@@ -12,4 +12,4 @@ # frozen_string_literal: true

MINOR = 0
TINY = 4
PRE = "1"
TINY = 5
PRE = nil

@@ -16,0 +16,0 @@ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")

@@ -42,3 +42,3 @@ # frozen_string_literal: true

@unique_by = find_unique_index_for(@unique_by)
@unique_by = find_unique_index_for(@unique_by) if @on_duplicate != :raise

@@ -52,3 +52,3 @@ configure_on_duplicate_update_logic

message = +"#{model} "
message = +"#{model.name} "
message << "Bulk " if inserts.many?

@@ -55,0 +55,0 @@ message << (on_duplicate == :update ? "Upsert" : "Insert")

@@ -142,3 +142,3 @@ # frozen_string_literal: true

commands = recorder.commands
@commands << [:change_table, [table_name], -> t { bulk_change_table(table_name, commands) }]
@commands << [:change_table, [table_name], -> t { bulk_change_table(t.name, commands.reverse) }]
else

@@ -145,0 +145,0 @@ yield delegate.update_table_definition(table_name, self)

@@ -1013,3 +1013,3 @@ # frozen_string_literal: true

#
# Both calls delete the affected posts all at once with a single DELETE statement.
# This call deletes the affected posts all at once with a single DELETE statement.
# If you need to destroy dependent associations or call your <tt>before_*</tt> or

@@ -1016,0 +1016,0 @@ # +after_destroy+ callbacks, use the #destroy_all method instead.

@@ -151,3 +151,3 @@ # frozen_string_literal: true

else
raise ActiveRecord::SoleRecordExceeded.new(model)
raise ActiveRecord::SoleRecordExceeded.new(self)
end

@@ -154,0 +154,0 @@ end

@@ -34,3 +34,5 @@ # frozen_string_literal: true

array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
array_predicates.inject(values_predicate, &:or)
values_predicate.or(
Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
)
end

@@ -37,0 +39,0 @@ end

@@ -185,3 +185,3 @@ # frozen_string_literal: true

predicates.reject do |node|
if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
if !non_attrs.empty? && equality_node?(node) && node.left.is_a?(Arel::Predications)
non_attrs.include?(node.left)

@@ -188,0 +188,0 @@ end || Arel.fetch_attribute(node) do |attr|

@@ -61,2 +61,7 @@ # frozen_string_literal: true

private
# Prevent Ruby 4.0 "delegator does not forward private method" warning.
# Kernel#inspect calls instance_variables_to_inspect which, without this,
# triggers Delegator#respond_to_missing? for a private method.
define_method(:instance_variables_to_inspect, Kernel.instance_method(:instance_variables))
def default_value?(value)

@@ -63,0 +68,0 @@ value == coder.load(nil)

@@ -234,5 +234,3 @@ # frozen_string_literal: true

nodes = others.map { |expr| send(method_id, expr, *extras) }
Nodes::Grouping.new nodes.inject { |memo, node|
Nodes::Or.new([memo, node])
}
Nodes::Grouping.new Nodes::Or.new(nodes)
end

@@ -239,0 +237,0 @@

Sorry, the diff of this file is too big to display