fresh_redis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format=nested
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fresh_redis.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fresh_redis (0.0.1)
5
+ redis
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ mock_redis (0.5.2)
12
+ redis (3.0.1)
13
+ rspec (2.11.0)
14
+ rspec-core (~> 2.11.0)
15
+ rspec-expectations (~> 2.11.0)
16
+ rspec-mocks (~> 2.11.0)
17
+ rspec-core (2.11.1)
18
+ rspec-expectations (2.11.3)
19
+ diff-lcs (~> 1.1.3)
20
+ rspec-mocks (2.11.3)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ fresh_redis!
27
+ mock_redis (= 0.5.2)
28
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Julian Doherty
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # FreshRedis
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'fresh_redis'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install fresh_redis
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path("../lib/fresh_redis/version", __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Julian Doherty (madlep)"]
5
+ gem.email = ["madlep@madlep.com"]
6
+ gem.description = %q{Aggregate, expiring, recent data in Redis}
7
+ gem.summary = <<-TEXT.strip
8
+ Use redis for working with recent temporal based data that can expiry gradually. Useful for things like "get a count all failed login attempts for the last hour"
9
+ TEXT
10
+ gem.homepage = ""
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.test_files = gem.files.grep(%r{^(spec)/})
14
+ gem.name = "fresh_redis"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = FreshRedis::VERSION
17
+
18
+ gem.add_runtime_dependency 'redis'
19
+
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'mock_redis', '0.5.2'
22
+ end
@@ -0,0 +1,17 @@
1
+ class FreshRedis
2
+ module Timestamp
3
+ def normalize_key(key, t, granularity)
4
+ [key, normalize_time(t, granularity)].join(":")
5
+ end
6
+
7
+ def normalize_time(t, granularity)
8
+ t - (t % granularity)
9
+ end
10
+
11
+ def range_timestamps(t, freshness, granularity)
12
+ from = normalize_time(t - freshness, granularity)
13
+ to = normalize_time(t, granularity)
14
+ (from..to).step(granularity)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ class FreshRedis
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,61 @@
1
+ require 'fresh_redis/timestamp'
2
+ require 'fresh_redis/version'
3
+
4
+ class FreshRedis
5
+ include Timestamp
6
+
7
+ VERSION = "0.0.1"
8
+
9
+ DEFAULT_OPTIONS = {
10
+ :freshness => 60 * 60, # 1 hour
11
+ :granularity => 1 * 60 # 1 minute
12
+ }
13
+
14
+ def initialize(redis)
15
+ @redis = redis
16
+ end
17
+
18
+ def fincr(key, options={})
19
+ options = default_options(options)
20
+ t = options[:t]
21
+ freshness = options[:freshness]
22
+ granularity = options[:granularity]
23
+
24
+ key = normalize_key(key, t, granularity)
25
+ @redis.multi do
26
+ @redis.incr key
27
+ @redis.expire key, freshness
28
+ end
29
+ end
30
+
31
+ def fsum(key, options={})
32
+ options = default_options(options)
33
+
34
+ reduce(key, options, 0){|acc, timestamp_total|
35
+ acc + timestamp_total.to_i
36
+ }
37
+ end
38
+
39
+ private
40
+ def reduce(key, options={}, initial=nil, &reduce_operation)
41
+ options = default_options(options)
42
+ t = options[:t]
43
+ freshness = options[:freshness]
44
+ granularity = options[:granularity]
45
+
46
+ raw_totals = @redis.pipelined {
47
+ range_timestamps(t, freshness, granularity).each do |timestamp|
48
+ timestamp_key = [key, timestamp].join(":")
49
+ @redis.get(timestamp_key)
50
+ end
51
+ }
52
+
53
+ raw_totals.reduce(initial, &reduce_operation)
54
+ end
55
+
56
+ def default_options(options)
57
+ options = DEFAULT_OPTIONS.merge(options)
58
+ options[:t] ||= Time.now.to_i
59
+ options
60
+ end
61
+ end
@@ -0,0 +1,49 @@
1
+ require 'fresh_redis'
2
+
3
+ describe FreshRedis::Timestamp do
4
+ subject { Class.new.extend(FreshRedis::Timestamp) }
5
+ let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00").to_i }
6
+ let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00").to_i }
7
+ let(:normalized_now_hour) { Time.new(2012, 9, 27, 15, 0, 0, "+10:00").to_i }
8
+
9
+ describe "#normalize_key" do
10
+ it "should append the normalized timestamp to the key" do
11
+ subject.normalize_key("foo", now, 60).should == "foo:#{normalized_now_minute}"
12
+ end
13
+ end
14
+
15
+ describe "#normalize_time" do
16
+ it "should round down timestamp to nearest multiple of granularity" do
17
+ subject.normalize_time(now, 60).should == normalized_now_minute
18
+ subject.normalize_time(now, 3600).should == normalized_now_hour
19
+ end
20
+
21
+ it "shouldn't change the timestamp if the granularity is 1" do
22
+ subject.normalize_time(now, 1).should == now
23
+ end
24
+ end
25
+
26
+ describe "#range_timestamps" do
27
+ let(:range) { subject.range_timestamps(now, 600, 60) }
28
+ it "should generate an enumerable over the range" do
29
+ range.should be_kind_of(Enumerable)
30
+ end
31
+
32
+ it "should have one timestamp for each granularity step in the fresh range" do
33
+ range.count.should == 11 # fence-posting. we include the first and last elements in a timestamp range split by granularity
34
+ end
35
+
36
+ it "should have the first timestamp as the maximum freshness" do
37
+ range.first.should == normalized_now_minute - 600
38
+ end
39
+
40
+ it "should have now as the maximum freshness" do
41
+ range.to_a.last.should == normalized_now_minute
42
+ end
43
+
44
+ it "should step through the normalized timestamps split up by granularity" do
45
+ range.each_with_index{|t, i| t.should == normalized_now_minute - 600 + i * 60 }
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,40 @@
1
+ require 'fresh_redis'
2
+ require 'mock_redis'
3
+
4
+ describe FreshRedis do
5
+ subject{ FreshRedis.new(mock_redis) }
6
+ let(:mock_redis) { MockRedis.new }
7
+ let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00").to_i }
8
+ let(:normalized_now_minute) { Time.new(2012, 9, 27, 15, 40, 0, "+10:00").to_i }
9
+
10
+ describe "#fincr" do
11
+ it "should increment the key for the normalized timestamp" do
12
+ subject.fincr "foo", :granularity => 60, :t => now
13
+ subject.fincr "foo", :granularity => 60, :t => now + 3
14
+ subject.fincr "foo", :granularity => 60, :t => now + 60 # different normalized key
15
+ mock_redis.data["foo:#{normalized_now_minute}"].to_i.should == 2
16
+ end
17
+
18
+ it "should set the freshness as the expiry" do
19
+ # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
20
+ subject.fincr "foo", :freshness => 3600, :t => now
21
+ mock_redis.ttl("foo:#{normalized_now_minute}").should == 3600
22
+ end
23
+ end
24
+
25
+ describe "#fsum" do
26
+ it "should add the values of keys for specified freshness and granularity" do
27
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 1
28
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 2
29
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 3
30
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 5
31
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 8
32
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 13
33
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 21
34
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 34
35
+ subject.fincr "foo", :freshness => 60, :granularity => 10, :t => now - 60 + 55
36
+
37
+ subject.fsum("foo", :freshness => 60, :granularity => 10, :t => now).should == 9
38
+ end
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fresh_redis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Julian Doherty (madlep)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-27 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: &70193817243680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70193817243680
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70193817243180 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70193817243180
36
+ - !ruby/object:Gem::Dependency
37
+ name: mock_redis
38
+ requirement: &70193817242380 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - =
42
+ - !ruby/object:Gem::Version
43
+ version: 0.5.2
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70193817242380
47
+ description: Aggregate, expiring, recent data in Redis
48
+ email:
49
+ - madlep@madlep.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rspec
56
+ - Gemfile
57
+ - Gemfile.lock
58
+ - LICENSE
59
+ - README.md
60
+ - Rakefile
61
+ - fresh_redis.gemspec
62
+ - lib/fresh_redis.rb
63
+ - lib/fresh_redis/timestamp.rb
64
+ - lib/fresh_redis/version.rb
65
+ - spec/fresh_redis/timestamp_spec.rb
66
+ - spec/fresh_redis_spec.rb
67
+ homepage: ''
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ segments:
80
+ - 0
81
+ hash: 107948125749487774
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ segments:
89
+ - 0
90
+ hash: 107948125749487774
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.10
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Use redis for working with recent temporal based data that can expiry gradually.
97
+ Useful for things like "get a count all failed login attempts for the last hour"
98
+ test_files:
99
+ - spec/fresh_redis/timestamp_spec.rb
100
+ - spec/fresh_redis_spec.rb