activerecord 7.2.2.2 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +316 -7
- data/README.rdoc +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/collection_association.rb +9 -7
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_handling.rb +12 -8
- data/lib/active_record/core.rb +27 -7
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +21 -20
- data/lib/active_record/enum.rb +13 -12
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration.rb +2 -1
- data/lib/active_record/query_logs.rb +4 -0
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +2 -2
- data/lib/active_record/railties/databases.rake +2 -1
- data/lib/active_record/relation/calculations.rb +35 -30
- data/lib/active_record/relation/finder_methods.rb +10 -10
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +16 -9
- data/lib/active_record/relation/where_clause.rb +8 -2
- data/lib/active_record/relation.rb +15 -5
- data/lib/active_record/schema_dumper.rb +29 -11
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
- data/lib/active_record/transactions.rb +3 -1
- data/lib/active_record.rb +1 -1
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +8 -8
| @@ -46,8 +46,8 @@ module ActiveRecord | |
| 46 46 | 
             
                #   Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
         | 
| 47 47 | 
             
                #   Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
         | 
| 48 48 | 
             
                #
         | 
| 49 | 
            -
                # Note that building your own SQL query string from user input may expose your application to
         | 
| 50 | 
            -
                # injection attacks | 
| 49 | 
            +
                # Note that building your own SQL query string from user input {may expose your application to
         | 
| 50 | 
            +
                # injection attacks}[https://guides.rubyonrails.org/security.html#sql-injection].
         | 
| 51 51 | 
             
                def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
         | 
| 52 52 | 
             
                  result = with_connection do |c|
         | 
| 53 53 | 
             
                    _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
         | 
| @@ -55,7 +55,7 @@ module ActiveRecord | |
| 55 55 | 
             
                  _load_from_sql(result, &block)
         | 
| 56 56 | 
             
                end
         | 
| 57 57 |  | 
| 58 | 
            -
                # Same as  | 
| 58 | 
            +
                # Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
         | 
| 59 59 | 
             
                def async_find_by_sql(sql, binds = [], preparable: nil, &block)
         | 
| 60 60 | 
             
                  result = with_connection do |c|
         | 
| 61 61 | 
             
                    _query_by_sql(c, sql, binds, preparable: preparable, async: true)
         | 
| @@ -112,7 +112,7 @@ module ActiveRecord | |
| 112 112 | 
             
                  end
         | 
| 113 113 | 
             
                end
         | 
| 114 114 |  | 
| 115 | 
            -
                # Same as  | 
| 115 | 
            +
                # Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
         | 
| 116 116 | 
             
                def async_count_by_sql(sql)
         | 
| 117 117 | 
             
                  with_connection do |c|
         | 
| 118 118 | 
             
                    c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
         | 
| @@ -23,8 +23,8 @@ module ActiveRecord | |
| 23 23 | 
             
                config.action_dispatch.rescue_responses.merge!(
         | 
| 24 24 | 
             
                  "ActiveRecord::RecordNotFound"   => :not_found,
         | 
| 25 25 | 
             
                  "ActiveRecord::StaleObjectError" => :conflict,
         | 
| 26 | 
            -
                  "ActiveRecord::RecordInvalid"    =>  | 
| 27 | 
            -
                  "ActiveRecord::RecordNotSaved"   =>  | 
| 26 | 
            +
                  "ActiveRecord::RecordInvalid"    => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
         | 
| 27 | 
            +
                  "ActiveRecord::RecordNotSaved"   => ActionDispatch::Constants::UNPROCESSABLE_CONTENT
         | 
| 28 28 | 
             
                )
         | 
| 29 29 |  | 
| 30 30 | 
             
                config.active_record.use_schema_cache_dump = true
         | 
| @@ -473,7 +473,8 @@ db_namespace = namespace :db do | |
| 473 473 |  | 
| 474 474 | 
             
                desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
         | 
| 475 475 | 
             
                task load: [:load_config, :check_protected_environments] do
         | 
| 476 | 
            -
                   | 
| 476 | 
            +
                  schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
         | 
| 477 | 
            +
                  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(schema_format, ENV["SCHEMA"])
         | 
| 477 478 | 
             
                end
         | 
| 478 479 |  | 
| 479 480 | 
             
                namespace :dump do
         | 
| @@ -60,37 +60,37 @@ module ActiveRecord | |
| 60 60 | 
             
                #   Person.distinct.count(:age)
         | 
| 61 61 | 
             
                #   # => counts the number of different age values
         | 
| 62 62 | 
             
                #
         | 
| 63 | 
            -
                # If  | 
| 63 | 
            +
                # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
         | 
| 64 64 | 
             
                # it returns a Hash whose keys represent the aggregated column,
         | 
| 65 65 | 
             
                # and the values are the respective amounts:
         | 
| 66 66 | 
             
                #
         | 
| 67 67 | 
             
                #   Person.group(:city).count
         | 
