Repository: rails/rails
File: activerecord/lib/active_record/associations/collection_proxy.rb (Ruby)

Code before:
      def reset
        proxy_association.reset
        proxy_association.reset_scope
        reset_scope
      end

      def reset_scope # :nodoc:
        @offsets = @take = nil
        @scope = nil
        self
      end

      def inspect # :nodoc:
        load_target if find_from_target?
        super
      end

      def pretty_print(pp) # :nodoc:
        load_target if find_from_target?
        super
      end

      %w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
        class_eval <<~RUBY, __FILE__, __LINE__ + 1
          def #{method}(...)
            if loaded? && @association&.target&.any? { |r| r.new_record? }
              ActiveRecord.deprecator.warn(<<~MSG)
                Using #{method} on association with unpersisted records
                (\#{to_a.reject(&:persisted?).map(&:class).uniq.join(', ')})
                is deprecated. The unpersisted records will be lost after this operation.
                Please either persist your records first or store them separately before
                calling #{method}. This will become an error in Rails 8.1.
              MSG
              scope.#{method}(...)
            else
              scope.#{method}(...).tap { reset }
            end
          end
        RUBY
      end

      delegate_methods = [
        QueryMethods,
        SpawnMethods,
      ].flat_map { |klass|
        klass.public_instance_methods(false)
      } - self.public_instance_methods(false) - [ :select ] + [ :scoping, :values, :load_async ]

      delegate(*delegate_methods, to: :scope)

      private

Review comment (bug):
Why is `loaded?` relevant?

This test would also change the behavior with the new code:

```
  def test_insert_all_with_unpersisted_records_deprecation
    author = Author.create!(name: "Rafael")
    author.books.build(title: "Unpersisted Book")

    author.books.insert_all([{ title: "New Book" }])

    assert_equal 1, author.books.size
    assert_equal ["Unpersisted Book"], author.books.pluck(:title)
  end
```

Should we not show the deprecation in that case as well?

Code after:
      def reset
        proxy_association.reset
        proxy_association.reset_scope
        reset_scope
      end

      def reset_scope # :nodoc:
        @offsets = @take = nil
        @scope = nil
        self
      end

      def inspect # :nodoc:
        load_target if find_from_target?
        super
      end

      def pretty_print(pp) # :nodoc:
        load_target if find_from_target?
        super
      end

      %w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
        class_eval <<~RUBY, __FILE__, __LINE__ + 1
          def #{method}(...)
            if @association&.target&.any? { |r| r.new_record? }
              association_name = @association.reflection.name
              ActiveRecord.deprecator.warn(<<~MSG)
                Using #{method} on association \#{association_name} with unpersisted records
                is deprecated. The unpersisted records will be lost after this operation.
                Please either persist your records first or store them separately before
                calling #{method}.
              MSG
              scope.#{method}(...)
            else
              scope.#{method}(...).tap { reset }
            end
          end
        RUBY
      end

      delegate_methods = [
        QueryMethods,
        SpawnMethods,
      ].flat_map { |klass|
        klass.public_instance_methods(false)
      } - self.public_instance_methods(false) - [ :select ] + [ :scoping, :values, :load_async ]

      delegate(*delegate_methods, to: :scope)

      private