fresh_redis 0.0.4 → 0.0.5

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.4)
4
+ fresh_redis (0.0.5)
5
5
  redis
6
6
 
7
7
  GEM
@@ -16,6 +16,7 @@ GEM
16
16
  rspec (~> 2.11)
17
17
  listen (0.5.3)
18
18
  mock_redis (0.5.2)
19
+ rake (0.9.2.2)
19
20
  redis (3.0.2)
20
21
  rspec (2.11.0)
21
22
  rspec-core (~> 2.11.0)
@@ -34,4 +35,5 @@ DEPENDENCIES
34
35
  fresh_redis!
35
36
  guard-rspec (= 2.1.0)
36
37
  mock_redis (= 0.5.2)
38
+ rake (= 0.9.2.2)
37
39
  rspec
data/fresh_redis.gemspec CHANGED
@@ -20,4 +20,5 @@ Gem::Specification.new do |gem|
20
20
  gem.add_development_dependency 'rspec'
21
21
  gem.add_development_dependency 'mock_redis', '0.5.2'
22
22
  gem.add_development_dependency 'guard-rspec', '2.1.0'
23
+ gem.add_development_dependency 'rake', '0.9.2.2'
23
24
  end
@@ -1,38 +1,47 @@
1
1
  class FreshRedis
2
2
  module Hash
3
+
3
4
  def fhset(key, hash_key, value, options={})
4
- key = Key.build(key, options)
5
+ key = build_key(key, options)
5
6
  @redis.multi do
6
- @redis.hset(key.redis_key, hash_key, value)
7
+ @redis.hset(key.redis_key, hash_key, n(value))
7
8
  @redis.expire(key.redis_key, key.freshness)
8
9
  end
9
10
  end
10
11
 
11
12
  def fhget(key, hash_key, options={})
12
- key = Key.build(key, options)
13
- @redis.pipelined {
14
- key.timestamp_buckets.each do |bucket_key|
13
+ key = build_key(key, options)
14
+
15
+ bucket_values = @redis.pipelined {
16
+ key.timestamp_buckets.reverse.each do |bucket_key|
15
17
  @redis.hget(bucket_key, hash_key)
16
18
  end
17
- }.compact
19
+ }
20
+
21
+ # find the first non-nil value
22
+ most_recent_value = bucket_values.find{|e| e }
23
+
24
+ un_n(most_recent_value)
18
25
  end
19
26
 
20
27
  def fhgetall(key, options={})
21
- key = Key.build(key, options)
22
- @redis.pipelined {
28
+ key = build_key(key, options)
29
+
30
+ bucket_values = @redis.pipelined {
23
31
  key.timestamp_buckets.each do |bucket_key|
24
32
  @redis.hgetall(bucket_key)
25
33
  end
26
- }.reject { |hash| hash.count.zero? }
34
+ }
35
+
36
+ merged_values = bucket_values.inject({}){ |acc, bucket_hash|
37
+ acc.merge(bucket_hash)
38
+ }
39
+
40
+ merged_values.reject{ |key, value| n?(value) }
27
41
  end
28
42
 
29
43
  def fhdel(key, hash_key, options={})
30
- key = Key.build(key, options)
31
- @redis.pipelined do
32
- key.timestamp_buckets.each do |bucket_key|
33
- @redis.hdel(bucket_key, hash_key)
34
- end
35
- end
44
+ fhset(key, hash_key, nil, options)
36
45
  end
37
46
  end
38
47
  end
@@ -1,7 +1,7 @@
1
1
  class FreshRedis
2
2
  module String
3
3
  def fincr(key, options={})
4
- key = Key.build(key, options)
4
+ key = build_key(key, options)
5
5
  @redis.multi do
6
6
  @redis.incr(key.redis_key)
7
7
  @redis.expire(key.redis_key, key.freshness)
@@ -9,7 +9,7 @@ class FreshRedis
9
9
  end
10
10
 
11
11
  def fsum(key, options={})