| 68 68 | 
             
                #   # => { 'Rome' => 5, 'Paris' => 3 }
         | 
| 69 69 | 
             
                #
         | 
| 70 | 
            -
                # If  | 
| 70 | 
            +
                # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
         | 
| 71 71 | 
             
                # keys are an array containing the individual values of each column and the value
         | 
| 72 | 
            -
                # of each key would be the  | 
| 72 | 
            +
                # of each key would be the count.
         | 
| 73 73 | 
             
                #
         | 
| 74 74 | 
             
                #   Article.group(:status, :category).count
         | 
| 75 75 | 
             
                #   # =>  {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
         | 
| 76 76 | 
             
                #
         | 
| 77 | 
            -
                # If  | 
| 77 | 
            +
                # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
         | 
| 78 78 | 
             
                #
         | 
| 79 79 | 
             
                #   Person.select(:age).count
         | 
| 80 80 | 
             
                #   # => counts the number of different age values
         | 
| 81 81 | 
             
                #
         | 
| 82 | 
            -
                # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid  | 
| 82 | 
            +
                # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
         | 
| 83 83 | 
             
                # between databases. In invalid cases, an error from the database is thrown.
         | 
| 84 84 | 
             
                #
         | 
| 85 | 
            -
                # When given a block,  | 
| 86 | 
            -
                #  | 
| 87 | 
            -
                # Returns the number of records for which the block returns a truthy value.
         | 
| 85 | 
            +
                # When given a block, calls the block with each record in the relation and
         | 
| 86 | 
            +
                # returns the number of records for which the block returns a truthy value.
         | 
| 88 87 | 
             
                #
         | 
| 89 88 | 
             
                #   Person.count { |person| person.age > 21 }
         | 
| 90 89 | 
             
                #   # => counts the number of people older that 21
         | 
| 91 90 | 
             
                #
         | 
| 92 | 
            -
                #  | 
| 93 | 
            -
                #  | 
| 91 | 
            +
                # If the relation hasn't been loaded yet, calling +count+ with a block will
         | 
| 92 | 
            +
                # load all records in the relation. If there are a lot of records in the
         | 
| 93 | 
            +
                # relation, loading all records could result in performance issues.
         | 
| 94 94 | 
             
                def count(column_name = nil)
         | 
| 95 95 | 
             
                  if block_given?
         | 
| 96 96 | 
             
                    unless column_name.nil?
         | 
| @@ -159,16 +159,15 @@ module ActiveRecord | |
| 159 159 | 
             
                #
         | 
| 160 160 | 
             
                #   Person.sum(:age) # => 4562
         | 
| 161 161 | 
             
                #
         | 
| 162 | 
            -
                # When given a block,  | 
| 163 | 
            -
                #  | 
| 164 | 
            -
                # Returns the sum of +initial_value_or_column+ and the block return
         | 
| 165 | 
            -
                # values:
         | 
| 162 | 
            +
                # When given a block, calls the block with each record in the relation and
         | 
| 163 | 
            +
                # returns the sum of +initial_value_or_column+ plus the block return values:
         | 
| 166 164 | 
             
                #
         | 
| 167 165 | 
             
                #   Person.sum { |person| person.age } # => 4562
         | 
| 168 166 | 
             
                #   Person.sum(1000) { |person| person.age } # => 5562
         | 
| 169 167 | 
             
                #
         | 
| 170 | 
            -
                #  | 
| 171 | 
            -
                #  | 
| 168 | 
            +
                # If the relation hasn't been loaded yet, calling +sum+ with a block will
         | 
| 169 | 
            +
                # load all records in the relation. If there are a lot of records in the
         | 
| 170 | 
            +
                # relation, loading all records could result in performance issues.
         | 
| 172 171 | 
             
                def sum(initial_value_or_column = 0, &block)
         | 
| 173 172 | 
             
                  if block_given?
         | 
| 174 173 | 
             
                    map(&block).sum(initial_value_or_column)
         | 
| @@ -406,6 +405,15 @@ module ActiveRecord | |
| 406 405 | 
             
                  async.ids
         | 
| 407 406 | 
             
                end
         | 
| 408 407 |  | 
| 408 | 
            +
                protected
         | 
| 409 | 
            +
                  def aggregate_column(column_name)
         | 
| 410 | 
            +
                    return column_name if Arel::Expressions === column_name
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                    arel_column(column_name.to_s) do |name|
         | 
| 413 | 
            +
                      column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
         | 
| 414 | 
            +
                    end
         | 
| 415 | 
            +
                  end
         | 
| 416 | 
            +
             | 
| 409 417 | 
             
                private
         | 
| 410 418 | 
             
                  def all_attributes?(column_names)
         | 
