Skip to content

Commit

Permalink
Merge pull request #40293 from eugeneius/batched_rows_affected
Browse files Browse the repository at this point in the history
Return rows affected from batched update_all and delete_all
  • Loading branch information
eugeneius committed Oct 3, 2020
2 parents 166b63e + 80e73ad commit d46668a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 9 deletions.
12 changes: 12 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,15 @@
* `BatchEnumerator#update_all` and `BatchEnumerator#delete_all` now return the
total number of rows affected, just like their non-batched counterparts.

```ruby
Person.in_batches.update_all("first_name = 'Eugene'") # => 42
Person.in_batches.delete_all # => 42
```

Fixes #40287.

*Eugene Kenny*

* Add support for PostgreSQL `interval` data type with conversion to
`ActiveSupport::Duration` when loading records from database and
serialization to ISO 8601 formatted duration string on save.
Expand Down
Expand Up @@ -41,19 +41,35 @@ def each_record
end
end

# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
# Deletes records in batches. Returns the total number of rows affected.
#
# People.in_batches.delete_all
# People.where('age < 10').in_batches.destroy_all
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
relation.public_send(method, *args, &block)
end
# Person.in_batches.delete_all
#
# See Relation#delete_all for details of how each batch is deleted.
def delete_all
sum(&:delete_all)
end

# Updates records in batches. Returns the total number of rows affected.
#
# Person.in_batches.update_all("age = age + 1")
#
# See Relation#update_all for details of how each batch is updated.
def update_all(updates)
sum do |relation|
relation.update_all(updates)
end
end

# Destroys records in batches.
#
# Person.where("age < 10").in_batches.destroy_all
#
# See Relation#destroy_all for details of how each batch is destroyed.
def destroy_all
each(&:destroy_all)
end

# Yields an ActiveRecord::Relation object for each batch of records.
#
# Person.in_batches.each do |relation|
Expand Down
16 changes: 16 additions & 0 deletions activerecord/test/cases/batches_test.rb
Expand Up @@ -356,13 +356,29 @@ def test_in_batches_update_all_affect_all_records
assert_equal Post.all.pluck(:title), ["updated-title"] * Post.count
end

def test_in_batches_update_all_returns_rows_affected
assert_equal 11, Post.in_batches(of: 2).update_all(title: "updated-title")
end

def test_in_batches_update_all_returns_zero_when_no_batches
assert_equal 0, Post.where("1=0").in_batches(of: 2).update_all(title: "updated-title")
end

def test_in_batches_delete_all_should_not_delete_records_in_other_batches
not_deleted_count = Post.where("id <= 2").count
Post.where("id > 2").in_batches(of: 2).delete_all
assert_equal 0, Post.where("id > 2").count
assert_equal not_deleted_count, Post.count
end

def test_in_batches_delete_all_returns_rows_affected
assert_equal 11, Post.in_batches(of: 2).delete_all
end

def test_in_batches_delete_all_returns_zero_when_no_batches
assert_equal 0, Post.where("1=0").in_batches(of: 2).delete_all
end

def test_in_batches_should_not_be_loaded
Post.in_batches(of: 1) do |relation|
assert_not_predicate relation, :loaded?
Expand Down

0 comments on commit d46668a

Please sign in to comment.