12
- key = Key.build(key, options)
12
+ key = build_key(key, options)
13
13
  @redis.pipelined {
14
14
  key.timestamp_buckets.each do |bucket_key|
15
15
  @redis.get(bucket_key)
@@ -1,3 +1,3 @@
1
1
  class FreshRedis
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/fresh_redis.rb CHANGED
@@ -7,7 +7,28 @@ class FreshRedis
7
7
  include Hash
8
8
  include String
9
9
 
10
- def initialize(redis)
10
+ NIL_VALUE = "__FR_NIL__"
11
+
12
+ def initialize(redis, options={})
11
13
  @redis = redis
14
+ @options = options
15
+ end
16
+
17
+ def build_key(base_key, options={})
18
+ options = @options.merge(options)
19
+ Key.build(base_key, options)
20
+ end
21
+
22
+ private
23
+ def n(value)
24
+ value || NIL_VALUE
25
+ end
26
+
27
+ def un_n(value)
28
+ n?(value) ? nil : value
29
+ end
30
+
31
+ def n?(value)
32
+ value == NIL_VALUE
12
33
  end
13
34
  end
@@ -6,53 +6,107 @@ describe FreshRedis do
6
6
  let(:mock_redis) { MockRedis.new }
7
7
  let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00").to_i }
8
8
  let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00").to_i }
9
+ let(:normalized_one_minute_ago) { Time.new(2012, 9, 27, 15, 39, 0, "+10:00").to_i }
10
+ let(:normalized_two_minutes_ago) { Time.new(2012, 9, 27, 15, 38, 0, "+10:00").to_i }
11
+ let(:normalized_old) { Time.new(2012, 9, 27, 14, 38, 0, "+10:00").to_i }
9
12
 
10
13
  context "hash keys" do
11
14
 
12
15
  describe "#fhset" do
13
- it "should set a value for a key in a hash for the normalized timestamp" do
14
- subject.fhset "foo", "bar", "value", :granularity => 60, :t => now
15
- subject.fhset "foo", "bar", "newer_value", :granularity => 60, :t => now + 3
16
- subject.fhset "foo", "bar", "different_bucket", :granularity => 60, :t => now + 60 # different normalized key
16
+ it "sets a value for a key in a hash for the normalized timestamp" do
17
+ subject.fhset "foo", "bar", "value", :t => now - 3
18
+ subject.fhset "foo", "bar", "newer_value", :t => now
19
+ subject.fhset "foo", "bar", "different_bucket", :t => now - 60 # different normalized key
20
+
17
21
  mock_redis.data["foo:#{normalized_now_minute}"].should == {"bar" => "newer_value"}
18
22
  end
19
23
 
20
- it "should set the freshness as the expiry" do
24
+ it "sets a placeholder value if nil is set as the value" do
25
+ subject.fhset "foo", "bar", nil, :t => now
26
+
27
+ mock_redis.data["foo:#{normalized_now_minute}"].should == {"bar" => FreshRedis::NIL_VALUE }
28
+ end
29
+
30
+ it "sets the freshness as the expiry" do
21
31
  # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
22
32
  subject.fhset "foo", "bar", "baz", :freshness => 3600, :t => now
33
+
23
34
  mock_redis.ttl("foo:#{normalized_now_minute}").should == 3600
24
35
  end
25
36
  end
26
37
 
27
38
  describe "#fhdel" do
28
- it "should remove a value for a key in a hash for the normalized timestamp" do
29
- subject.fhset "foo", "bar", "value", :granularity => 10, :freshness => 20, :t => now - 15
30
- subject.fhset "foo", "bar", "different_bucket", :granularity => 10, :freshness => 20, :t => now
31
- subject.fhdel "foo", "bar", :granularity => 10, :freshness => 0, :t => now # Should only delete the most recent bucket
32
- subject.fhget("foo", "bar", :granularity => 10, :freshness => 20, :t => now ).should == ["value"]
39
+ it "sets the value for a key in a hash for the normalized timestamp to be placeholder nil" do
40
+ subject.fhset "foo", "bar", "value", :t => now
41
+ subject.fhset "foo", "bar", "different_bucket", :t => now - 60
42
+
43
+ subject.fhdel "foo", "bar", :t => now # Should only change the most recent bucket
44
+
45
+ mock_redis.data["foo:#{normalized_now_minute}"].should == { "bar" => FreshRedis::NIL_VALUE }
46
+ mock_redis.data["foo:#{normalized_one_minute_ago}"].should == { "bar" => "different_bucket" }
33
47
  end
34
48
  end
35
49
 
36
50
  describe "#fhget" do
37
- it "should get all the values of the specified key in specified hash for specified freshness and granularity" do
38
- subject.fhset "requests", "some_key", "0", :freshness => 60, :granularity => 10, :t => now - 60 - 10 # Too old of a bucket
39
- subject.fhset "requests", "some_key", "1", :freshness => 60, :granularity => 10, :t => now - 60 + 5
40
- subject.fhset "requests", "some_key", "2", :freshness => 60, :granularity => 10, :t => now - 60 + 15
41
- subject.fhset "requests", "some_key", "3", :freshness => 60, :granularity => 10, :t => now - 60 + 16 # This overwrites the previous value in the bucket
42
- subject.fhget("requests", "some_key", :freshness => 60, :granularity => 10, :t => now).should == ["1", "3"]
51
+ it "gets the most recent value for the field across timestamped buckets" do
52
+ mock_redis.hset "foo:#{normalized_now_minute}", "notbar", "francis"
53
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "bar", "bill"
54
+ mock_redis.hset "foo:#{normalized_two_minutes_ago}", "bar", "louis"
55
+
56
+ subject.fhget("foo", "bar", :t => now).should == "bill"
57
+ end
58
+
59
+ it "returns nil if the most recent value is the nil placeholder" do
60
+ mock_redis.hset "foo:#{normalized_now_minute}", "notbar", "francis"
61
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "bar", FreshRedis::NIL_VALUE
62
+ mock_redis.hset "foo:#{normalized_two_minutes_ago}", "bar", "louis"
63
+
64
+ subject.fhget("foo", "bar", :t => now).should be_nil
65
+ end
66
+
67
+ it "returns the most recent value if a nil placeholder value in an earlier bucket has been overwritten in a later bucket" do
68
+ mock_redis.hset "foo:#{normalized_now_minute}", "bar", "francis"
69
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "bar", FreshRedis::NIL_VALUE
70
+ mock_redis.hset "foo:#{normalized_two_minutes_ago}", "bar", "louis"
71
+
72
+ subject.fhget("foo", "bar", :t => now).should == "francis"
73
+ end
74
+
75
+ it "returns nil if value is not found" do
76
+ subject.fhget("foo", "bar", :t => now).should be_nil
77
+ end
78
+
79
+ it "returns nil if the value is in a bucket that has expired" do
80
+ # this should be handled by redis expiry anyway, but verify code is behaving as expected and not querying more data than needed
81
+ mock_redis.hset "foo:#{normalized_old}", "bar", "louis"
82
+ subject.fhget("foo", "bar", :t => now).should be_nil
43
83
  end
44
84
  end
45
85
 
46
86
  describe "#fhgetall" do
47
- it "should get all the values of the specified hash for specified freshness and granularity" do
48
- subject.fhset "requests", "some_key", "0", :freshness => 60, :granularity => 10, :t => now - 60 - 10 # Too old of a bucket
49
- subject.fhset "requests", "some_key", "1", :freshness => 60, :granularity => 10, :t => now - 60 + 5
50
- subject.fhset "requests", "some_key", "2", :freshness => 60, :granularity => 10, :t => now - 60 + 15
51
- subject.fhset "requests", "another_key", "3", :freshness => 60, :granularity => 10, :t => now - 60 + 16 # This overwrites the previous value in the bucket
52
- subject.fhgetall("requests", :freshness => 60, :granularity => 10, :t => now).should == [
53
- {"some_key" => "1"},
54
- {"some_key" => "2", "another_key" => "3"}
55
- ]
87
+ it "merges the values for all keys across timestamp buckets" do
88
+ mock_redis.hset "foo:#{normalized_now_minute}", "bar", "francis"
89
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "baz", "zoey"
90
+ mock_redis.hset "foo:#{normalized_two_minutes_ago}", "boz", "louis"
91
+
92
+ subject.fhgetall("foo", :t => now).should == { "bar" => "francis", "baz" => "zoey", "boz" => "louis" }
93
+ end
94
+
95
+ it "removes keys that have a nil placeholder value as the most recent value" do
96
+ mock_redis.hset "foo:#{normalized_now_minute}", "bar", FreshRedis::NIL_VALUE
97
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "bar", "zoey"
98
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "baz", "louis"
99
+
100
+ subject.fhgetall("foo", :t => now).should == { "baz" => "louis" }
101
+ end
102
+
103
+ it "returns the most recent value if a nil placeholder value in an earlier bucket has been overwritten in a later bucket" do
104
+ mock_redis.hset "foo:#{normalized_now_minute}", "bar", "francis"
105
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "bar", FreshRedis::NIL_VALUE
106
+ mock_redis.hset "foo:#{normalized_two_minutes_ago}", "bar", "louis"
107
+ mock_redis.hset "foo:#{normalized_one_minute_ago}", "baz", "bill"
108
+
109
+ subject.fhgetall("foo", :t => now).should == { "bar" => "francis", "baz" => "bill" }
56
110
  end
57
111
  end
58
112
  end
@@ -3,7 +3,6 @@ require 'fresh_redis'
3
3
  describe FreshRedis::Key do
4
4
  let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00").to_i }
5
5
  let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00").to_i }
6
- let(:normalized_now_hour) { Time.new(2012, 9, 27, 15, 0, 0, "+10:00").to_i }
7
6
 
8
7
  describe ".build" do
9
8
  it "complains if no args" do
@@ -10,9 +10,9 @@ describe FreshRedis do
10
10
  context "string keys" do
11
11
  describe "#fincr" do
12
12
  it "should increment the key for the normalized timestamp" do
13
- subject.fincr "foo", :granularity => 60, :t => now
14
- subject.fincr "foo", :granularity => 60, :t => now + 3
15
- subject.fincr "foo", :granularity => 60, :t => now + 60 # different normalized key
13
+ subject.fincr "foo", :t => now
14
+ subject.fincr "foo", :t => now + 3
15
+ subject.fincr "foo", :t => now + 60 # different normalized key
16
16
  mock_redis.data["foo:#{normalized_now_minute}"].to_i.should == 2
17
17
  end
18
18
 
@@ -24,19 +24,20 @@ describe FreshRedis do
24
24
  end
25
25
 
26
26
  describe "#fsum" do
27
+ subject{ FreshRedis.new(mock_redis, :granularity => 10, :freshness => 60) }
27
28
  it "should add the values of keys for specified freshness and granularity" do
28
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 - 10
29
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 1
30
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 2
31
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 3
32
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 5
33
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 8
34
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 13
35
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 21
36
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 34
37
- subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 55
29
+ subject.fincr "foo", :t => now - 60 - 10
30
+ subject.fincr "foo", :t => now - 60 + 1
31
+ subject.fincr "foo", :t => now - 60 + 2
32
+ subject.fincr "foo", :t => now - 60 + 3
33
+ subject.fincr "foo", :t => now - 60 + 5
34
+ subject.fincr "foo", :t => now - 60 + 8
35
+ subject.fincr "foo", :t => now - 60 + 13
36
+ subject.fincr "foo", :t => now - 60 + 21
37
+ subject.fincr "foo", :t => now - 60 + 34
38
+ subject.fincr "foo", :t => now - 60 + 55
38
39
 
39
- subject.fsum("foo", :freshness => 60, :granularity => 10, :t => now).should == 9
40
+ subject.fsum("foo", :t => now).should == 9
40
41
  end
41
42
  end
42
43
 
@@ -2,4 +2,26 @@ require 'fresh_redis'
2
2
  require 'mock_redis'
3
3
 
4
4
  describe FreshRedis do
5
+ let(:mock_redis) { MockRedis.new }
6
+
7
+ describe "#build_key" do
8
+ it "builds a new key based on custom options" do
9
+ key = "key"
10
+ FreshRedis::Key.should_receive(:build).with("foo", :granularity => 111, :freshness => 222).and_return(key)
11
+
12
+ fresh_redis = FreshRedis.new(mock_redis, :granularity => 111, :freshness => 222)
13
+
14
+ fresh_redis.build_key("foo").should == key
15
+ end
16
+
17
+ it "builds a new key no options if custom options not provided" do
18
+ key = "key"
19
+ FreshRedis::Key.should_receive(:build).with("foo", {}).and_return(key)
20
+
21
+ fresh_redis = FreshRedis.new(mock_redis)
22
+
23
+ fresh_redis.build_key("foo").should == key
24
+ end
25
+
26
+ end
5
27
  end
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.4
4
+ version: 0.0.5
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-14 00:00:00.000000000 Z
12
+ date: 2012-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -75,6 +75,22 @@ dependencies:
75
75
  - - '='
76
76
  - !ruby/object:Gem::Version
77
77
  version: 2.1.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - '='
84
+ - !ruby/object:Gem::Version
85
+ version: 0.9.2.2
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - '='
92
+ - !ruby/object:Gem::Version
93
+ version: 0.9.2.2
78
94
  description: Aggregate, expiring, recent data in Redis
79
95
  email:
80
96
  - madlep@madlep.com
@@ -112,12 +128,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
128
  - - ! '>='
113
129
  - !ruby/object:Gem::Version
114
130
  version: '0'
131
+ segments:
132
+ - 0
133
+ hash: -1443868887867383191
115
134
  required_rubygems_version: !ruby/object:Gem::Requirement
116
135
  none: false
117
136
  requirements:
118
137
  - - ! '>='
119
138
  - !ruby/object:Gem::Version
120
139
  version: '0'
140
+ segments:
141
+ - 0
142
+ hash: -1443868887867383191
121
143
  requirements: []
122
144
  rubyforge_project:
123
145
  rubygems_version: 1.8.23