| 411 419 | 
             
                    (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
         | 
| @@ -446,14 +454,6 @@ module ActiveRecord | |
| 446 454 | 
             
                    column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
         | 
| 447 455 | 
             
                  end
         | 
| 448 456 |  | 
| 449 | 
            -
                  def aggregate_column(column_name)
         | 
| 450 | 
            -
                    return column_name if Arel::Expressions === column_name
         | 
| 451 | 
            -
             | 
| 452 | 
            -
                    arel_column(column_name.to_s) do |name|
         | 
| 453 | 
            -
                      column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
         | 
| 454 | 
            -
                    end
         | 
| 455 | 
            -
                  end
         | 
| 456 | 
            -
             | 
| 457 457 | 
             
                  def operation_over_aggregate_column(column, operation, distinct)
         | 
| 458 458 | 
             
                    operation == "count" ? column.count(distinct) : column.public_send(operation)
         | 
| 459 459 | 
             
                  end
         | 
| @@ -469,7 +469,7 @@ module ActiveRecord | |
| 469 469 | 
             
                      # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
         | 
| 470 470 | 
             
                      relation = unscope(:order).distinct!(false)
         | 
| 471 471 |  | 
| 472 | 
            -
                      column = aggregate_column(column_name)
         | 
| 472 | 
            +
                      column = relation.aggregate_column(column_name)
         | 
| 473 473 | 
             
                      select_value = operation_over_aggregate_column(column, operation, distinct)
         | 
| 474 474 | 
             
                      select_value.distinct = true if operation == "sum" && distinct
         | 
| 475 475 |  | 
| @@ -479,7 +479,11 @@ module ActiveRecord | |
| 479 479 | 
             
                    end
         | 
| 480 480 |  | 
| 481 481 | 
             
                    query_result = if relation.where_clause.contradiction?
         | 
| 482 | 
            -
                       | 
| 482 | 
            +
                      if @async
         | 
| 483 | 
            +
                        FutureResult.wrap(ActiveRecord::Result.empty)
         | 
| 484 | 
            +
                      else
         | 
| 485 | 
            +
                        ActiveRecord::Result.empty
         | 
| 486 | 
            +
                      end
         | 
| 483 487 | 
             
                    else
         | 
| 484 488 | 
             
                      skip_query_cache_if_necessary do
         | 
| 485 489 | 
             
                        @klass.with_connection do |c|
         | 
| @@ -508,7 +512,9 @@ module ActiveRecord | |
| 508 512 | 
             
                      associated   = association && association.belongs_to? # only count belongs_to associations
         | 
| 509 513 | 
             
                      group_fields = Array(association.foreign_key) if associated
         | 
| 510 514 | 
             
                    end
         | 
| 511 | 
            -
             | 
| 515 | 
            +
             | 
| 516 | 
            +
                    relation = except(:group).distinct!(false)
         | 
| 517 | 
            +
                    group_fields = relation.arel_columns(group_fields)
         | 
| 512 518 |  | 
| 513 519 | 
             
                    @klass.with_connection do |connection|
         | 
| 514 520 | 
             
                      column_alias_tracker = ColumnAliasTracker.new(connection)
         | 
| @@ -519,7 +525,7 @@ module ActiveRecord | |
| 519 525 | 
             
                      }
         | 
| 520 526 | 
             
                      group_columns = group_aliases.zip(group_fields)
         | 
| 521 527 |  | 
| 522 | 
            -
                      column = aggregate_column(column_name)
         | 
| 528 | 
            +
                      column = relation.aggregate_column(column_name)
         | 
| 523 529 | 
             
                      column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
         | 
| 524 530 | 
             
                      select_value = operation_over_aggregate_column(column, operation, distinct)
         | 
| 525 531 | 
             
                      select_value.as(adapter_class.quote_column_name(column_alias))
         | 
| @@ -536,7 +542,6 @@ module ActiveRecord | |
| 536 542 | 
             
                        end
         | 
| 537 543 | 
             
                      }
         | 
| 538 544 |  | 
| 539 | 
            -
                      relation = except(:group).distinct!(false)
         | 
| 540 545 | 
             
                      relation.group_values  = group_fields
         | 
| 541 546 | 
             
                      relation.select_values = select_values
         | 
| 542 547 |  | 
| @@ -662,7 +667,7 @@ module ActiveRecord | |
| 662 667 | 
             
                      relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
         | 
| 663 668 | 
             
                    else
         | 
| 664 669 | 
             
                      column_alias = Arel.sql("count_column")
         | 
| 665 | 
            -
                      relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
         | 
| 670 | 
            +
                      relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
         | 
| 666 671 | 
             
                    end
         | 
| 667 672 |  | 
| 668 673 | 
             
                    subquery_alias = Arel.sql("subquery_for_count", retryable: true)
         | 
| @@ -24,22 +24,22 @@ module ActiveRecord | |
| 24 24 | 
             
                #   TravelRoute.primary_key = [:origin, :destination]
         | 
| 25 25 | 
             
                #
         | 
| 26 26 | 
             
                #   TravelRoute.find(["Ottawa", "London"])
         | 
