Add initial value support to MemCacheStore #increment and #decrement

This commit is contained in:
Andrej Blagojević 2022-05-11 15:44:32 +00:00 committed by GitHub
parent fe8d41eda1
commit f48bf3975f
7 changed files with 141 additions and 32 deletions

View File

@ -1,3 +1,11 @@
* Allow #increment and #decrement methods of `ActiveSupport::Cache::Store`
subclasses to set new values.
Previously incrementing or decrementing an unset key would fail and return
nil. A default will now be assumed and the key will be created.
*Andrej Blagojević*, *Eugene Kenny*
* Add `skip_nil:` support to `RedisCacheStore`
*Joey Paris*

View File

@ -43,14 +43,33 @@ module ActiveSupport
end
end
# Increments an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
# Increment a cached integer value. Returns the updated value.
#
# If the key is unset, it starts from +0+:
#
# cache.increment("foo") # => 1
# cache.increment("bar", 100) # => 100
#
# To set a specific value, call #write:
#
# cache.write("baz", 5)
# cache.increment("baz") # => 6
#
def increment(name, amount = 1, options = nil)
modify_value(name, amount, options)
end
# Decrements an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
# Decrement a cached integer value. Returns the updated value.
#
# If the key is unset, it will be set to +-amount+.
#
# cache.decrement("foo") # => -1
#
# To set a specific value, call #write:
#
# cache.write("baz", 5)
# cache.decrement("baz") # => 4
#
def decrement(name, amount = 1, options = nil)
modify_value(name, -amount, options)
end
@ -179,8 +198,8 @@ module ActiveSupport
end
end
# Modifies the amount of an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
# Modifies the amount of an integer value that is stored in the cache.
# If the key is not found it is created and set to +amount+.
def modify_value(name, amount, options)
file_name = normalize_key(name, options)
@ -191,6 +210,9 @@ module ActiveSupport
num = num.to_i + amount
write(name, num, options)
num
else
write(name, Integer(amount), options)
amount
end
end
end

View File

@ -129,28 +129,50 @@ module ActiveSupport
end
end
# Increment a cached value. This method uses the memcached incr atomic
# operator and can only be used on values written with the +:raw+ option.
# Calling it on a value not stored with +:raw+ will initialize that value
# to zero.
# Increment a cached integer value using the memcached incr atomic operator.
# Returns the updated value.
#
# If the key is unset or has expired, it will be set to +amount+:
#
# cache.increment("foo") # => 1
# cache.increment("bar", 100) # => 100
#
# To set a specific value, call #write passing <tt>raw: true</tt>:
#
# cache.write("baz", 5, raw: true)
# cache.increment("baz") # => 6
#
# Incrementing a non-numeric value, or a value written without
# <tt>raw: true</tt>, will fail and return +nil+.
def increment(name, amount = 1, options = nil)
options = merged_options(options)
instrument(:increment, name, amount: amount) do
rescue_error_with nil do
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in], amount) }
end
end
end
# Decrement a cached value. This method uses the memcached decr atomic
# operator and can only be used on values written with the +:raw+ option.
# Calling it on a value not stored with +:raw+ will initialize that value
# to zero.
# Decrement a cached integer value using the memcached decr atomic operator.
# Returns the updated value.
#
# If the key is unset or has expired, it will be set to 0. Memcached
# does not support negative counters.
#
# cache.decrement("foo") # => 0
#
# To set a specific value, call #write passing <tt>raw: true</tt>:
#
# cache.write("baz", 5, raw: true)
# cache.decrement("baz") # => 4
#
# Decrementing a non-numeric value, or a value written without
# <tt>raw: true</tt>, will fail and return +nil+.
def decrement(name, amount = 1, options = nil)
options = merged_options(options)
instrument(:decrement, name, amount: amount) do
rescue_error_with nil do
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in], 0) }
end
end
end

View File

@ -108,12 +108,33 @@ module ActiveSupport
@pruning
end
# Increment an integer value in the cache.
# Increment a cached integer value. Returns the updated value.
#
# If the key is unset, it will be set to +amount+:
#
# cache.increment("foo") # => 1
# cache.increment("bar", 100) # => 100
#
# To set a specific value, call #write:
#
# cache.write("baz", 5)
# cache.increment("baz") # => 6
#
def increment(name, amount = 1, options = nil)
modify_value(name, amount, options)
end
# Decrement an integer value in the cache.
# Decrement a cached integer value. Returns the updated value.
#
# If the key is unset or has expired, it will be set to +-amount+.
#
# cache.decrement("foo") # => -1
#
# To set a specific value, call #write:
#
# cache.write("baz", 5)
# cache.decrement("baz") # => 4
#
def decrement(name, amount = 1, options = nil)
modify_value(name, -amount, options)
end
@ -188,6 +209,8 @@ module ActiveSupport
end
end
# Modifies the amount of an integer value that is stored in the cache.
# If the key is not found it is created and set to +amount+.
def modify_value(name, amount, options)
options = merged_options(options)
synchronize do
@ -195,6 +218,9 @@ module ActiveSupport
num = num.to_i + amount
write(name, num, options)
num
else
write(name, Integer(amount), options)
amount
end
end
end

