diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4d8b19b8ab..3b47799989 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Make `increment_counter`/`decrement_counter` accept an amount argument
+
+ ```ruby
+ Post.increment_counter(:comments_count, 5, by: 3)
+ ```
+
+ *fatkodima*
+
* Add support for `Array#intersect?` to `ActiveRecord::Relation`.
`Array#intersect?` is only available on Ruby 3.1 or later.
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 8a72ee2f61..480f08a081 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -126,6 +126,7 @@ def update_counters(id, counters)
#
# * +counter_name+ - The name of the field that should be incremented.
# * +id+ - The id of the object that should be incremented or an array of ids.
+ # * :by - The amount by which to increment the value. Defaults to +1+.
# * :touch - Touch timestamp columns when updating.
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
# touch that column or an array of symbols to touch just those ones.
@@ -136,10 +137,14 @@ def update_counters(id, counters)
# DiscussionBoard.increment_counter(:posts_count, 5)
#
# # Increment the posts_count column for the record with an id of 5
+ # # by a specific amount.
+ # DiscussionBoard.increment_counter(:posts_count, 5, by: 3)
+ #
+ # # Increment the posts_count column for the record with an id of 5
# # and update the updated_at value.
# DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
- def increment_counter(counter_name, id, touch: nil)
- update_counters(id, counter_name => 1, touch: touch)
+ def increment_counter(counter_name, id, by: 1, touch: nil)
+ update_counters(id, counter_name => by, touch: touch)
end
# Decrement a numeric field by one, via a direct SQL update.
@@ -151,6 +156,7 @@ def increment_counter(counter_name, id, touch: nil)
#
# * +counter_name+ - The name of the field that should be decremented.
# * +id+ - The id of the object that should be decremented or an array of ids.
+ # * :by - The amount by which to increment the value. Defaults to +1+.
# * :touch - Touch timestamp columns when updating.
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
# touch that column or an array of symbols to touch just those ones.
@@ -161,10 +167,14 @@ def increment_counter(counter_name, id, touch: nil)
# DiscussionBoard.decrement_counter(:posts_count, 5)
#
# # Decrement the posts_count column for the record with an id of 5
+ # by a specific amount.
+ # DiscussionBoard.decrement_counter(:posts_count, 5, by: 3)
+ #
+ # # Decrement the posts_count column for the record with an id of 5
# # and update the updated_at value.
# DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
- def decrement_counter(counter_name, id, touch: nil)
- update_counters(id, counter_name => -1, touch: touch)
+ def decrement_counter(counter_name, id, by: 1, touch: nil)
+ update_counters(id, counter_name => -by, touch: touch)
end
def counter_cache_column?(name) # :nodoc:
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 52d34747df..6a16d14d8b 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -40,12 +40,24 @@ class ::SpecialReply < ::Reply
end
end
+ test "increment counter by specific amount" do
+ assert_difference "@topic.reload.replies_count", +2 do
+ Topic.increment_counter(:replies_count, @topic.id, by: 2)
+ end
+ end
+
test "decrement counter" do
assert_difference "@topic.reload.replies_count", -1 do
Topic.decrement_counter(:replies_count, @topic.id)
end
end
+ test "decrement counter by specific amount" do
+ assert_difference "@topic.reload.replies_count", -2 do
+ Topic.decrement_counter(:replies_count, @topic.id, by: 2)
+ end
+ end
+
test "reset counters" do
# throw the count off by 1
Topic.increment_counter(:replies_count, @topic.id)