fresh_redis 0.0.6 → 0.0.7

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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fresh_redis (0.0.6)
4
+ fresh_redis (0.0.7)
5
5
  redis
6
6
 
7
7
  GEM
@@ -16,7 +16,7 @@ GEM
16
16
  guard (>= 1.1)
17
17
  rspec (~> 2.11)
18
18
  listen (0.5.3)
19
- mock_redis (0.5.2)
19
+ mock_redis (0.5.4)
20
20
  rake (0.9.2.2)
21
21
  rb-fsevent (0.9.2)
22
22
  rb-inotify (0.8.8)
@@ -39,7 +39,7 @@ PLATFORMS
39
39
  DEPENDENCIES
40
40
  fresh_redis!
41
41
  guard-rspec (= 2.1.0)
42
- mock_redis (= 0.5.2)
42
+ mock_redis (= 0.5.4)
43
43
  rake (= 0.9.2.2)
44
44
  rb-fsevent
45
45
  rb-inotify
data/README.md CHANGED
@@ -26,6 +26,8 @@ Or install it yourself as:
26
26
 
27
27
  ### Simple counts
28
28
 
29
+ Increment a counter, but gradually expire portions of that counter. Useful for *"find count of X for the last Y minutes"* type queries, where some event occurs occasionally over a period of time.
30
+
29
31
  ```ruby
30
32
  require "redis"
31
33
  require "fresh_redis"
@@ -47,11 +49,32 @@ fresh.fsum "failed_login" # will return 2, cause the first incr has expired by n
47
49
 
48
50
  ### Hash operations
49
51
 
52
+ fresh_redis Hashes work similarly to existing Redis hashes, except that keys have individual expiry.
53
+
50
54
  ```ruby
51
- # TODO
52
- ```
55
+ require "redis"
56
+ require "fresh_redis"
57
+ fresh = FreshRedis.new(Redis.current)
53
58
 
54
- TODO note about handling of deletes/nil values and :force option on `fhdel` operation
59
+ fresh.fhset "temp_banned_users", "madlep", "too much cowbell"
60
+ # wait a bit
61
+ fresh.fhset "temp_banned_users", "normthegnome", "inappropriate office conduct"
62
+ fresh.fhset "temp_banned_users", "joeblogs", "suspected ninja"
63
+
64
+ # then straight away...
65
+ fresh.fhget "temp_banned_users", "madlep" # will return "threatening..."
66
+ fresh.fhget "temp_banned_users", "normthegnone" # will return "inappropriate..."
67
+ fresh.fhget "temp_banned_users", "joeblogs", # will return "suspected..."
68
+ fresh.fhgetall "temp_banned_users" # will return hash of {"madlep" => ..., "normthegnone" => ..., "joeblogs" => ...}
69
+
70
+ # wait for first fhset to expire
71
+
72
+ fresh.fhget "temp_banned_users", "madlep" # will return nil
73
+ fresh.fhget "temp_banned_users", "normthegnone" # will return "inappropriate..." - unchanged
74
+ fresh.fhget "temp_banned_users", "joeblogs", # will return "suspected..." - unchanged
75
+ fresh.fhgetall "temp_banned_users" # will return hash WITHOUT "madlep" as a key (just "normthegnone" and "joeblogs")
76
+
77
+ ```
55
78
 
56
79
  ### Tweaking _"freshness"_ and _"granularity"_.
57
80
 
@@ -59,14 +82,13 @@ Think of it like stock rotation at your local supermarket. Freshness is how long
59
82
 
60
83
  ```ruby
61
84
  # lets track douch users spamming the forum so we can do something about it...
85
+ fresh = FreshRedis.new(Redis.current, :freshness => 600, :granularity => 30)
62
86
 
63
87
  # store post count for a user for 10 minutes (600 seconds), in buckets of time duration 30 seconds
64
- fresh.fincr "recent_posts:#{user.id}", :freshness => 600, :granularity => 30
65
-
66
- # ...
88
+ fresh.fincr "recent_posts:#{user.id}"
67
89
 
68
- # note, need to pass in the SAME freshness and granularity options as fincr, so it can correclty lookup the correct keys
69
- fresh.fsum "recent_posts:#{user.id}", :freshness => 600, :granularity => 30
90
+ # do stuff
91
+ fresh.fsum "recent_posts:#{user.id}"
70
92
  ```
71
93
 
72
94
  # Recipes