View File

@ -235,12 +235,21 @@ module ActiveSupport
end
end
# Cache Store API implementation.
# Increment a cached integer value using the Redis incrby atomic operator.
# Returns the updated value.
#
# Increment a cached value. This method uses the Redis incr atomic
# operator and can only be used on values written with the +:raw+ option.
# Calling it on a value not stored with +:raw+ will initialize that value
# to zero.
# If the key is unset or has expired, it will be set to +amount+:
#
# cache.increment("foo") # => 1
# cache.increment("bar", 100) # => 100
#
# To set a specific value, call #write passing <tt>raw: true</tt>:
#
# cache.write("baz", 5, raw: true)
# cache.increment("baz") # => 6
#
# Incrementing a non-numeric value, or a value written without
# <tt>raw: true</tt>, will fail and return +nil+.
#
# Failsafe: Raises errors.
def increment(name, amount = 1, options = nil)
@ -258,12 +267,20 @@ module ActiveSupport
end
end
# Cache Store API implementation.
# Decrement a cached integer value using the Redis decrby atomic operator.
# Returns the updated value.
#
# Decrement a cached value. This method uses the Redis decr atomic
# operator and can only be used on values written with the +:raw+ option.
# Calling it on a value not stored with +:raw+ will initialize that value
# to zero.
# If the key is unset or has expired, it will be set to -amount:
#
# cache.decrement("foo") # => -1
#
# To set a specific value, call #write passing <tt>raw: true</tt>:
#
# cache.write("baz", 5, raw: true)
# cache.decrement("baz") # => 4
#
# Decrementing a non-numeric value, or a value written without
# <tt>raw: true</tt>, will fail and return +nil+.
#
# Failsafe: Raises errors.
def decrement(name, amount = 1, options = nil)

View File

@ -11,7 +11,9 @@ module CacheIncrementDecrementBehavior
assert_equal 3, @cache.read(key, raw: true).to_i
missing = @cache.increment(SecureRandom.alphanumeric)
assert(missing.nil? || missing == 1)
assert_equal 1, missing
missing = @cache.increment(SecureRandom.alphanumeric, 100)
assert_equal 100, missing
end
def test_decrement
@ -24,6 +26,8 @@ module CacheIncrementDecrementBehavior
assert_equal 1, @cache.read(key, raw: true).to_i
missing = @cache.decrement(SecureRandom.alphanumeric)
assert(missing.nil? || missing == -1)
assert_equal @cache.is_a?(ActiveSupport::Cache::MemCacheStore) ? 0 : -1, missing
missing = @cache.decrement(SecureRandom.alphanumeric, 100)
assert_equal @cache.is_a?(ActiveSupport::Cache::MemCacheStore) ? 0 : -100, missing
end
end

View File

@ -127,6 +127,11 @@ class MemCacheStoreTest < ActiveSupport::TestCase
end
end
def test_increment_unset_key
assert_equal 1, @cache.increment("foo")
assert_equal "1", @cache.read("foo", raw: true)
end
def test_write_expires_at
cache = lookup_store(raw: true, namespace: nil)
@ -139,14 +144,19 @@ class MemCacheStoreTest < ActiveSupport::TestCase
def test_increment_expires_in
cache = lookup_store(raw: true, namespace: nil)
assert_called_with client(cache), :incr, [ "foo", 1, 60 ] do
assert_called_with client(cache), :incr, [ "foo", 1, 60, 1 ] do
cache.increment("foo", 1, expires_in: 60)
end
end
def test_decrement_unset_key
assert_equal 0, @cache.decrement("foo")
assert_equal "0", @cache.read("foo", raw: true)
end
def test_decrement_expires_in
cache = lookup_store(raw: true, namespace: nil)
assert_called_with client(cache), :decr, [ "foo", 1, 60 ] do
assert_called_with client(cache), :decr, [ "foo", 1, 60, 0 ] do
cache.decrement("foo", 1, expires_in: 60)
end
end