Allow to set cache expiry as an absolute timestamp
Sometime it can be useful to set a cache entry expiry not relative to current time, but as an absolute timestamps, e.g.: - If you want to cache an API token that was provided to you with a precise expiry time. - If you want to cache something until a precise cutoff time, e.g. `expires_at: Time.now.at_end_of_hour` This leaves the `@created_at` variable in a weird state, but this is to avoid breaking the binary format.
This commit is contained in:
parent
0ff395e1b1
commit
9de17ac4a4
|
@ -1,3 +1,11 @@
|
|||
* Add `expires_at` argument to `ActiveSupport::Cache` `write` and `fetch` to set a cache entry TTL as an absolute time.
|
||||
|
||||
```ruby
|
||||
Rails.cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
||||
```
|
||||
|
||||
*Jean Boussier*
|
||||
|
||||
* Deprecate `ActiveSupport::TimeWithZone.name` so that from Rails 7.1 it will use the default implementation.
|
||||
|
||||
*Andrew White*
|
||||
|
|
|
@ -267,6 +267,14 @@ module ActiveSupport
|
|||
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
||||
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
||||
#
|
||||
# Setting <tt>:expires_at</tt> will set an absolute expiration time on the cache.
|
||||
# All caches support auto-expiring content after a specified number of
|
||||
# seconds. This value can only be supplied to the +fetch+ or +write+ method to
|
||||
# affect just one entry.
|
||||
#
|
||||
# cache = ActiveSupport::Cache::MemoryStore.new
|
||||
# cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
||||
#
|
||||
# Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
|
||||
# is of the same version. nil is returned on mismatches despite contents.
|
||||
# This feature is used to support recyclable cache keys.
|
||||
|
@ -751,7 +759,7 @@ module ActiveSupport
|
|||
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
||||
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
||||
# for a brief period while the entry is being recalculated.
|
||||
entry.expires_at = Time.now + race_ttl
|
||||
entry.expires_at = Time.now.to_f + race_ttl
|
||||
write_entry(key, entry, expires_in: race_ttl * 2)
|
||||
else
|
||||
delete_entry(key, **options)
|
||||
|
@ -801,12 +809,12 @@ module ActiveSupport
|
|||
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
||||
|
||||
# Creates a new cache entry for the specified value. Options supported are
|
||||
# +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
|
||||
def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
|
||||
# +:compress+, +:compress_threshold+, +:version+, +:expires_at+ and +:expires_in+.
|
||||
def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, expires_at: nil, **)
|
||||
@value = value
|
||||
@version = version
|
||||
@created_at = Time.now.to_f
|
||||
@expires_in = expires_in && expires_in.to_f
|
||||
@created_at = 0.0
|
||||
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
|
||||
|
||||
compress!(compress_threshold) if compress
|
||||
end
|
||||
|
|
|
@ -393,16 +393,42 @@ module CacheStoreBehavior
|
|||
time = Time.local(2008, 4, 24)
|
||||
|
||||
Time.stub(:now, time) do
|
||||
@cache.write("foo", "bar")
|
||||
@cache.write("foo", "bar", expires_in: 1.minute)
|
||||
@cache.write("egg", "spam", expires_in: 2.minute)
|
||||
assert_equal "bar", @cache.read("foo")
|
||||
assert_equal "spam", @cache.read("egg")
|
||||
end
|
||||
|
||||
Time.stub(:now, time + 30) do
|
||||
assert_equal "bar", @cache.read("foo")
|
||||
assert_equal "spam", @cache.read("egg")
|
||||
end
|
||||
|
||||
Time.stub(:now, time + 61) do
|
||||
assert_nil @cache.read("foo")
|
||||
assert_equal "spam", @cache.read("egg")
|
||||
end
|
||||
|
||||
Time.stub(:now, time + 121) do
|
||||
assert_nil @cache.read("foo")
|
||||
assert_nil @cache.read("egg")
|
||||
end
|
||||
end
|
||||
|
||||
def test_expires_at
|
||||
time = Time.local(2008, 4, 24)
|
||||
|
||||
Time.stub(:now, time) do
|
||||
@cache.write("foo", "bar", expires_at: time + 15.seconds)
|
||||
assert_equal "bar", @cache.read("foo")
|
||||
end
|
||||
|
||||
Time.stub(:now, time + 10) do
|
||||
assert_equal "bar", @cache.read("foo")
|
||||
end
|
||||
|
||||
Time.stub(:now, time + 30) do
|
||||
assert_nil @cache.read("foo")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,8 +9,14 @@ class CacheEntryTest < ActiveSupport::TestCase
|
|||
assert_not entry.expired?, "entry not expired"
|
||||
entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
|
||||
assert_not entry.expired?, "entry not expired"
|
||||
Time.stub(:now, Time.now + 61) do
|
||||
Time.stub(:now, Time.at(entry.expires_at + 1)) do
|
||||
assert entry.expired?, "entry is expired"
|
||||
end
|
||||
end
|
||||
|
||||
def test_initialize_with_expires_at
|
||||
entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
|
||||
clone = ActiveSupport::Cache::Entry.new("value", expires_at: entry.expires_at)
|
||||
assert_equal entry.expires_at, clone.expires_at
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue