activerecord
Advanced tools
+70
-0
@@ -0,1 +1,71 @@ | ||
| ## Rails 8.1.2 (January 08, 2026) ## | ||
| * Fix counting cached queries in `ActiveRecord::RuntimeRegistry`. | ||
| *fatkodima* | ||
| * 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* | ||
| * Restore the ability of enum to be foats. | ||
| ```ruby | ||
| enum :rating, { low: 0.0, medium: 0.5, high: 1.0 }, | ||
| ``` | ||
| In Rails 8.1.0, enum values are eagerly validated, and floats weren't expected. | ||
| *Said Kaldybaev* | ||
| * 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 structured events for Active Record was not being emitted. | ||
| *Yuji Yaginuma* | ||
| * 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.1.1 (October 28, 2025) ## | ||
@@ -2,0 +72,0 @@ |
@@ -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 |
@@ -9,2 +9,3 @@ # frozen_string_literal: true | ||
| require "active_record/log_subscriber" | ||
| require "active_record/structured_event_subscriber" | ||
| require "active_record/relation/delegation" | ||
@@ -11,0 +12,0 @@ require "active_record/attributes" |
@@ -716,33 +716,32 @@ # frozen_string_literal: true | ||
| @lock.synchronize do | ||
| @allow_preconnect = false | ||
| attempt_configure_connection do | ||
| @allow_preconnect = false | ||
| reconnect | ||
| reconnect | ||
| enable_lazy_transactions! | ||
| @raw_connection_dirty = false | ||
| @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
| @verified = true | ||
| @allow_preconnect = true | ||
| enable_lazy_transactions! | ||
| @raw_connection_dirty = false | ||
| @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
| @verified = true | ||
| @allow_preconnect = 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 | ||
@@ -759,2 +758,4 @@ end | ||
| @connected_since = nil | ||
| @last_activity = nil | ||
| @verified = false | ||
| end | ||
@@ -782,5 +783,7 @@ end | ||
| 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 | ||
@@ -819,8 +822,10 @@ | ||
| if @unconfigured_connection | ||
| @raw_connection = @unconfigured_connection | ||
| @unconfigured_connection = nil | ||
| attempt_configure_connection | ||
| @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
| @verified = true | ||
| @allow_preconnect = true | ||
| attempt_configure_connection do | ||
| @raw_connection = @unconfigured_connection | ||
| @unconfigured_connection = nil | ||
| configure_connection | ||
| @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
| @verified = true | ||
| @allow_preconnect = true | ||
| end | ||
| return | ||
@@ -1289,3 +1294,3 @@ end | ||
| def attempt_configure_connection | ||
| configure_connection | ||
| yield | ||
| rescue Exception # Need to handle things such as Timeout::ExitException | ||
@@ -1292,0 +1297,0 @@ disconnect! |
@@ -398,2 +398,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 | ||
@@ -1013,2 +1018,4 @@ # method does nothing. | ||
| schema_search_path # populate cache | ||
| reload_type_map | ||
@@ -1015,0 +1022,0 @@ end |
@@ -22,9 +22,21 @@ # frozen_string_literal: true | ||
| 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) | ||
@@ -31,0 +43,0 @@ spec = super |
@@ -352,6 +352,6 @@ # frozen_string_literal: true | ||
| case value | ||
| when String, Integer, true, false, nil | ||
| when String, Integer, Float, true, false, nil | ||
| # noop | ||
| else | ||
| raise ArgumentError, "Enum values #{values} must be only booleans, integers, symbols or strings, got: #{value.class}" | ||
| raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}" | ||
| end | ||
@@ -358,0 +358,0 @@ end |
@@ -12,3 +12,3 @@ # frozen_string_literal: true | ||
| MINOR = 1 | ||
| TINY = 1 | ||
| TINY = 2 | ||
| PRE = nil | ||
@@ -15,0 +15,0 @@ |
@@ -1025,3 +1025,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 | ||
@@ -1028,0 +1028,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 |
@@ -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| |
@@ -36,2 +36,3 @@ # frozen_string_literal: true | ||
| (finish - start) * 1_000.0, | ||
| cached: payload[:cached], | ||
| async: payload[:async], | ||
@@ -38,0 +39,0 @@ lock_wait: payload[:lock_wait], |
@@ -235,4 +235,4 @@ # frozen_string_literal: true | ||
| add_index_statements = indexes.map do |index| | ||
| table_name = remove_prefix_and_suffix(index.table).inspect | ||
| " add_index #{([relation_name(table_name)] + index_parts(index)).join(', ')}" | ||
| table_name = remove_prefix_and_suffix(index.table) | ||
| " add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}" | ||
| end | ||
@@ -239,0 +239,0 @@ |
Sorry, the diff of this file is too big to display