| 27 | 
            -
                #   => #<TravelRoute origin: "Ottawa", destination: "London">
         | 
| 27 | 
            +
                #   # => #<TravelRoute origin: "Ottawa", destination: "London">
         | 
| 28 28 | 
             
                #
         | 
| 29 29 | 
             
                #   TravelRoute.find([["Paris", "Montreal"]])
         | 
| 30 | 
            -
                #   => [#<TravelRoute origin: "Paris", destination: "Montreal">]
         | 
| 30 | 
            +
                #   # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
         | 
| 31 31 | 
             
                #
         | 
| 32 32 | 
             
                #   TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
         | 
| 33 | 
            -
                #   => [
         | 
| 34 | 
            -
                # | 
| 35 | 
            -
                # | 
| 36 | 
            -
                # | 
| 33 | 
            +
                #   # => [
         | 
| 34 | 
            +
                #   #      #<TravelRoute origin: "New York", destination: "Las Vegas">,
         | 
| 35 | 
            +
                #   #      #<TravelRoute origin: "New York", destination: "Portland">
         | 
| 36 | 
            +
                #   #    ]
         | 
| 37 37 | 
             
                #
         | 
| 38 38 | 
             
                #   TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
         | 
| 39 | 
            -
                #   => [
         | 
| 40 | 
            -
                # | 
| 41 | 
            -
                # | 
| 42 | 
            -
                # | 
| 39 | 
            +
                #   # => [
         | 
| 40 | 
            +
                #   #      #<TravelRoute origin: "Berlin", destination: "London">,
         | 
| 41 | 
            +
                #   #      #<TravelRoute origin: "Barcelona", destination: "Lisbon">
         | 
| 42 | 
            +
                #   #    ]
         | 
| 43 43 | 
             
                #
         | 
| 44 44 | 
             
                # NOTE: The returned records are in the same order as the ids you provide.
         | 
| 45 45 | 
             
                # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
         | 
| @@ -35,7 +35,7 @@ module ActiveRecord | |
| 35 35 | 
             
                  def nil?
         | 
| 36 36 | 
             
                    unless value_before_type_cast.is_a?(StatementCache::Substitute)
         | 
| 37 37 | 
             
                      value_before_type_cast.nil? ||
         | 
| 38 | 
            -
                        type.respond_to?(:subtype) && serializable? && value_for_database.nil?
         | 
