From f4bc1576d9fba76eefa00bf9989a32f885c9a6c8 Mon Sep 17 00:00:00 2001 From: Seiichi Yonezawa Date: Tue, 7 Nov 2023 01:37:13 +0900 Subject: [PATCH 01/17] Support expiration time for lists --- lib/kredis/types.rb | 4 ++-- lib/kredis/types/list.rb | 20 ++++++++++++++++---- test/types/list_test.rb | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/lib/kredis/types.rb b/lib/kredis/types.rb index 91c8db6..0ee6f2c 100644 --- a/lib/kredis/types.rb +++ b/lib/kredis/types.rb @@ -61,8 +61,8 @@ def hash(key, typed: :string, default: nil, config: :shared, after_change: nil) type_from(Hash, config, key, after_change: after_change, default: default, typed: typed) end - def list(key, default: nil, typed: :string, config: :shared, after_change: nil) - type_from(List, config, key, after_change: after_change, default: default, typed: typed) + def list(key, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + type_from(List, config, key, after_change: after_change, default: default, typed: typed, expires_in: expires_in) end def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 0c1e06f..133ed3a 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -3,9 +3,9 @@ class Kredis::Types::List < Kredis::Types::Proxying prepend Kredis::DefaultValues - proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del + proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del, :expire - attr_accessor :typed + attr_accessor :typed, :expires_in def elements strings_to_types(lrange(0, -1) || [], typed) @@ -17,11 +17,19 @@ def remove(*elements) end def prepend(*elements) - lpush types_to_strings(elements, typed) if elements.flatten.any? + return if elements.flatten.empty? + + lpush types_to_strings(elements, typed) + expire_in expires_in if expires_in.present? + elements end def append(*elements) - rpush types_to_strings(elements, typed) if elements.flatten.any? + return if elements.flatten.empty? + + rpush types_to_strings(elements, typed) + expire_in expires_in if expires_in.present? + elements end alias << append @@ -33,6 +41,10 @@ def last(n = nil) n ? lrange(-n, -1) : lrange(-1, -1).first end + def expire_in(seconds) + expire seconds.to_i + end + private def set_default append default diff --git a/test/types/list_test.rb b/test/types/list_test.rb index 7507548..25ec713 100644 --- a/test/types/list_test.rb +++ b/test/types/list_test.rb @@ -76,6 +76,27 @@ class ListTest < ActiveSupport::TestCase assert_equal %w[ 2 3 ], @list.elements end + test "append with expiring list" do + @list = Kredis.list "mylist", expires_in: 1.second + @list.append(%w[1 2 3]) + + sleep 0.5.seconds + assert_equal %w[ 1 2 3 ], @list.elements + + sleep 0.6.seconds + assert_equal [], @list.elements + end + + test "prepend with expiring list" do + @list = Kredis.list "mylist", expires_in: 1.second + @list.prepend(%w[1 2 3]) + + sleep 0.5.seconds + assert_equal %w[ 3 2 1 ], @list.elements + + sleep 0.6.seconds + assert_equal [], @list.elements + end test "default" do @list = Kredis.list "mylist", default: %w[ 1 2 3 ] From ca5d561c48bcaf51a0591e67ed1654b3718a5b69 Mon Sep 17 00:00:00 2001 From: Seiichi Yonezawa Date: Sat, 23 Dec 2023 14:16:14 +0900 Subject: [PATCH 02/17] Remove present? --- lib/kredis/types/list.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 133ed3a..67d13dc 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -20,7 +20,7 @@ def prepend(*elements) return if elements.flatten.empty? lpush types_to_strings(elements, typed) - expire_in expires_in if expires_in.present? + expire_in expires_in if expires_in elements end @@ -28,7 +28,7 @@ def append(*elements) return if elements.flatten.empty? rpush types_to_strings(elements, typed) - expire_in expires_in if expires_in.present? + expire_in expires_in if expires_in elements end alias << append From 1dcd8ea3e972a3b088611c4315d9a45dec4c3dfb Mon Sep 17 00:00:00 2001 From: Seiichi Yonezawa Date: Fri, 29 Dec 2023 07:51:37 +0900 Subject: [PATCH 03/17] Support nx and remove expire_in --- lib/kredis/types/list.rb | 8 ++------ test/types/list_test.rb | 16 +++++++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 67d13dc..cb4e767 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -20,7 +20,7 @@ def prepend(*elements) return if elements.flatten.empty? lpush types_to_strings(elements, typed) - expire_in expires_in if expires_in + expire expires_in.to_i, nx: true if expires_in elements end @@ -28,7 +28,7 @@ def append(*elements) return if elements.flatten.empty? rpush types_to_strings(elements, typed) - expire_in expires_in if expires_in + expire expires_in.to_i, nx: true if expires_in elements end alias << append @@ -41,10 +41,6 @@ def last(n = nil) n ? lrange(-n, -1) : lrange(-1, -1).first end - def expire_in(seconds) - expire seconds.to_i - end - private def set_default append default diff --git a/test/types/list_test.rb b/test/types/list_test.rb index 25ec713..06c57c9 100644 --- a/test/types/list_test.rb +++ b/test/types/list_test.rb @@ -78,9 +78,12 @@ class ListTest < ActiveSupport::TestCase test "append with expiring list" do @list = Kredis.list "mylist", expires_in: 1.second - @list.append(%w[1 2 3]) - - sleep 0.5.seconds + @list.append(%w[1 2]) + + sleep 0.2.seconds + @list.append(3) + + sleep 0.3.seconds assert_equal %w[ 1 2 3 ], @list.elements sleep 0.6.seconds @@ -89,9 +92,12 @@ class ListTest < ActiveSupport::TestCase test "prepend with expiring list" do @list = Kredis.list "mylist", expires_in: 1.second - @list.prepend(%w[1 2 3]) + @list.prepend(%w[1 2]) + + sleep 0.2.seconds + @list.prepend(3) - sleep 0.5.seconds + sleep 0.3.seconds assert_equal %w[ 3 2 1 ], @list.elements sleep 0.6.seconds From 08a1c5f40f2cad0edbe3d1c24bd3312e398f5c51 Mon Sep 17 00:00:00 2001 From: Miller Date: Sat, 27 Jan 2024 01:46:36 +0900 Subject: [PATCH 04/17] Support expiration using ttl --- lib/kredis/types/list.rb | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index cb4e767..701bbd2 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -3,7 +3,7 @@ class Kredis::Types::List < Kredis::Types::Proxying prepend Kredis::DefaultValues - proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del, :expire + proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del, :expire, :ttl attr_accessor :typed, :expires_in @@ -19,17 +19,18 @@ def remove(*elements) def prepend(*elements) return if elements.flatten.empty? - lpush types_to_strings(elements, typed) - expire expires_in.to_i, nx: true if expires_in - elements + with_expiration do + lpush types_to_strings(elements, typed) + end end def append(*elements) return if elements.flatten.empty? - rpush types_to_strings(elements, typed) - expire expires_in.to_i, nx: true if expires_in - elements + + with_expiration do + rpush types_to_strings(elements, typed) + end end alias << append @@ -45,4 +46,12 @@ def last(n = nil) def set_default append default end + + def with_expiration(&block) + block.call.tap do + if expires_in && ttl < 0 + expire expires_in.to_i + end + end + end end From 7c23109b2a99bbca5cc6204caf511b06f265aafb Mon Sep 17 00:00:00 2001 From: Miller Date: Sat, 27 Jan 2024 01:55:14 +0900 Subject: [PATCH 05/17] Use result instead of tap --- lib/kredis/types/list.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 701bbd2..0704556 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -48,10 +48,10 @@ def set_default end def with_expiration(&block) - block.call.tap do - if expires_in && ttl < 0 - expire expires_in.to_i - end + result = block.call + if expires_in && ttl < 0 + expire expires_in.to_i end + result end end From e9a50e6786dd11dfb36be5615a035c9261323d5c Mon Sep 17 00:00:00 2001 From: Miller Date: Sat, 27 Jan 2024 02:01:04 +0900 Subject: [PATCH 06/17] Remove trailing whitespace --- test/types/list_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/list_test.rb b/test/types/list_test.rb index 06c57c9..d9c5301 100644 --- a/test/types/list_test.rb +++ b/test/types/list_test.rb @@ -79,10 +79,10 @@ class ListTest < ActiveSupport::TestCase test "append with expiring list" do @list = Kredis.list "mylist", expires_in: 1.second @list.append(%w[1 2]) - + sleep 0.2.seconds @list.append(3) - + sleep 0.3.seconds assert_equal %w[ 1 2 3 ], @list.elements From 0e05ec15cb032e26a19e228f0ea31d6ac97d1ae2 Mon Sep 17 00:00:00 2001 From: Miller Date: Sat, 27 Jan 2024 02:21:57 +0900 Subject: [PATCH 07/17] Extract module `Expiration` --- lib/kredis.rb | 1 + lib/kredis/expiration.rb | 19 +++++++++++++++++++ lib/kredis/types/list.rb | 13 +++---------- 3 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 lib/kredis/expiration.rb diff --git a/lib/kredis.rb b/lib/kredis.rb index 28f0baf..03f3db7 100644 --- a/lib/kredis.rb +++ b/lib/kredis.rb @@ -11,6 +11,7 @@ require "kredis/namespace" require "kredis/type_casting" require "kredis/default_values" +require "kredis/expiration" require "kredis/types" require "kredis/attributes" diff --git a/lib/kredis/expiration.rb b/lib/kredis/expiration.rb new file mode 100644 index 0000000..a26d560 --- /dev/null +++ b/lib/kredis/expiration.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Kredis::Expiration + extend ActiveSupport::Concern + + included do + proxying :ttl, :expire + attr_accessor :expires_in + end + + private + def with_expiration(&block) + result = block.call + if expires_in && ttl < 0 + expire expires_in.to_i + end + result + end +end diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 0704556..3410022 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -2,10 +2,11 @@ class Kredis::Types::List < Kredis::Types::Proxying prepend Kredis::DefaultValues + include Kredis::Expiration - proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del, :expire, :ttl + proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del - attr_accessor :typed, :expires_in + attr_accessor :typed def elements strings_to_types(lrange(0, -1) || [], typed) @@ -46,12 +47,4 @@ def last(n = nil) def set_default append default end - - def with_expiration(&block) - result = block.call - if expires_in && ttl < 0 - expire expires_in.to_i - end - result - end end From 5397a6e74613b9f7d8d4a1f3fd226b2c8d621e4a Mon Sep 17 00:00:00 2001 From: Miller Date: Mon, 29 Jan 2024 11:47:54 +0900 Subject: [PATCH 08/17] Support expiration in `Set` --- lib/kredis/types.rb | 4 ++-- lib/kredis/types/set.rb | 7 ++++++- test/types/set_test.rb | 24 ++++++++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/kredis/types.rb b/lib/kredis/types.rb index 0ee6f2c..e70c533 100644 --- a/lib/kredis/types.rb +++ b/lib/kredis/types.rb @@ -69,8 +69,8 @@ def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, type_from(UniqueList, config, key, after_change: after_change, default: default, typed: typed, limit: limit) end - def set(key, default: nil, typed: :string, config: :shared, after_change: nil) - type_from(Set, config, key, after_change: after_change, default: default, typed: typed) + def set(key, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + type_from(Set, config, key, after_change: after_change, default: default, typed: typed, expires_in: expires_in) end def ordered_set(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil) diff --git a/lib/kredis/types/set.rb b/lib/kredis/types/set.rb index 4a157b5..328957b 100644 --- a/lib/kredis/types/set.rb +++ b/lib/kredis/types/set.rb @@ -2,6 +2,7 @@ class Kredis::Types::Set < Kredis::Types::Proxying prepend Kredis::DefaultValues + include Kredis::Expiration proxying :smembers, :sadd, :srem, :multi, :del, :sismember, :scard, :spop, :exists?, :srandmember @@ -13,7 +14,11 @@ def members alias to_a members def add(*members) - sadd types_to_strings(members, typed) if members.flatten.any? + return unless members.flatten.any? + + with_expiration do + sadd types_to_strings(members, typed) + end end alias << add diff --git a/test/types/set_test.rb b/test/types/set_test.rb index a062aa5..c1d86fa 100644 --- a/test/types/set_test.rb +++ b/test/types/set_test.rb @@ -2,6 +2,7 @@ require "test_helper" require "active_support/core_ext/object/inclusion" +require "active_support/core_ext/integer" class SetTest < ActiveSupport::TestCase setup { @set = Kredis.set "myset" } @@ -70,10 +71,10 @@ class SetTest < ActiveSupport::TestCase @set.add 1.5, 2.7 @set << 2.7 - assert_equal [ 1.5, 2.7 ], @set.members + assert_equal [1.5, 2.7], @set.members @set.remove(2.7) - assert_equal [ 1.5 ], @set.members + assert_equal [1.5], @set.members assert_equal 1.5, @set.take end @@ -97,11 +98,10 @@ class SetTest < ActiveSupport::TestCase @set = Kredis.set "mylist", typed: :float @set.add 1.5, 2.7 - assert @set.sample.in?([ 1.5, 2.7 ]) - assert_equal [ 1.5, 2.7 ], @set.sample(2).sort + assert @set.sample.in?([1.5, 2.7]) + assert_equal [1.5, 2.7], @set.sample(2).sort end - test "default" do @set = Kredis.set "mylist", default: %w[ 1 2 3 ] assert_equal %w[ 1 2 3 ], @set.members @@ -128,6 +128,18 @@ class SetTest < ActiveSupport::TestCase assert_equal [ 1, 2, 3, 5, 6, 7 ], @set.members end + test "add with expiration" do + @set = Kredis.set "mylist", typed: :integer, expires_in: 1.second + @set.add(%w[ 1 2 3 ]) + + sleep 0.7.seconds + @set.add(%w[ 4 5 ]) + assert_equal [1, 2, 3, 4, 5], @set.members + + sleep 0.5.seconds + assert_equal [], @set.members + end + test "remove with default" do @set = Kredis.set "mylist", default: -> () { %w[ 1 2 3 4 ] } @set.remove(%w[ 2 3 ]) @@ -139,6 +151,6 @@ class SetTest < ActiveSupport::TestCase @set = Kredis.set "mylist", typed: :integer, default: -> () { %w[ 1 2 3 ] } @set.add(%w[ 5 6 7 ]) @set.replace(%w[ 8 9 10 ]) - assert_equal [ 8, 9, 10 ], @set.members + assert_equal [8, 9, 10], @set.members end end From 69081d6236da7acf86ced09d2e6e751d4bb9565e Mon Sep 17 00:00:00 2001 From: Miller Date: Thu, 1 Feb 2024 12:12:53 +0900 Subject: [PATCH 09/17] Support expiration in `OrderedSet` --- lib/kredis/types.rb | 4 ++-- lib/kredis/types/ordered_set.rb | 9 ++++++--- test/types/ordered_set_test.rb | 11 +++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/kredis/types.rb b/lib/kredis/types.rb index e70c533..26a0040 100644 --- a/lib/kredis/types.rb +++ b/lib/kredis/types.rb @@ -73,8 +73,8 @@ def set(key, default: nil, typed: :string, config: :shared, after_change: nil, e type_from(Set, config, key, after_change: after_change, default: default, typed: typed, expires_in: expires_in) end - def ordered_set(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil) - type_from(OrderedSet, config, key, after_change: after_change, default: default, typed: typed, limit: limit) + def ordered_set(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil, expires_in: nil) + type_from(OrderedSet, config, key, after_change: after_change, default: default, typed: typed, limit: limit, expires_in: expires_in) end def slot(key, config: :shared, after_change: nil) diff --git a/lib/kredis/types/ordered_set.rb b/lib/kredis/types/ordered_set.rb index 49e0a78..392432f 100644 --- a/lib/kredis/types/ordered_set.rb +++ b/lib/kredis/types/ordered_set.rb @@ -2,6 +2,7 @@ class Kredis::Types::OrderedSet < Kredis::Types::Proxying prepend Kredis::DefaultValues + include Kredis::Expiration proxying :multi, :zrange, :zrem, :zadd, :zremrangebyrank, :zcard, :exists?, :del, :zscore @@ -53,9 +54,11 @@ def insert(elements, prepending: false) [ score, element ] end - multi do - zadd(elements_with_scores) - trim(from_beginning: prepending) + with_expiration do + multi do + zadd(elements_with_scores) + trim(from_beginning: prepending) + end end end diff --git a/test/types/ordered_set_test.rb b/test/types/ordered_set_test.rb index 3738dc9..fd9cdf9 100644 --- a/test/types/ordered_set_test.rb +++ b/test/types/ordered_set_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "test_helper" +require "active_support/core_ext/integer" class OrderedSetTest < ActiveSupport::TestCase setup { @set = Kredis.ordered_set "ordered-set", limit: 5 } @@ -31,6 +32,16 @@ class OrderedSetTest < ActiveSupport::TestCase assert_equal thousand_elements, @set.elements end + test "append with expiry" do + @set = Kredis.ordered_set "ordered-set", limit: 5, expires_in: 1.second + + @set.append(%w[ 1 2 3 ]) + assert_equal %w[ 1 2 3 ], @set.elements + + sleep 1.1 + assert @set.elements.empty? + end + test "prepend" do @set.prepend(%w[ 1 2 3 ]) @set.prepend(%w[ 1 2 3 4 ]) From f7bcb6162db02fab110b1ad7f8b62c9ae6431736 Mon Sep 17 00:00:00 2001 From: Miller Date: Thu, 1 Feb 2024 12:25:01 +0900 Subject: [PATCH 10/17] Support expiration in `UniqueList` --- lib/kredis/expiration.rb | 4 ++-- lib/kredis/types.rb | 4 ++-- lib/kredis/types/list.rb | 9 ++++----- lib/kredis/types/unique_list.rb | 23 +++++++++++++++-------- test/types/unique_list_test.rb | 11 +++++++++++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/kredis/expiration.rb b/lib/kredis/expiration.rb index a26d560..3c98bda 100644 --- a/lib/kredis/expiration.rb +++ b/lib/kredis/expiration.rb @@ -9,9 +9,9 @@ module Kredis::Expiration end private - def with_expiration(&block) + def with_expiration(suppress: false, &block) result = block.call - if expires_in && ttl < 0 + if !suppress && expires_in && ttl < 0 expire expires_in.to_i end result diff --git a/lib/kredis/types.rb b/lib/kredis/types.rb index 26a0040..6fdedb0 100644 --- a/lib/kredis/types.rb +++ b/lib/kredis/types.rb @@ -65,8 +65,8 @@ def list(key, default: nil, typed: :string, config: :shared, after_change: nil, type_from(List, config, key, after_change: after_change, default: default, typed: typed, expires_in: expires_in) end - def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil) - type_from(UniqueList, config, key, after_change: after_change, default: default, typed: typed, limit: limit) + def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil, expires_in: nil) + type_from(UniqueList, config, key, after_change: after_change, default: default, typed: typed, limit: limit, expires_in: expires_in) end def set(key, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 3410022..77013de 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -17,19 +17,18 @@ def remove(*elements) types_to_strings(elements, typed).each { |element| lrem 0, element } end - def prepend(*elements) + def prepend(*elements, suppress_expiration: false) return if elements.flatten.empty? - with_expiration do + with_expiration(suppress: suppress_expiration) do lpush types_to_strings(elements, typed) end end - def append(*elements) + def append(*elements, suppress_expiration: false) return if elements.flatten.empty? - - with_expiration do + with_expiration(suppress: suppress_expiration) do rpush types_to_strings(elements, typed) end end diff --git a/lib/kredis/types/unique_list.rb b/lib/kredis/types/unique_list.rb index bd73088..38b34bf 100644 --- a/lib/kredis/types/unique_list.rb +++ b/lib/kredis/types/unique_list.rb @@ -3,6 +3,7 @@ # You'd normally call this a set, but Redis already has another data type for that class Kredis::Types::UniqueList < Kredis::Types::List proxying :multi, :ltrim, :exists? + include Kredis::Expiration attr_accessor :typed, :limit @@ -10,10 +11,13 @@ def prepend(elements) elements = Array(elements).uniq return if elements.empty? - multi do - remove elements - super - ltrim 0, (limit - 1) if limit + with_expiration do + + multi do + remove elements + super(elements, suppress_expiration: true) + ltrim 0, (limit - 1) if limit + end end end @@ -21,10 +25,13 @@ def append(elements) elements = Array(elements).uniq return if elements.empty? - multi do - remove elements - super - ltrim(-limit, -1) if limit + with_expiration do + + multi do + remove elements + super(elements, suppress_expiration: true) + ltrim(-limit, -1) if limit + end end end alias << append diff --git a/test/types/unique_list_test.rb b/test/types/unique_list_test.rb index 8ac91ed..4883175 100644 --- a/test/types/unique_list_test.rb +++ b/test/types/unique_list_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "test_helper" +require "active_support/core_ext/integer" class UniqueListTest < ActiveSupport::TestCase setup { @list = Kredis.unique_list "myuniquelist", limit: 5 } @@ -14,6 +15,16 @@ class UniqueListTest < ActiveSupport::TestCase assert_equal %w[ 1 2 3 4 5 ], @list.elements end + test "append with expiration" do + @list = Kredis.unique_list "xs", limit: 5, expires_in: 1.second + + @list.append(%w[ 1 2 3 ]) + assert_equal %w[ 1 2 3 ], @list.elements + + sleep 1.1 + assert @list.elements.empty? + end + test "prepend" do @list.prepend(%w[ 1 2 3 ]) @list.prepend(%w[ 1 2 3 4 ]) From f403578e644a111835c1b0515a9e0991c093da21 Mon Sep 17 00:00:00 2001 From: Miller Date: Thu, 1 Feb 2024 12:39:48 +0900 Subject: [PATCH 11/17] Lint --- lib/kredis/types/unique_list.rb | 2 -- test/types/set_test.rb | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/kredis/types/unique_list.rb b/lib/kredis/types/unique_list.rb index 38b34bf..d18934a 100644 --- a/lib/kredis/types/unique_list.rb +++ b/lib/kredis/types/unique_list.rb @@ -12,7 +12,6 @@ def prepend(elements) return if elements.empty? with_expiration do - multi do remove elements super(elements, suppress_expiration: true) @@ -26,7 +25,6 @@ def append(elements) return if elements.empty? with_expiration do - multi do remove elements super(elements, suppress_expiration: true) diff --git a/test/types/set_test.rb b/test/types/set_test.rb index c1d86fa..9501baf 100644 --- a/test/types/set_test.rb +++ b/test/types/set_test.rb @@ -71,10 +71,10 @@ class SetTest < ActiveSupport::TestCase @set.add 1.5, 2.7 @set << 2.7 - assert_equal [1.5, 2.7], @set.members + assert_equal [ 1.5, 2.7 ], @set.members @set.remove(2.7) - assert_equal [1.5], @set.members + assert_equal [ 1.5 ], @set.members assert_equal 1.5, @set.take end @@ -98,8 +98,8 @@ class SetTest < ActiveSupport::TestCase @set = Kredis.set "mylist", typed: :float @set.add 1.5, 2.7 - assert @set.sample.in?([1.5, 2.7]) - assert_equal [1.5, 2.7], @set.sample(2).sort + assert @set.sample.in?([ 1.5, 2.7 ]) + assert_equal [ 1.5, 2.7 ], @set.sample(2).sort end test "default" do @@ -151,6 +151,6 @@ class SetTest < ActiveSupport::TestCase @set = Kredis.set "mylist", typed: :integer, default: -> () { %w[ 1 2 3 ] } @set.add(%w[ 5 6 7 ]) @set.replace(%w[ 8 9 10 ]) - assert_equal [8, 9, 10], @set.members + assert_equal [ 8, 9, 10 ], @set.members end end From 4b2fa7d8e70191406d66a34c34aca4fdbd1a11d7 Mon Sep 17 00:00:00 2001 From: Miller Date: Thu, 1 Feb 2024 12:44:59 +0900 Subject: [PATCH 12/17] Lint --- test/types/set_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/set_test.rb b/test/types/set_test.rb index 9501baf..8c085de 100644 --- a/test/types/set_test.rb +++ b/test/types/set_test.rb @@ -134,7 +134,7 @@ class SetTest < ActiveSupport::TestCase sleep 0.7.seconds @set.add(%w[ 4 5 ]) - assert_equal [1, 2, 3, 4, 5], @set.members + assert_equal [ 1, 2, 3, 4, 5 ], @set.members sleep 0.5.seconds assert_equal [], @set.members From 70bd88b0fd683d320884cf5176225b7bac15b0e1 Mon Sep 17 00:00:00 2001 From: Benjamin Bock Date: Fri, 1 Mar 2024 23:11:51 +0100 Subject: [PATCH 13/17] Add list and set expiration to attributes --- lib/kredis/attributes.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/kredis/attributes.rb b/lib/kredis/attributes.rb index cd00319..2ef7cfa 100644 --- a/lib/kredis/attributes.rb +++ b/lib/kredis/attributes.rb @@ -44,20 +44,20 @@ def kredis_json(name, key: nil, default: nil, config: :shared, after_change: nil kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_list(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_list(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change, expires_in: expires_in end - def kredis_unique_list(name, limit: nil, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change + def kredis_unique_list(name, limit: nil, key: nil, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change, expires_in: expires_in end - def kredis_set(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_set(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change, expires_in: expires_in end - def kredis_ordered_set(name, limit: nil, default: nil, key: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change + def kredis_ordered_set(name, limit: nil, default: nil, key: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change, expires_in: expires_in end def kredis_slot(name, key: nil, config: :shared, after_change: nil) From 93aef02deaf40f2c7314211001d07abaa211baef Mon Sep 17 00:00:00 2001 From: Miller Date: Sun, 3 Mar 2024 23:48:24 +0900 Subject: [PATCH 14/17] Add some test --- test/attributes_test.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/attributes_test.rb b/test/attributes_test.rb index 6fa0b4e..33a0e5d 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -13,8 +13,10 @@ class Person kredis_list :names_with_custom_key_via_lambda, key: ->(p) { "person:#{p.id}:names_customized" } kredis_list :names_with_custom_key_via_method, key: :generate_key kredis_list :names_with_default_via_lambda, default: ->(p) { [ "Random", p.name ] } + kredis_list :names_with_ttl, expires_in: 1.second kredis_unique_list :skills, limit: 2 kredis_unique_list :skills_with_default_via_lambda, default: ->(p) { [ "Random", "Random", p.name ] } + kredis_unique_list :skills_with_ttl, expires_in: 1.second kredis_ordered_set :reading_list, limit: 2 kredis_flag :special kredis_flag :temporary_special, expires_in: 1.second @@ -34,6 +36,7 @@ class Person kredis_slots :meetings, available: 3 kredis_set :vacations kredis_set :vacations_with_default_via_lambda, default: ->(p) { JSON.parse(p.vacation_destinations).map { |location| location["city"] } } + kredis_set :vacations_with_ttl, expires_in: 1.second kredis_json :settings kredis_json :settings_with_default_via_lambda, default: ->(p) { JSON.parse(p.anthropometry).merge(eye_color: p.eye_color) } kredis_counter :amount @@ -148,6 +151,14 @@ class AttributesTest < ActiveSupport::TestCase assert_equal %w[ Random Jason ], Kredis.redis.lrange("people:8:names_with_default_via_lambda", 0, -1) end + test "list with ttl" do + @person.names_with_ttl.append(%w[ david kasper ]) + assert_equal %w[ david kasper ], @person.names_with_ttl.elements + + sleep 1.1 + assert_equal [], @person.names_with_ttl.elements + end + test "unique list" do @person.skills.prepend(%w[ trolling photography ]) @person.skills.prepend("racing") @@ -160,6 +171,14 @@ class AttributesTest < ActiveSupport::TestCase assert_equal %w[ Random Jason ], Kredis.redis.lrange("people:8:skills_with_default_via_lambda", 0, -1) end + test "unique list with ttl" do + @person.skills_with_ttl.prepend(%w[ trolling photography ]) + assert_equal %w[ trolling photography ].to_set, @person.skills_with_ttl.elements.to_set + + sleep 1.1 + assert_equal [], @person.skills_with_ttl.elements + end + test "ordered set" do @person.reading_list.prepend(%w[ rework shapeup remote ]) assert_equal %w[ remote shapeup ], @person.reading_list.elements @@ -324,6 +343,14 @@ class AttributesTest < ActiveSupport::TestCase assert_equal [ "Paris" ], Kredis.redis.smembers("people:8:vacations_with_default_via_lambda") end + test "set with ttl" do + @person.vacations_with_ttl.add "paris" + assert_equal [ "paris" ], @person.vacations_with_ttl.members + + sleep 1.1 + assert_equal [], @person.vacations_with_ttl.members + end + test "json" do @person.settings.value = { "color" => "red", "count" => 2 } assert_equal({ "color" => "red", "count" => 2 }, @person.settings.value) From 1ad81dacb301fbc5986100faa29e417ecf0ea18a Mon Sep 17 00:00:00 2001 From: Miller Date: Mon, 4 Mar 2024 00:00:29 +0900 Subject: [PATCH 15/17] Document expiration --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 66fcbf7..1f0164c 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,18 @@ limiter.poke # => SET limiter 0 NX + INCRBY limiter 1 false == limiter.exceeded? # => GET "limiter" ``` +Lists, unique lists, sets, and ordered sets support expiration: + +```ruby +set = Kredis.set "myset", expires_in: 1.second +set.add "hello", "world" # => SADD myset "hello" "world" +true == set.include?("hello") # => SISMEMBER myset "hello +sleep 2 +[] == set.members # => SMEMBERS myset +``` + +To support lower versions of redis, which does not has `nx` option on `EXPIRE` command, multiple commands are used to achieve the same effect. + ### Models You can use all these structures in models: @@ -189,6 +201,7 @@ class Person < ApplicationRecord kredis_unique_list :skills, limit: 2 kredis_enum :morning, values: %w[ bright blue black ], default: "bright" kredis_counter :steps, expires_in: 1.hour + kredis_set :favorite_colors, expires_in: 1.day private def generate_names_key From 48801cfe35d626697a77bcd4010d5458a49a8203 Mon Sep 17 00:00:00 2001 From: Miller Date: Mon, 4 Mar 2024 17:47:11 +0900 Subject: [PATCH 16/17] Test ordered set attribute with ttl --- test/attributes_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/attributes_test.rb b/test/attributes_test.rb index 33a0e5d..2eed8b7 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -18,6 +18,7 @@ class Person kredis_unique_list :skills_with_default_via_lambda, default: ->(p) { [ "Random", "Random", p.name ] } kredis_unique_list :skills_with_ttl, expires_in: 1.second kredis_ordered_set :reading_list, limit: 2 + kredis_ordered_set :reading_list_with_ttl, expires_in: 1.second kredis_flag :special kredis_flag :temporary_special, expires_in: 1.second kredis_string :address @@ -184,6 +185,14 @@ class AttributesTest < ActiveSupport::TestCase assert_equal %w[ remote shapeup ], @person.reading_list.elements end + test "ordered set with ttl" do + @person.reading_list_with_ttl.prepend(%w[ rework ]) + assert_equal %w[ rework ], @person.reading_list_with_ttl.elements + + sleep 1.1 + assert_equal [], @person.reading_list_with_ttl.elements + end + test "flag" do assert_not @person.special? From 2dc3745090d256b8919f7861e38fe81ee3a4f296 Mon Sep 17 00:00:00 2001 From: Miller Date: Sun, 31 Mar 2024 14:53:08 +0900 Subject: [PATCH 17/17] Support ttl in hash --- README.md | 2 +- lib/kredis/attributes.rb | 4 ++-- lib/kredis/types.rb | 4 ++-- lib/kredis/types/hash.rb | 5 ++++- test/attributes_test.rb | 9 +++++++++ test/types/hash_test.rb | 10 ++++++++++ 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1f0164c..cc236c3 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ limiter.poke # => SET limiter 0 NX + INCRBY limiter 1 false == limiter.exceeded? # => GET "limiter" ``` -Lists, unique lists, sets, and ordered sets support expiration: +Lists, unique lists, sets, ordered sets, and hashes support expiration: ```ruby set = Kredis.set "myset", expires_in: 1.second diff --git a/lib/kredis/attributes.rb b/lib/kredis/attributes.rb index 2ef7cfa..8f75db4 100644 --- a/lib/kredis/attributes.rb +++ b/lib/kredis/attributes.rb @@ -76,8 +76,8 @@ def kredis_limiter(name, limit:, key: nil, config: :shared, after_change: nil, e kredis_connection_with __method__, name, key, limit: limit, config: config, after_change: after_change, expires_in: expires_in end - def kredis_hash(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_hash(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change, expires_in: expires_in end def kredis_boolean(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) diff --git a/lib/kredis/types.rb b/lib/kredis/types.rb index 6fdedb0..1c8e855 100644 --- a/lib/kredis/types.rb +++ b/lib/kredis/types.rb @@ -57,8 +57,8 @@ def enum(key, values:, default:, config: :shared, after_change: nil) type_from(Enum, config, key, after_change: after_change, values: values, default: default) end - def hash(key, typed: :string, default: nil, config: :shared, after_change: nil) - type_from(Hash, config, key, after_change: after_change, default: default, typed: typed) + def hash(key, typed: :string, default: nil, config: :shared, after_change: nil, expires_in: nil) + type_from(Hash, config, key, after_change: after_change, default: default, typed: typed, expires_in: expires_in) end def list(key, default: nil, typed: :string, config: :shared, after_change: nil, expires_in: nil) diff --git a/lib/kredis/types/hash.rb b/lib/kredis/types/hash.rb index b441a19..01d22ed 100644 --- a/lib/kredis/types/hash.rb +++ b/lib/kredis/types/hash.rb @@ -4,6 +4,7 @@ class Kredis::Types::Hash < Kredis::Types::Proxying prepend Kredis::DefaultValues + include Kredis::Expiration proxying :hget, :hset, :hmget, :hdel, :hgetall, :hkeys, :hvals, :del, :exists? @@ -18,7 +19,9 @@ def []=(key, value) end def update(**entries) - hset entries.transform_values { |val| type_to_string(val, typed) }.compact if entries.flatten.any? + with_expiration do + hset entries.transform_values { |val| type_to_string(val, typed) }.compact if entries.flatten.any? + end end def values_at(*keys) diff --git a/test/attributes_test.rb b/test/attributes_test.rb index 2eed8b7..306ec66 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -46,6 +46,7 @@ class Person kredis_string :temporary_password, expires_in: 1.second kredis_hash :high_scores, typed: :integer kredis_hash :high_scores_with_default_via_lambda, typed: :integer, default: ->(p) { { high_score: JSON.parse(p.scores).max } } + kredis_hash :high_scores_with_ttl, typed: :integer, expires_in: 1.second kredis_boolean :onboarded kredis_boolean :adult_with_default_via_lambda, default: ->(p) { Date.today.year - p.birthdate.year >= 18 } kredis_limiter :update_limit, limit: 3, expires_in: 1.second @@ -404,6 +405,14 @@ class AttributesTest < ActiveSupport::TestCase assert_equal({ "high_score" => 28 }, @person.high_scores_with_default_via_lambda.to_h) end + test "hash with ttl" do + @person.high_scores_with_ttl.update(the_lost_viking: 99) + assert_equal({ "the_lost_viking" => 99 }, @person.high_scores_with_ttl.to_h) + + sleep 1.1 + assert_equal({}, @person.high_scores_with_ttl.to_h) + end + test "boolean" do @person.onboarded.value = true assert @person.onboarded.value diff --git a/test/types/hash_test.rb b/test/types/hash_test.rb index ef1f0de..6b2fd81 100644 --- a/test/types/hash_test.rb +++ b/test/types/hash_test.rb @@ -130,4 +130,14 @@ class HashTest < ActiveSupport::TestCase assert_nil @hash["key"] assert_equal "value2", @hash["key2"] end + + test "support ttl" do + @hash = Kredis.hash "myhash", expires_in: 1.second + @hash[:key] = :value + @hash.update("key2" => "value2", "key3" => "value3") + assert_equal "value", @hash[:key] + + sleep 1.1 + assert_equal [], @hash.keys + end end