fresh_redis 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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