| 38 | 
            +
                        (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
         | 
| 39 39 | 
             
                    end
         | 
| 40 40 | 
             
                  end
         | 
| 41 41 |  | 
| @@ -491,7 +491,8 @@ module ActiveRecord | |
| 491 491 |  | 
| 492 492 | 
             
                # Like #with, but modifies relation in place.
         | 
| 493 493 | 
             
                def with!(*args) # :nodoc:
         | 
| 494 | 
            -
                   | 
| 494 | 
            +
                  args = process_with_args(args)
         | 
| 495 | 
            +
                  self.with_values |= args
         | 
| 495 496 | 
             
                  self
         | 
| 496 497 | 
             
                end
         | 
| 497 498 |  | 
| @@ -502,7 +503,7 @@ module ActiveRecord | |
| 502 503 | 
             
                #   # WITH post_and_replies AS (
         | 
| 503 504 | 
             
                #   #   (SELECT * FROM posts WHERE id = 42)
         | 
| 504 505 | 
             
                #   #   UNION ALL
         | 
| 505 | 
            -
                #   #   (SELECT * FROM posts JOIN  | 
| 506 | 
            +
                #   #   (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
         | 
| 506 507 | 
             
                #   # )
         | 
| 507 508 | 
             
                #   # SELECT * FROM posts
         | 
| 508 509 | 
             
                #
         | 
| @@ -514,7 +515,8 @@ module ActiveRecord | |
| 514 515 |  | 
| 515 516 | 
             
                # Like #with_recursive but modifies the relation in place.
         | 
| 516 517 | 
             
                def with_recursive!(*args) # :nodoc:
         | 
| 517 | 
            -
                   | 
| 518 | 
            +
                  args = process_with_args(args)
         | 
| 519 | 
            +
                  self.with_values |= args
         | 
| 518 520 | 
             
                  @with_is_recursive = true
         | 
| 519 521 | 
             
                  self
         | 
| 520 522 | 
             
                end
         | 
| @@ -1277,13 +1279,13 @@ module ActiveRecord | |
| 1277 1279 | 
             
                #
         | 
| 1278 1280 | 
             
                #   users = User.readonly
         | 
| 1279 1281 | 
             
                #   users.first.save
         | 
| 1280 | 
            -
                #   => ActiveRecord::ReadOnlyRecord: User is marked as readonly
         | 
| 1282 | 
            +
                #   # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
         | 
| 1281 1283 | 
             
                #
         | 
| 1282 1284 | 
             
                # To make a readonly relation writable, pass +false+.
         | 
| 1283 1285 | 
             
                #
         | 
| 1284 1286 | 
             
                #   users.readonly(false)
         | 
| 1285 1287 | 
             
                #   users.first.save
         | 
| 1286 | 
            -
                #   => true
         | 
| 1288 | 
            +
                #   # => true
         | 
| 1287 1289 | 
             
                def readonly(value = true)
         | 
| 1288 1290 | 
             
                  spawn.readonly!(value)
         | 
| 1289 1291 | 
             
                end
         | 
| @@ -1298,7 +1300,7 @@ module ActiveRecord | |
| 1298 1300 | 
             
                #
         | 
| 1299 1301 | 
             
                #   user = User.strict_loading.first
         | 
| 1300 1302 | 
             
                #   user.comments.to_a
         | 
| 1301 | 
            -
                #   => ActiveRecord::StrictLoadingViolationError
         | 
| 1303 | 
            +
                #   # => ActiveRecord::StrictLoadingViolationError
         | 
| 1302 1304 | 
             
                def strict_loading(value = true)
         | 
| 1303 1305 | 
             
                  spawn.strict_loading!(value)
         | 
| 1304 1306 | 
             
                end
         | 
| @@ -1896,8 +1898,6 @@ module ActiveRecord | |
| 1896 1898 | 
             
                    return if with_values.empty?
         | 
| 1897 1899 |  | 
| 1898 1900 | 
             
                    with_statements = with_values.map do |with_value|
         | 
| 1899 | 
            -
                      raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
         | 
| 1900 | 
            -
             | 
| 1901 1901 | 
             
                      build_with_value_from_hash(with_value)
         | 
| 1902 1902 | 
             
                    end
         | 
| 1903 1903 |  | 
| @@ -2096,7 +2096,7 @@ module ActiveRecord | |
| 2096 2096 | 
             
                          arg.expr.relation.name
         | 
| 2097 2097 | 
             
                        end
         | 
| 2098 2098 | 
             
                      end
         | 
| 2099 | 
            -
                    end. | 
| 2099 | 
            +
                    end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
         | 
| 2100 2100 | 
             
                  end
         | 
| 2101 2101 |  | 
| 2102 2102 | 
             
                  def extract_table_name_from(string)
         | 
| @@ -2208,6 +2208,13 @@ module ActiveRecord | |
| 2208 2208 | 
             
                    end
         | 
| 2209 2209 | 
             
                  end
         | 
| 2210 2210 |  | 
| 2211 | 
            +
                  def process_with_args(args)
         | 
| 2212 | 
            +
                    args.flat_map do |arg|
         | 
| 2213 | 
            +
                      raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
         | 
| 2214 | 
            +
                      arg.map { |k, v| { k => v } }
         | 
| 2215 | 
            +
                    end
         | 
| 2216 | 
            +
                  end
         | 
| 2217 | 
            +
             | 
| 2211 2218 | 
             
                  STRUCTURAL_VALUE_METHODS = (
         | 
| 2212 2219 | 
             
                    Relation::VALUE_METHODS -
         | 
| 2213 2220 | 
             
                    [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
         | 
| @@ -135,10 +135,14 @@ module ActiveRecord | |
| 135 135 |  | 
| 136 136 | 
             
                    def extract_attribute(node)
         | 
| 137 137 | 
             
                      attr_node = nil
         | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 138 | 
            +
             | 
| 139 | 
            +
                      valid_attrs = Arel.fetch_attribute(node) do |attr|
         | 
| 140 | 
            +
                        !attr_node || attr_node == attr # all attr nodes should be the same
         | 
| 141 | 
            +
                      ensure
         | 
| 140 142 | 
             
                        attr_node = attr
         | 
| 141 143 | 
             
                      end
         | 
| 144 | 
            +
                      return unless valid_attrs # all nested nodes should yield an attribute
         | 
| 145 | 
            +
             | 
| 142 146 | 
             
                      attr_node
         | 
| 143 147 | 
             
                    end
         | 
| 144 148 |  | 
| @@ -172,6 +176,8 @@ module ActiveRecord | |
| 172 176 | 
             
                    end
         | 
| 173 177 |  | 
| 174 178 | 
             
                    def except_predicates(columns)
         | 
| 179 | 
            +
                      return predicates if columns.empty?
         | 
| 180 | 
            +
             | 
| 175 181 | 
             
                      attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
         | 
| 176 182 | 
             
                      non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
         | 
| 177 183 |  | 
| @@ -265,7 +265,12 @@ module ActiveRecord | |
| 265 265 | 
             
                # such situation.
         | 
| 266 266 | 
             
                def create_or_find_by(attributes, &block)
         | 
| 267 267 | 
             
                  with_connection do |connection|
         | 
| 268 | 
            -
                     | 
| 268 | 
            +
                    record = nil
         | 
| 269 | 
            +
                    transaction(requires_new: true) do
         | 
| 270 | 
            +
                      record = create(attributes, &block)
         | 
| 271 | 
            +
                      record._last_transaction_return_status || raise(ActiveRecord::Rollback)
         | 
| 272 | 
            +
                    end
         | 
| 273 | 
            +
                    record
         | 
| 269 274 | 
             
                  rescue ActiveRecord::RecordNotUnique
         | 
| 270 275 | 
             
                    if connection.transaction_open?
         | 
| 271 276 | 
             
                      where(attributes).lock.find_by!(attributes)
         | 
| @@ -280,7 +285,12 @@ module ActiveRecord | |
| 280 285 | 
             
                # is raised if the created record is invalid.
         | 
| 281 286 | 
             
                def create_or_find_by!(attributes, &block)
         | 
| 282 287 | 
             
                  with_connection do |connection|
         | 
| 283 | 
            -
                     | 
| 288 | 
            +
                    record = nil
         | 
| 289 | 
            +
                    transaction(requires_new: true) do
         | 
| 290 | 
            +
                      record = create!(attributes, &block)
         | 
| 291 | 
            +
                      record._last_transaction_return_status || raise(ActiveRecord::Rollback)
         | 
| 292 | 
            +
                    end
         | 
| 293 | 
            +
                    record
         | 
| 284 294 | 
             
                  rescue ActiveRecord::RecordNotUnique
         | 
| 285 295 | 
             
                    if connection.transaction_open?
         | 
| 286 296 | 
             
                      where(attributes).lock.find_by!(attributes)
         | 
| @@ -290,7 +300,7 @@ module ActiveRecord | |
| 290 300 | 
             
                  end
         | 
| 291 301 | 
             
                end
         | 
| 292 302 |  | 
| 293 | 
            -
                # Like #find_or_create_by, but calls {new}[rdoc-ref:Core | 
| 303 | 
            +
                # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
         | 
| 294 304 | 
             
                # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
         | 
| 295 305 | 
             
                def find_or_initialize_by(attributes, &block)
         | 
| 296 306 | 
             
                  find_by(attributes) || new(attributes, &block)
         | 
| @@ -813,7 +823,7 @@ module ActiveRecord | |
| 813 823 | 
             
                #
         | 
| 814 824 | 
             
                # [:returning]
         | 
| 815 825 | 
             
                #   (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
         | 
| 816 | 
            -
                #    | 
| 826 | 
            +
                #   upserted records, which by default is the primary key.
         | 
| 817 827 | 
             
                #   Pass <tt>returning: %w[ id name ]</tt> for both id and name
         | 
| 818 828 | 
             
                #   or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
         | 
| 819 829 | 
             
                #   clause entirely.
         | 
| @@ -942,7 +952,7 @@ module ActiveRecord | |
| 942 952 | 
             
                # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
         | 
| 943 953 | 
             
                # If no time argument is passed, the current time is used as default.
         | 
| 944 954 | 
             
                #
         | 
| 945 | 
            -
                #  | 
| 955 | 
            +
                # ==== Examples
         | 
| 946 956 | 
             
                #
         | 
| 947 957 | 
             
                #   # Touch all records
         | 
| 948 958 | 
             
                #   Person.all.touch_all
         | 
| @@ -202,12 +202,17 @@ module ActiveRecord | |
| 202 202 | 
             
                      end
         | 
| 203 203 |  | 
| 204 204 | 
             
                      indexes_in_create(table, tbl)
         | 
| 205 | 
            -
                      check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
         | 
| 205 | 
            +
                      remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
         | 
| 206 206 | 
             
                      exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
         | 
| 207 207 | 
             
                      unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
         | 
| 208 208 |  | 
| 209 209 | 
             
                      tbl.puts "  end"
         | 
| 210 210 |  | 
| 211 | 
            +
                      if remaining
         | 
| 212 | 
            +
                        tbl.puts
         | 
| 213 | 
            +
                        tbl.print remaining.string
         | 
| 214 | 
            +
                      end
         | 
| 215 | 
            +
             | 
| 211 216 | 
             
                      stream.print tbl.string
         | 
| 212 217 | 
             
                    rescue => e
         | 
| 213 218 | 
             
                      stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
         | 
| @@ -272,24 +277,37 @@ module ActiveRecord | |
| 272 277 |  | 
| 273 278 | 
             
                  def check_constraints_in_create(table, stream)
         | 
| 274 279 | 
             
                    if (check_constraints = @connection.check_constraints(table)).any?
         | 
| 275 | 
            -
                       | 
| 276 | 
            -
                        parts = [
         | 
| 277 | 
            -
                          "t.check_constraint #{check_constraint.expression.inspect}"
         | 
| 278 | 
            -
                        ]
         | 
| 280 | 
            +
                      check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
         | 
| 279 281 |  | 
| 280 | 
            -
             | 
| 281 | 
            -
             | 
| 282 | 
            +
                      unless check_valid.empty?
         | 
| 283 | 
            +
                        check_constraint_statements = check_valid.map do |check|
         | 
| 284 | 
            +
                          "    t.check_constraint #{check_parts(check).join(', ')}"
         | 
| 282 285 | 
             
                        end
         | 
| 283 286 |  | 
| 284 | 
            -
                         | 
| 285 | 
            -
             | 
| 286 | 
            -
                        "    #{parts.join(', ')}"
         | 
| 287 | 
            +
                        stream.puts check_constraint_statements.sort.join("\n")
         | 
| 287 288 | 
             
                      end
         | 
| 288 289 |  | 
| 289 | 
            -
                       | 
| 290 | 
            +
                      unless check_invalid.empty?
         | 
| 291 | 
            +
                        remaining = StringIO.new
         | 
| 292 | 
            +
                        table_name = remove_prefix_and_suffix(table).inspect
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                        add_check_constraint_statements = check_invalid.map do |check|
         | 
| 295 | 
            +
                          "  add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
         | 
| 296 | 
            +
                        end
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                        remaining.puts add_check_constraint_statements.sort.join("\n")
         | 
| 299 | 
            +
                        remaining
         | 
| 300 | 
            +
                      end
         | 
| 290 301 | 
             
                    end
         | 
| 291 302 | 
             
                  end
         | 
| 292 303 |  | 
| 304 | 
            +
                  def check_parts(check)
         | 
| 305 | 
            +
                    check_parts = [ check.expression.inspect ]
         | 
| 306 | 
            +
                    check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
         | 
| 307 | 
            +
                    check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
         | 
| 308 | 
            +
                    check_parts
         | 
| 309 | 
            +
                  end
         | 
| 310 | 
            +
             | 
| 293 311 | 
             
                  def foreign_keys(table, stream)
         | 
| 294 312 | 
             
                    if (foreign_keys = @connection.foreign_keys(table)).any?
         | 
| 295 313 | 
             
                      add_foreign_key_statements = foreign_keys.map do |foreign_key|
         | 
| @@ -30,13 +30,13 @@ module ActiveRecord | |
| 30 30 | 
             
                  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
         | 
| 31 31 | 
             
                  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
         | 
| 32 32 | 
             
                  #
         | 
| 33 | 
            -
                  #  | 
| 33 | 
            +
                  # ==== Options
         | 
| 34 34 | 
             
                  #
         | 
| 35 | 
            -
                  # [ | 
| 35 | 
            +
                  # [+:length+]
         | 
| 36 36 | 
             
                  #   Length of the Secure Random, with a minimum of 24 characters. It will
         | 
| 37 37 | 
             
                  #   default to 24.
         | 
| 38 38 | 
             
                  #
         | 
| 39 | 
            -
                  # [ | 
| 39 | 
            +
                  # [+:on+]
         | 
| 40 40 | 
             
                  #   The callback when the value is generated. When called with <tt>on:
         | 
| 41 41 | 
             
                  #   :initialize</tt>, the value is generated in an
         | 
| 42 42 | 
             
                  #   <tt>after_initialize</tt> callback, otherwise the value will be used
         | 
| @@ -57,12 +57,12 @@ module ActiveRecord | |
| 57 57 | 
             
                    end
         | 
| 58 58 | 
             
                  end
         | 
| 59 59 |  | 
| 60 | 
            -
                  # Works like find_signed, but will raise an  | 
| 60 | 
            +
                  # Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
         | 
| 61 61 | 
             
                  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
         | 
| 62 | 
            -
                  # or has been tampered with. It will also raise an  | 
| 62 | 
            +
                  # or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
         | 
| 63 63 | 
             
                  # the valid signed id can't find a record.
         | 
| 64 64 | 
             
                  #
         | 
| 65 | 
            -
                  #  | 
| 65 | 
            +
                  # ==== Examples
         | 
| 66 66 | 
             
                  #
         | 
| 67 67 | 
             
                  #   User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
         | 
| 68 68 | 
             
                  #
         | 
| @@ -76,8 +76,9 @@ module ActiveRecord | |
| 76 76 | 
             
                  end
         | 
| 77 77 |  | 
| 78 78 | 
             
                  # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
         | 
| 79 | 
            -
                  # with the class-level +signed_id_verifier_secret+, which within  | 
| 80 | 
            -
                  # Rails.application.key_generator. | 
| 79 | 
            +
                  # with the class-level +signed_id_verifier_secret+, which within Rails comes from
         | 
| 80 | 
            +
                  # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
         | 
| 81 | 
            +
                  # By default, it's SHA256 for the digest and JSON for the serialization.
         | 
| 81 82 | 
             
                  def signed_id_verifier
         | 
| 82 83 | 
             
                    @signed_id_verifier ||= begin
         | 
| 83 84 | 
             
                      secret = signed_id_verifier_secret
         | 
| @@ -93,7 +94,7 @@ module ActiveRecord | |
| 93 94 |  | 
| 94 95 | 
             
                  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
         | 
| 95 96 | 
             
                  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
         | 
| 96 | 
            -
                  # your custom verifier for that in advance. See  | 
| 97 | 
            +
                  # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
         | 
| 97 98 | 
             
                  def signed_id_verifier=(verifier)
         | 
| 98 99 | 
             
                    @signed_id_verifier = verifier
         | 
| 99 100 | 
             
                  end
         | 
| @@ -132,6 +132,13 @@ module ActiveRecord | |
| 132 132 | 
             
                      tempfile = Tempfile.open("uncommented_structure.sql")
         | 
| 133 133 | 
             
                      begin
         | 
| 134 134 | 
             
                        File.foreach(filename) do |line|
         | 
| 135 | 
            +
                          next if line.start_with?("\\restrict ")
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                          if line.start_with?("\\unrestrict ")
         | 
| 138 | 
            +
                            removing_comments = true
         | 
| 139 | 
            +
                            next
         | 
| 140 | 
            +
                          end
         | 
| 141 | 
            +
             | 
| 135 142 | 
             
                          unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
         | 
| 136 143 | 
             
                            tempfile << line
         | 
| 137 144 | 
             
                            removing_comments = false
         | 
| @@ -13,7 +13,7 @@ module ActiveRecord | |
| 13 13 | 
             
                                   scope: [:kind, :name]
         | 
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| 16 | 
            -
                attr_accessor :_new_record_before_last_commit # :nodoc:
         | 
| 16 | 
            +
                attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:
         | 
| 17 17 |  | 
| 18 18 | 
             
                # = Active Record \Transactions
         | 
| 19 19 | 
             
                #
         | 
| @@ -418,6 +418,7 @@ module ActiveRecord | |
| 418 418 | 
             
                      status = yield
         | 
| 419 419 | 
             
                      raise ActiveRecord::Rollback unless status
         | 
| 420 420 | 
             
                    end
         | 
| 421 | 
            +
                    @_last_transaction_return_status = status
         | 
| 421 422 | 
             
                    status
         | 
| 422 423 | 
             
                  end
         | 
| 423 424 | 
             
                end
         | 
| @@ -433,6 +434,7 @@ module ActiveRecord | |
| 433 434 | 
             
                  def init_internals
         | 
| 434 435 | 
             
                    super
         | 
| 435 436 | 
             
                    @_start_transaction_state = nil
         | 
| 437 | 
            +
                    @_last_transaction_return_status = nil
         | 
| 436 438 | 
             
                    @_committed_already_called = nil
         | 
| 437 439 | 
             
                    @_new_record_before_last_commit = nil
         | 
| 438 440 | 
             
                  end
         | 
    
        data/lib/active_record.rb
    CHANGED
    
    | @@ -86,7 +86,6 @@ module ActiveRecord | |
| 86 86 | 
             
              autoload :Timestamp
         | 
| 87 87 | 
             
              autoload :TokenFor
         | 
| 88 88 | 
             
              autoload :TouchLater
         | 
| 89 | 
            -
              autoload :Transaction
         | 
| 90 89 | 
             
              autoload :Transactions
         | 
| 91 90 | 
             
              autoload :Translation
         | 
| 92 91 | 
             
              autoload :Validations
         | 
| @@ -108,6 +107,7 @@ module ActiveRecord | |
| 108 107 | 
             
                autoload :Result
         | 
| 109 108 | 
             
                autoload :StatementCache
         | 
| 110 109 | 
             
                autoload :TableMetadata
         | 
| 110 | 
            +
                autoload :Transaction
         | 
| 111 111 | 
             
                autoload :Type
         | 
| 112 112 |  | 
| 113 113 | 
             
                autoload_under "relation" do
         | 
    
        data/lib/arel/collectors/bind.rb
    CHANGED
    
    
    
        data/lib/arel/crud.rb
    CHANGED
    
    | @@ -26,6 +26,7 @@ module Arel # :nodoc: all | |
| 26 26 | 
             
                  um.offset(offset)
         | 
| 27 27 | 
             
                  um.order(*orders)
         | 
| 28 28 | 
             
                  um.wheres = constraints
         | 
| 29 | 
            +
                  um.comment(comment)
         | 
| 29 30 | 
             
                  um.key = key
         | 
| 30 31 |  | 
| 31 32 | 
             
                  um.group(group_values_columns) unless group_values_columns.empty?
         | 
| @@ -39,6 +40,7 @@ module Arel # :nodoc: all | |
| 39 40 | 
             
                  dm.offset(offset)
         | 
| 40 41 | 
             
                  dm.order(*orders)
         | 
| 41 42 | 
             
                  dm.wheres = constraints
         | 
| 43 | 
            +
                  dm.comment(comment)
         | 
| 42 44 | 
             
                  dm.key = key
         | 
| 43 45 | 
             
                  dm.group(group_values_columns) unless group_values_columns.empty?
         | 
| 44 46 | 
             
                  dm.having(having_clause) unless having_clause.nil?
         |