weathercock 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Steepfile +13 -0
- data/lib/weathercock/key_builder.rb +3 -2
- data/lib/weathercock/scorer.rb +16 -8
- data/lib/weathercock/version.rb +1 -1
- data/sig/weathercock/key_builder.rbs +13 -0
- data/sig/weathercock/scorable.rbs +25 -0
- data/sig/weathercock/scorer.rbs +20 -0
- data/sig/weathercock.rbs +15 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6f2f450b510bee3dbea48bf0b4e7e37ef24040d82141caaabe45d6dce3c3cb6
|
|
4
|
+
data.tar.gz: ab78b22090a84493d97b3f5deca74963f150d3ea21af0b47408babb64fdb7985
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e10e7fee6c2e629c994c097920af3226a3f6fdde65a18aed9a85b5556ae6d0a4b4abf78029b833d50d88581745f2517911177ebaee78bbe7de6aef2c78eacb49
|
|
7
|
+
data.tar.gz: 4a2089ecaceb03a54cb1462504f9b946145b81837db87c4a497650164180c56533246f851ab0c1e1c8f84a337f06faf767c782960e8a9ac6273149f0e69f5766
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.2.0 - 2026-09-16
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `union` now reuses an existing destination key instead of always recomputing it, so its 900 second TTL works as a cache
|
|
8
|
+
- `union_dest` now includes `decay_factor` so decayed and non-decayed results no longer share a cache entry
|
|
9
|
+
|
|
3
10
|
## v1.1.0 - 2026-04-17
|
|
4
11
|
|
|
5
12
|
### Added
|
data/Steepfile
ADDED
|
@@ -38,8 +38,9 @@ module Weathercock
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def union_dest(base, type, count)
|
|
42
|
-
"#{base}:top:#{type}:#{count}"
|
|
41
|
+
def union_dest(base, type, count, decay_factor: nil)
|
|
42
|
+
dest = "#{base}:top:#{type}:#{count}"
|
|
43
|
+
decay_factor ? "#{dest}:decay:#{decay_factor}" : dest
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
end
|
data/lib/weathercock/scorer.rb
CHANGED
|
@@ -74,7 +74,9 @@ module Weathercock
|
|
|
74
74
|
base = @key_builder.base(event)
|
|
75
75
|
dest = window.empty? ? @key_builder.total(base) : union(event, window)
|
|
76
76
|
scores = @redis.call("ZMSCORE", dest, *ids.map(&:to_s))
|
|
77
|
-
ids.zip(scores).to_h
|
|
77
|
+
ids.zip(scores).to_h do |id, score|
|
|
78
|
+
[id.to_s, Integer(score || "0")] #: [String, Integer]
|
|
79
|
+
end
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
private
|
|
@@ -82,13 +84,19 @@ module Weathercock
|
|
|
82
84
|
def union(event, window, decay_factor: nil)
|
|
83
85
|
base = @key_builder.base(event)
|
|
84
86
|
type, count = window.first
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@redis.call(
|
|
87
|
+
dest = if decay_factor
|
|
88
|
+
@key_builder.union_dest(base, type, count, decay_factor: decay_factor)
|
|
89
|
+
else
|
|
90
|
+
@key_builder.union_dest(base, type, count)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
unless @redis.call("EXISTS", dest) == 1
|
|
94
|
+
keys = @key_builder.window_keys(base, type, count)
|
|
95
|
+
weights = decay_factor ? count.times.map { |i| (decay_factor**i).round(10) } : nil
|
|
96
|
+
zunionstore_args = ["ZUNIONSTORE", dest, keys.size, *keys]
|
|
97
|
+
zunionstore_args += ["WEIGHTS", *weights] if weights
|
|
98
|
+
@redis.call(*zunionstore_args)
|
|
99
|
+
end
|
|
92
100
|
@redis.call("EXPIRE", dest, 900)
|
|
93
101
|
dest
|
|
94
102
|
end
|
data/lib/weathercock/version.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Weathercock::KeyBuilder
|
|
2
|
+
def initialize: (namespace: String, klass: Module) -> void
|
|
3
|
+
|
|
4
|
+
def base: (Symbol) -> String
|
|
5
|
+
|
|
6
|
+
def total: (String) -> String
|
|
7
|
+
|
|
8
|
+
def bucket: (String, :hours | :days | :months, Date | Time) -> String
|
|
9
|
+
|
|
10
|
+
def window_keys: (String, :hours | :days | :months, Integer) -> Array[String]
|
|
11
|
+
|
|
12
|
+
def union_dest: (String, :hours | :days | :months, Integer, ?decay_factor: Float) -> String
|
|
13
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Weathercock::Scorable
|
|
2
|
+
def self.included: (Module) -> void
|
|
3
|
+
# steep infers self.class in instance methods as singleton(Scorable),
|
|
4
|
+
# so declare weathercock_scorer here to satisfy the type checker.
|
|
5
|
+
def self.weathercock_scorer: () -> Weathercock::Scorer
|
|
6
|
+
|
|
7
|
+
# Expected to be provided by the including class (e.g. ActiveRecord).
|
|
8
|
+
def id: () -> Weathercock::id
|
|
9
|
+
|
|
10
|
+
def hit: (Symbol, ?increment: Integer) -> void
|
|
11
|
+
|
|
12
|
+
def hit_count: (Symbol, ?hours: Integer, ?days: Integer, ?months: Integer) -> Integer
|
|
13
|
+
|
|
14
|
+
def rank: (Symbol, ?hours: Integer, ?days: Integer, ?months: Integer) -> Integer?
|
|
15
|
+
|
|
16
|
+
def remove_hits: (Symbol) -> void
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Weathercock::Scorable::ClassMethods : Module
|
|
20
|
+
def weathercock_scorer: () -> Weathercock::Scorer
|
|
21
|
+
|
|
22
|
+
def top: (Symbol, limit: Integer?, ?decay_factor: Float, ?hours: Integer, ?days: Integer, ?months: Integer) -> Array[String]
|
|
23
|
+
|
|
24
|
+
def hit_counts: (Symbol, ids: Array[Weathercock::id], ?hours: Integer, ?days: Integer, ?months: Integer) -> Hash[String, Integer]
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Weathercock::Scorer
|
|
2
|
+
BUCKET_COUNT: Integer
|
|
3
|
+
BUCKET_TTLS: Hash[:hours | :days | :months, Integer]
|
|
4
|
+
|
|
5
|
+
def initialize: (klass: Module) -> void
|
|
6
|
+
|
|
7
|
+
def hit: (Weathercock::id, Symbol, ?increment: Integer) -> void
|
|
8
|
+
|
|
9
|
+
def hit_count: (Weathercock::id, Symbol, ?hours: Integer, ?days: Integer, ?months: Integer) -> Integer
|
|
10
|
+
|
|
11
|
+
def union: (Symbol, Hash[untyped, untyped], ?decay_factor: Float) -> String
|
|
12
|
+
|
|
13
|
+
def top: (Symbol, limit: Integer?, ?decay_factor: Float, ?hours: Integer, ?days: Integer, ?months: Integer) -> Array[String]
|
|
14
|
+
|
|
15
|
+
def hit_counts: (Symbol, ids: Array[Weathercock::id], ?hours: Integer, ?days: Integer, ?months: Integer) -> Hash[String, Integer]
|
|
16
|
+
|
|
17
|
+
def rank: (Weathercock::id, Symbol, ?hours: Integer, ?days: Integer, ?months: Integer) -> Integer?
|
|
18
|
+
|
|
19
|
+
def remove_hits: (Weathercock::id, Symbol) -> void
|
|
20
|
+
end
|
data/sig/weathercock.rbs
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
module Weathercock
|
|
2
2
|
VERSION: String
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
type id = Integer | String
|
|
5
|
+
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Config
|
|
10
|
+
attr_accessor namespace: String
|
|
11
|
+
attr_accessor redis: untyped
|
|
12
|
+
|
|
13
|
+
def initialize: () -> void
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.configure: () { (Config) -> void } -> void
|
|
17
|
+
def self.config: () -> Config
|
|
4
18
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: weathercock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Takafumi ONAKA
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- LICENSE.txt
|
|
50
50
|
- README.md
|
|
51
51
|
- Rakefile
|
|
52
|
+
- Steepfile
|
|
52
53
|
- compose.yaml
|
|
53
54
|
- lib/weathercock.rb
|
|
54
55
|
- lib/weathercock/key_builder.rb
|
|
@@ -56,6 +57,9 @@ files:
|
|
|
56
57
|
- lib/weathercock/scorer.rb
|
|
57
58
|
- lib/weathercock/version.rb
|
|
58
59
|
- sig/weathercock.rbs
|
|
60
|
+
- sig/weathercock/key_builder.rbs
|
|
61
|
+
- sig/weathercock/scorable.rbs
|
|
62
|
+
- sig/weathercock/scorer.rbs
|
|
59
63
|
homepage: https://github.com/onk/weathercock
|
|
60
64
|
licenses:
|
|
61
65
|
- MIT
|
|
@@ -77,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
81
|
- !ruby/object:Gem::Version
|
|
78
82
|
version: '0'
|
|
79
83
|
requirements: []
|
|
80
|
-
rubygems_version: 4.0.
|
|
84
|
+
rubygems_version: 4.0.16
|
|
81
85
|
specification_version: 4
|
|
82
86
|
summary: Hit counter and popularity tracking using Valkey/Redis Sorted Sets
|
|
83
87
|
test_files: []
|