data/fresh_redis.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.add_runtime_dependency 'redis'
19
19
 
20
20
  gem.add_development_dependency 'rspec'
21
- gem.add_development_dependency 'mock_redis', '0.5.2'
21
+ gem.add_development_dependency 'mock_redis', '0.5.4'
22
22
  gem.add_development_dependency 'guard-rspec', '2.1.0'
23
23
  gem.add_development_dependency 'rake', '0.9.2.2'
24
24
  gem.add_development_dependency 'timecop', '0.5.2'
@@ -2,7 +2,6 @@ require 'time'
2
2
 
3
3
  class FreshRedis
4
4
  class Key
5
- # TODO remove concept of time from a key. Just be about redis key, freshness, granularity
6
5
 
7
6
  DEFAULT_OPTIONS = {
8
7
  :freshness => 60 * 60, # 1 hour
@@ -1,13 +1,33 @@
1
1
  class FreshRedis
2
2
  module String
3
+ def fincrby(key, increment, options={})
4
+ key = build_key(key, options)
5
+ @redis.multi do
6
+ @redis.incrby(key.redis_key, increment)
7
+ @redis.expire(key.redis_key, key.freshness)
8
+ end
9
+ end
10
+
3
11
  def fincr(key, options={})
12
+ fincrby(key, 1, options)
13
+ end
14
+
15
+ def fincrbyfloat(key, increment, options={})
4
16
  key = build_key(key, options)
5
17
  @redis.multi do
6
- @redis.incr(key.redis_key)
18
+ @redis.incrbyfloat(key.redis_key, increment)
7
19
  @redis.expire(key.redis_key, key.freshness)
8
20
  end
9
21
  end
10
22
 
23
+ def fdecr(key, options={})
24
+ fincrby(key, -1, options)
25
+ end
26
+
27
+ def fdecrby(key, decrement, options={})
28
+ fincrby(key, -1 * decrement, options)
29
+ end
30
+
11
31
  def fsum(key, options={})
12
32
  key = build_key(key, options)
13
33
  @redis.pipelined {
@@ -15,7 +35,7 @@ class FreshRedis
15
35
  @redis.get(bucket_key)
16
36
  end
17
37
  }.reduce(0){|acc, value|
18
- value ? acc + value.to_i : acc
38
+ value ? acc + value.to_f : acc
19
39
  }
20
40
  end
21
41
  end
@@ -1,3 +1,3 @@
1
1
  class FreshRedis
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -7,6 +7,7 @@ describe FreshRedis do
7
7
  let(:mock_redis) { MockRedis.new }
8
8
  let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00") }
9
9
  let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00") }
10
+ let(:normalized_one_minute_ago) { Time.new(2012, 9, 27, 15, 39, 0, "+10:00") }
10
11
 
11
12
  before(:each) { Timecop.travel(now) }
12
13
  after(:each) { Timecop.return }
@@ -16,8 +17,9 @@ describe FreshRedis do
16
17
  it "should increment the key for the normalized timestamp" do
17
18
  Timecop.freeze(now) { subject.fincr "foo" }
18
19
  Timecop.freeze(now + 3) { subject.fincr "foo" }
19
- Timecop.freeze(now + 60) { subject.fincr "foo" } # different normalized key
20
+ Timecop.freeze(now - 60) { subject.fincr "foo" } # different normalized key
20
21
  mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == 2
22
+ mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == 1
21
23
  end
22
24
 
23
25
  it "should set the freshness as the expiry" do
@@ -27,21 +29,75 @@ describe FreshRedis do
27
29
  end
28
30
  end
29
31
 
32
+ describe "#fincrby" do
33
+ it "should increment the key for the normalized timestamp by the specified amount" do
34
+ Timecop.freeze(now) { subject.fincrby "foo", 2 }
35
+ Timecop.freeze(now + 3) { subject.fincrby "foo", 3 }
36
+ Timecop.freeze(now - 60) { subject.fincrby "foo", 4 } # different normalized key
37
+ mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == 5
38
+ mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == 4
39
+ end
40
+
41
+ it "should set the freshness as the expiry" do
42
+ # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
43
+ subject.fincrby "foo", 5, :freshness => 3600
44
+ mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
45
+ end
46
+ end
47
+
48
+ describe "#fincrbyfloat" do
49
+ it "should increment the key for the normalized timestamp by the specified amount" do
50
+ Timecop.freeze(now) { subject.fincrbyfloat "foo", 2.1 }
51
+ Timecop.freeze(now + 3) { subject.fincrbyfloat "foo", 3.2 }
52
+ Timecop.freeze(now - 60) { subject.fincrbyfloat "foo", 4.3 } # different normalized key
53
+ mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_f.should be_within(0.00001).of(5.3)
54
+ mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_f.should be_within(0.00001).of(4.3)
55
+ end
56
+
57
+ it "should set the freshness as the expiry" do
58
+ # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
59
+ subject.fincrbyfloat "foo", 5, :freshness => 3600
60
+ mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
61
+ end
62
+ end
63
+
64
+ describe "#fdecr" do
65
+ it "should decrement the key for the normalized timestamp" do
66
+ Timecop.freeze(now) { subject.fdecr "foo" }
67
+ Timecop.freeze(now + 3) { subject.fdecr "foo" }
68
+ Timecop.freeze(now - 60) { subject.fdecr "foo" } # different normalized key
69
+ mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == -2
70
+ mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == -1
71
+ end
72
+
73
+ it "should set the freshness as the expiry" do
74
+ # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
75
+ subject.fdecr "foo", :freshness => 3600
76
+ mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
77
+ end
78
+ end
79
+
80
+ describe "#fdecrby" do
81
+ it "should decrement the key for the normalized timestamp by the specified amount" do
82
+ Timecop.freeze(now) { subject.fdecrby "foo", 2 }
83
+ Timecop.freeze(now + 3) { subject.fdecrby "foo", 3 }
84
+ Timecop.freeze(now - 60) { subject.fdecrby "foo", 4 } # different normalized key
85
+ mock_redis.data["foo:#{normalized_now_minute.to_i}"].to_i.should == -5
86
+ mock_redis.data["foo:#{normalized_one_minute_ago.to_i}"].to_i.should == -4
87
+ end
88
+
89
+ it "should set the freshness as the expiry" do
90
+ # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
91
+ subject.fdecrby "foo", 5, :freshness => 3600
92
+ mock_redis.ttl("foo:#{normalized_now_minute.to_i}").should == 3600
93
+ end
94
+ end
95
+
30
96
  describe "#fsum" do
31
- subject{ FreshRedis.new(mock_redis, :granularity => 10, :freshness => 60) }
32
97
  it "should add the values of keys for specified freshness and granularity" do
33
- Timecop.freeze(now - 60 - 10) { subject.fincr "foo" }
34
- Timecop.freeze(now - 60 + 1) { subject.fincr "foo" }
35
- Timecop.freeze(now - 60 + 2) { subject.fincr "foo" }
36
- Timecop.freeze(now - 60 + 3) { subject.fincr "foo" }
37
- Timecop.freeze(now - 60 + 5) { subject.fincr "foo" }
38
- Timecop.freeze(now - 60 + 8) { subject.fincr "foo" }
39
- Timecop.freeze(now - 60 + 13) { subject.fincr "foo" }
40
- Timecop.freeze(now - 60 + 21) { subject.fincr "foo" }
41
- Timecop.freeze(now - 60 + 34) { subject.fincr "foo" }
42
- Timecop.freeze(now - 60 + 55) { subject.fincr "foo" }
43
-
44
- subject.fsum("foo").should == 9
98
+ mock_redis.set("foo:#{normalized_now_minute.to_i}", "7")
99
+ mock_redis.set("foo:#{normalized_one_minute_ago.to_i}", "-2")
100
+ subject.fsum("foo").should == 5
45
101
  end
46
102
  end
47
103
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fresh_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-17 00:00:00.000000000 Z
12
+ date: 2012-10-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 0.5.2
53
+ version: 0.5.4
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +58,7 @@ dependencies:
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.5.2
61
+ version: 0.5.4
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: guard-rspec
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -146,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
146
  version: '0'
147
147
  segments:
148
148
  - 0
149
- hash: -3713423432962146166
149
+ hash: 3765390037678342565
150
150
  required_rubygems_version: !ruby/object:Gem::Requirement
151
151
  none: false
152
152
  requirements:
@@ -155,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  version: '0'
156
156
  segments:
157
157
  - 0
158
- hash: -3713423432962146166
158
+ hash: 3765390037678342565
159
159
  requirements: []
160
160
  rubyforge_project:
161
161
  rubygems_version: 1.8.23