fresh_redis 0.0.1 → 0.0.4
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 +11 -2
 - data/Guardfile +6 -0
 - data/README.md +47 -3
 - data/fresh_redis.gemspec +1 -0
 - data/lib/fresh_redis/hash.rb +38 -0
 - data/lib/fresh_redis/key.rb +56 -0
 - data/lib/fresh_redis/string.rb +22 -0
 - data/lib/fresh_redis/version.rb +1 -1
 - data/lib/fresh_redis.rb +5 -53
 - data/spec/fresh_redis/hash_spec.rb +60 -0
 - data/spec/fresh_redis/key_spec.rb +64 -0
 - data/spec/fresh_redis/string_spec.rb +44 -0
 - data/spec/fresh_redis_spec.rb +0 -35
 - metadata +51 -19
 - data/lib/fresh_redis/timestamp.rb +0 -17
 - data/spec/fresh_redis/timestamp_spec.rb +0 -49
 
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,15 +1,22 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                fresh_redis (0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
                fresh_redis (0.0.4)
         
     | 
| 
       5 
5 
     | 
    
         
             
                  redis
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            GEM
         
     | 
| 
       8 
8 
     | 
    
         
             
              remote: https://rubygems.org/
         
     | 
| 
       9 
9 
     | 
    
         
             
              specs:
         
     | 
| 
       10 
10 
     | 
    
         
             
                diff-lcs (1.1.3)
         
     | 
| 
      
 11 
     | 
    
         
            +
                guard (1.4.0)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  listen (>= 0.4.2)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  thor (>= 0.14.6)
         
     | 
| 
      
 14 
     | 
    
         
            +
                guard-rspec (2.1.0)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  guard (>= 1.1)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  rspec (~> 2.11)
         
     | 
| 
      
 17 
     | 
    
         
            +
                listen (0.5.3)
         
     | 
| 
       11 
18 
     | 
    
         
             
                mock_redis (0.5.2)
         
     | 
| 
       12 
     | 
    
         
            -
                redis (3.0. 
     | 
| 
      
 19 
     | 
    
         
            +
                redis (3.0.2)
         
     | 
| 
       13 
20 
     | 
    
         
             
                rspec (2.11.0)
         
     | 
| 
       14 
21 
     | 
    
         
             
                  rspec-core (~> 2.11.0)
         
     | 
| 
       15 
22 
     | 
    
         
             
                  rspec-expectations (~> 2.11.0)
         
     | 
| 
         @@ -18,11 +25,13 @@ GEM 
     | 
|
| 
       18 
25 
     | 
    
         
             
                rspec-expectations (2.11.3)
         
     | 
| 
       19 
26 
     | 
    
         
             
                  diff-lcs (~> 1.1.3)
         
     | 
| 
       20 
27 
     | 
    
         
             
                rspec-mocks (2.11.3)
         
     | 
| 
      
 28 
     | 
    
         
            +
                thor (0.16.0)
         
     | 
| 
       21 
29 
     | 
    
         | 
| 
       22 
30 
     | 
    
         
             
            PLATFORMS
         
     | 
| 
       23 
31 
     | 
    
         
             
              ruby
         
     | 
| 
       24 
32 
     | 
    
         | 
| 
       25 
33 
     | 
    
         
             
            DEPENDENCIES
         
     | 
| 
       26 
34 
     | 
    
         
             
              fresh_redis!
         
     | 
| 
      
 35 
     | 
    
         
            +
              guard-rspec (= 2.1.0)
         
     | 
| 
       27 
36 
     | 
    
         
             
              mock_redis (= 0.5.2)
         
     | 
| 
       28 
37 
     | 
    
         
             
              rspec
         
     | 
    
        data/Guardfile
    ADDED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,6 +1,12 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #  
     | 
| 
      
 1 
     | 
    
         
            +
            # fresh\_redis
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            Redis is great for managing data that expires on atomically (like caches). However, for data that expires gradually over time, built in commands don't get you all the way. 
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            For instance, how would you calculate _"count of login failures and successes for the last hour"_? The problem is while you can keep a count using a simple `incr` operation, you have to expire the entire total all at once, or not at all.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            A common solution is to split the data up into buckets, say one for each minute, each with their own expiry. You write to the current bucket, set the expiry, then allow it to naturally expire and drop out of your result set over time. To obtain the total value, you `get` all the bucket values, and aggregate the values in some fashion.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            That's pretty much what fresh\_redis does, except with less boilerplate, and a little more flexibility.
         
     | 
| 
       4 
10 
     | 
    
         | 
| 
       5 
11 
     | 
    
         
             
            ## Installation
         
     | 
| 
       6 
12 
     | 
    
         | 
| 
         @@ -18,7 +24,42 @@ Or install it yourself as: 
     | 
|
| 
       18 
24 
     | 
    
         | 
| 
       19 
25 
     | 
    
         
             
            ## Usage
         
     | 
| 
       20 
26 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
            ### Simple usage
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 30 
     | 
    
         
            +
            require "redis"
         
     | 
| 
      
 31 
     | 
    
         
            +
            require "fresh_redis"
         
     | 
| 
      
 32 
     | 
    
         
            +
            fresh = FreshRedis.new(Redis.current)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            fresh.fincr "failed_login"
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            # wait a bit...
         
     | 
| 
      
 37 
     | 
    
         
            +
            fresh.fincr "failed_login"
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            # then straight away...
         
     | 
| 
      
 40 
     | 
    
         
            +
            fresh.fincr "failed_login"
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            fresh.fsum "failed_login" # will return 3
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            # wait for the first incr to expire...
         
     | 
| 
      
 45 
     | 
    
         
            +
            fresh.fsum "failed_login" # will return 2, cause the first incr has expired by now
         
     | 
| 
      
 46 
     | 
    
         
            +
            ```
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            ### Tweaking _"freshness"_ and _"granularity"_. 
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            Think of it like stock rotation at your local supermarket. Freshness is how long we'll keep food around for before throwing it out, granularity is what batches we'll throw old food out together as. Something like _"we'll keep food around for a week, but we'll throw out everything for the same day at the same time."_ This is a performance trade off. Smaller granularity means more precise expiration of data, at the expense of having to store, retrieve, and check more buckets of data to get the aggregate value.
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 53 
     | 
    
         
            +
            # lets track douch users spamming the forum so we can do something about it...
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            # store post count for a user for 10 minutes (600 seconds), in buckets of time duration 30 seconds
         
     | 
| 
      
 56 
     | 
    
         
            +
            fresh.fincr "recent_posts:#{user.id}", :freshness => 600, :granularity => 30
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            # ...
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            # note, need to pass in the SAME freshness and granularity options as fincr, so it can correclty lookup the correct keys
         
     | 
| 
      
 61 
     | 
    
         
            +
            fresh.fsum "recent_posts:#{user.id}", :freshness => 600, :granularity => 30
         
     | 
| 
      
 62 
     | 
    
         
            +
            ```
         
     | 
| 
       22 
63 
     | 
    
         | 
| 
       23 
64 
     | 
    
         
             
            ## Contributing
         
     | 
| 
       24 
65 
     | 
    
         | 
| 
         @@ -27,3 +68,6 @@ TODO: Write usage instructions here 
     | 
|
| 
       27 
68 
     | 
    
         
             
            3. Commit your changes (`git commit -am 'Added some feature'`)
         
     | 
| 
       28 
69 
     | 
    
         
             
            4. Push to the branch (`git push origin my-new-feature`)
         
     | 
| 
       29 
70 
     | 
    
         
             
            5. Create new Pull Request
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            ## Who the hell?
         
     | 
| 
      
 73 
     | 
    
         
            +
            I blame [@madlep](http://twitter.com/madlep) aka Julian Doherty. Send hate mail to [madlep@madlep.com](mailto:madlep@madlep.com), or deface [madlep.com](http://madlep.com) in protest
         
     | 
    
        data/fresh_redis.gemspec
    CHANGED
    
    
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class FreshRedis
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Hash
         
     | 
| 
      
 3 
     | 
    
         
            +
                def fhset(key, hash_key, value, options={})
         
     | 
| 
      
 4 
     | 
    
         
            +
                  key = Key.build(key, options)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @redis.multi do
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @redis.hset(key.redis_key, hash_key, value)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @redis.expire(key.redis_key, key.freshness)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def fhget(key, hash_key, options={})
         
     | 
| 
      
 12 
     | 
    
         
            +
                  key = Key.build(key, options)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @redis.pipelined {
         
     | 
| 
      
 14 
     | 
    
         
            +
                    key.timestamp_buckets.each do |bucket_key|
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @redis.hget(bucket_key, hash_key)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  }.compact
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def fhgetall(key, options={})
         
     | 
| 
      
 21 
     | 
    
         
            +
                  key = Key.build(key, options)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @redis.pipelined {
         
     | 
| 
      
 23 
     | 
    
         
            +
                    key.timestamp_buckets.each do |bucket_key|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @redis.hgetall(bucket_key)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  }.reject { |hash| hash.count.zero? }
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                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
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,56 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class FreshRedis
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Key
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                DEFAULT_OPTIONS = {
         
     | 
| 
      
 5 
     | 
    
         
            +
                  :freshness => 60 * 60, # 1 hour
         
     | 
| 
      
 6 
     | 
    
         
            +
                  :granularity => 1 * 60 # 1 minute
         
     | 
| 
      
 7 
     | 
    
         
            +
                }
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def self.build(*args)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  raise "Don't know how to build FreshRedis::Key for #{args.inspect}" unless args[0]
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  return args[0] if Key === args[0] # early exit if we've already got a key
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  base_key = args[0]
         
     | 
| 
      
 15 
     | 
    
         
            +
                    
         
     | 
| 
      
 16 
     | 
    
         
            +
                  options = DEFAULT_OPTIONS.merge(args[1] || {})
         
     | 
| 
      
 17 
     | 
    
         
            +
                  options[:t] ||= Time.now.to_i
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  self.new(base_key, options[:t], options[:freshness], options[:granularity])
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
                
         
     | 
| 
      
 22 
     | 
    
         
            +
                attr_reader :freshness
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(base_key, t, freshness, granularity)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @base_key     = base_key
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @t            = t
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @freshness    = freshness
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @granularity  = granularity
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def redis_key
         
     | 
| 
      
 32 
     | 
    
         
            +
                  [@base_key, normalize_time(@t, @granularity)].join(":")
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def timestamp_buckets
         
     | 
| 
      
 36 
     | 
    
         
            +
                  from = normalize_time(@t - @freshness, @granularity)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  to = normalize_time(@t, @granularity)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  (from..to).step(@granularity).map{|timestamp| [@base_key, timestamp].join(":") }
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  same = true
         
     | 
| 
      
 43 
     | 
    
         
            +
                  same &= Key === other
         
     | 
| 
      
 44 
     | 
    
         
            +
                  same &= @base_key     == other.instance_variable_get(:@base_key)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  same &= @t            == other.instance_variable_get(:@t)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  same &= @freshness    == other.instance_variable_get(:@freshness)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  same &= @granularity  == other.instance_variable_get(:@granularity)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  same
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                private
         
     | 
| 
      
 52 
     | 
    
         
            +
                def normalize_time(t, granularity)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  t - (t % granularity)
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class FreshRedis
         
     | 
| 
      
 2 
     | 
    
         
            +
              module String
         
     | 
| 
      
 3 
     | 
    
         
            +
                def fincr(key, options={})
         
     | 
| 
      
 4 
     | 
    
         
            +
                  key = Key.build(key, options)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @redis.multi do
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @redis.incr(key.redis_key)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @redis.expire(key.redis_key, key.freshness)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def fsum(key, options={})
         
     | 
| 
      
 12 
     | 
    
         
            +
                  key = Key.build(key, options)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @redis.pipelined {
         
     | 
| 
      
 14 
     | 
    
         
            +
                    key.timestamp_buckets.each do |bucket_key|
         
     | 
| 
      
 15 
     | 
    
         
            +
                      @redis.get(bucket_key)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  }.reduce(0){|acc, value|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    value ? acc + value.to_i : acc
         
     | 
| 
      
 19 
     | 
    
         
            +
                  }
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fresh_redis/version.rb
    CHANGED
    
    
    
        data/lib/fresh_redis.rb
    CHANGED
    
    | 
         @@ -1,61 +1,13 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require 'fresh_redis/ 
     | 
| 
      
 1 
     | 
    
         
            +
            require 'fresh_redis/hash'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fresh_redis/key'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'fresh_redis/string'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'fresh_redis/version'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            class FreshRedis
         
     | 
| 
       5 
     | 
    
         
            -
              include  
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
              VERSION = "0.0.1"
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
              DEFAULT_OPTIONS = {
         
     | 
| 
       10 
     | 
    
         
            -
                :freshness => 60 * 60, # 1 hour
         
     | 
| 
       11 
     | 
    
         
            -
                :granularity => 1 * 60 # 1 minute
         
     | 
| 
       12 
     | 
    
         
            -
              }
         
     | 
| 
      
 7 
     | 
    
         
            +
              include Hash
         
     | 
| 
      
 8 
     | 
    
         
            +
              include String
         
     | 
| 
       13 
9 
     | 
    
         | 
| 
       14 
10 
     | 
    
         
             
              def initialize(redis)
         
     | 
| 
       15 
11 
     | 
    
         
             
                @redis = redis
         
     | 
| 
       16 
12 
     | 
    
         
             
              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 
13 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
              context "hash keys" do 
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                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
         
     | 
| 
      
 17 
     | 
    
         
            +
                    mock_redis.data["foo:#{normalized_now_minute}"].should == {"bar" => "newer_value"}
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  it "should set the freshness as the expiry" do
         
     | 
| 
      
 21 
     | 
    
         
            +
                    # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
         
     | 
| 
      
 22 
     | 
    
         
            +
                    subject.fhset "foo", "bar", "baz", :freshness => 3600, :t => now
         
     | 
| 
      
 23 
     | 
    
         
            +
                    mock_redis.ttl("foo:#{normalized_now_minute}").should == 3600
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                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"]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                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"]
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                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 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fresh_redis'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe FreshRedis::Key do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:now) { Time.new(2012, 9, 27, 15, 40, 56, "+10:00").to_i }
         
     | 
| 
      
 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 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              describe ".build" do
         
     | 
| 
      
 9 
     | 
    
         
            +
                it "complains if no args" do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  expect { FreshRedis::Key.build() }.to raise_error
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                it "just returns the key if a FreshRedis::Key is provided" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  key = FreshRedis::Key.new("key", 123, 456, 789)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  FreshRedis::Key.build(key).should == key
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                it "constructs a FreshRedis::Key with the provided options" do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  key = FreshRedis::Key.build("key", :t => 123, :freshness => 456, :granularity => 789)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  key.should == FreshRedis::Key.new("key", 123, 456, 789)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    
         
     | 
| 
      
 24 
     | 
    
         
            +
                it "constructs a FreshRedis::Key with the default options" do
         
     | 
| 
      
 25 
     | 
    
         
            +
                  key = FreshRedis::Key.build("key")
         
     | 
| 
      
 26 
     | 
    
         
            +
                  key.should == FreshRedis::Key.new(
         
     | 
| 
      
 27 
     | 
    
         
            +
                    "key", 
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Time.now.to_i,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    FreshRedis::Key::DEFAULT_OPTIONS[:freshness], 
         
     | 
| 
      
 30 
     | 
    
         
            +
                    FreshRedis::Key::DEFAULT_OPTIONS[:granularity]
         
     | 
| 
      
 31 
     | 
    
         
            +
                  )
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              describe "#redis_key" do
         
     | 
| 
      
 37 
     | 
    
         
            +
                it "should append the normalized timestamp to the key" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  FreshRedis::Key.build("foo", :t => now, :granularity => 60).redis_key.should == "foo:#{normalized_now_minute}"
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
              
         
     | 
| 
      
 42 
     | 
    
         
            +
              describe "#timestamp_buckets" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                let(:buckets) { FreshRedis::Key.build("foo", :t => now, :freshness => 600, :granularity => 60).timestamp_buckets }
         
     | 
| 
      
 44 
     | 
    
         
            +
                it "generates an enumerable over the range" do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  buckets.should be_kind_of(Enumerable)
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                it "has one timestamp bucket for each granularity step in the fresh range" do
         
     | 
| 
      
 49 
     | 
    
         
            +
                  buckets.count.should == 11 # fence-posting. we include the first and last elements in a timestamp range split by granularity
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                it "has the first timestamp as the maximum freshness" do
         
     | 
| 
      
 53 
     | 
    
         
            +
                  buckets.first.should == ["foo", normalized_now_minute - 600].join(":")
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                it "has now as the maximum freshness" do
         
     | 
| 
      
 57 
     | 
    
         
            +
                  buckets.to_a.last.should == ["foo", normalized_now_minute].join(":")
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                it "steps through the normalized timestamps split up by granularity" do
         
     | 
| 
      
 61 
     | 
    
         
            +
                  buckets.each_with_index{|b, i| b.should == ["foo", normalized_now_minute - 600 + i * 60].join(":") }
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
              context "string keys" do
         
     | 
| 
      
 11 
     | 
    
         
            +
                describe "#fincr" do
         
     | 
| 
      
 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
         
     | 
| 
      
 16 
     | 
    
         
            +
                    mock_redis.data["foo:#{normalized_now_minute}"].to_i.should == 2
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  it "should set the freshness as the expiry" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                    # relying on mock_redis's time handling here - which converts to/from using Time.now Possible flakey temporal breakage potential
         
     | 
| 
      
 21 
     | 
    
         
            +
                    subject.fincr "foo", :freshness => 3600, :t => now
         
     | 
| 
      
 22 
     | 
    
         
            +
                    mock_redis.ttl("foo:#{normalized_now_minute}").should == 3600
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                describe "#fsum" do
         
     | 
| 
      
 27 
     | 
    
         
            +
                  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
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    subject.fsum("foo", :freshness => 60, :granularity => 10, :t => now).should ==  9
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/fresh_redis_spec.rb
    CHANGED
    
    | 
         @@ -2,39 +2,4 @@ require 'fresh_redis' 
     | 
|
| 
       2 
2 
     | 
    
         
             
            require 'mock_redis'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
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 
5 
     | 
    
         
             
            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 
     | 
    
         
            +
              version: 0.0.4
         
     | 
| 
       5 
5 
     | 
    
         
             
              prerelease: 
         
     | 
| 
       6 
6 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       7 
7 
     | 
    
         
             
            authors:
         
     | 
| 
         @@ -9,11 +9,11 @@ authors: 
     | 
|
| 
       9 
9 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       10 
10 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       11 
11 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       12 
     | 
    
         
            -
            date: 2012- 
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2012-10-14 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
15 
     | 
    
         
             
              name: redis
         
     | 
| 
       16 
     | 
    
         
            -
              requirement:  
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       17 
17 
     | 
    
         
             
                none: false
         
     | 
| 
       18 
18 
     | 
    
         
             
                requirements:
         
     | 
| 
       19 
19 
     | 
    
         
             
                - - ! '>='
         
     | 
| 
         @@ -21,10 +21,15 @@ dependencies: 
     | 
|
| 
       21 
21 
     | 
    
         
             
                    version: '0'
         
     | 
| 
       22 
22 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       23 
23 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       24 
     | 
    
         
            -
              version_requirements:  
     | 
| 
      
 24 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 25 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 26 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 27 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 28 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 29 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
       25 
30 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       26 
31 
     | 
    
         
             
              name: rspec
         
     | 
| 
       27 
     | 
    
         
            -
              requirement:  
     | 
| 
      
 32 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       28 
33 
     | 
    
         
             
                none: false
         
     | 
| 
       29 
34 
     | 
    
         
             
                requirements:
         
     | 
| 
       30 
35 
     | 
    
         
             
                - - ! '>='
         
     | 
| 
         @@ -32,18 +37,44 @@ dependencies: 
     | 
|
| 
       32 
37 
     | 
    
         
             
                    version: '0'
         
     | 
| 
       33 
38 
     | 
    
         
             
              type: :development
         
     | 
| 
       34 
39 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       35 
     | 
    
         
            -
              version_requirements:  
     | 
| 
      
 40 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 41 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 42 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 43 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 44 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 45 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
       36 
46 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       37 
47 
     | 
    
         
             
              name: mock_redis
         
     | 
| 
       38 
     | 
    
         
            -
              requirement:  
     | 
| 
      
 48 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       39 
49 
     | 
    
         
             
                none: false
         
     | 
| 
       40 
50 
     | 
    
         
             
                requirements:
         
     | 
| 
       41 
     | 
    
         
            -
                - - =
         
     | 
| 
      
 51 
     | 
    
         
            +
                - - '='
         
     | 
| 
       42 
52 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       43 
53 
     | 
    
         
             
                    version: 0.5.2
         
     | 
| 
       44 
54 
     | 
    
         
             
              type: :development
         
     | 
| 
       45 
55 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       46 
     | 
    
         
            -
              version_requirements:  
     | 
| 
      
 56 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 57 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: 0.5.2
         
     | 
| 
      
 62 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 63 
     | 
    
         
            +
              name: guard-rspec
         
     | 
| 
      
 64 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 66 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 67 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 68 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 69 
     | 
    
         
            +
                    version: 2.1.0
         
     | 
| 
      
 70 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 71 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 72 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 73 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 74 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 75 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 76 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 77 
     | 
    
         
            +
                    version: 2.1.0
         
     | 
| 
       47 
78 
     | 
    
         
             
            description: Aggregate, expiring, recent data in Redis
         
     | 
| 
       48 
79 
     | 
    
         
             
            email:
         
     | 
| 
       49 
80 
     | 
    
         
             
            - madlep@madlep.com
         
     | 
| 
         @@ -55,14 +86,19 @@ files: 
     | 
|
| 
       55 
86 
     | 
    
         
             
            - .rspec
         
     | 
| 
       56 
87 
     | 
    
         
             
            - Gemfile
         
     | 
| 
       57 
88 
     | 
    
         
             
            - Gemfile.lock
         
     | 
| 
      
 89 
     | 
    
         
            +
            - Guardfile
         
     | 
| 
       58 
90 
     | 
    
         
             
            - LICENSE
         
     | 
| 
       59 
91 
     | 
    
         
             
            - README.md
         
     | 
| 
       60 
92 
     | 
    
         
             
            - Rakefile
         
     | 
| 
       61 
93 
     | 
    
         
             
            - fresh_redis.gemspec
         
     | 
| 
       62 
94 
     | 
    
         
             
            - lib/fresh_redis.rb
         
     | 
| 
       63 
     | 
    
         
            -
            - lib/fresh_redis/ 
     | 
| 
      
 95 
     | 
    
         
            +
            - lib/fresh_redis/hash.rb
         
     | 
| 
      
 96 
     | 
    
         
            +
            - lib/fresh_redis/key.rb
         
     | 
| 
      
 97 
     | 
    
         
            +
            - lib/fresh_redis/string.rb
         
     | 
| 
       64 
98 
     | 
    
         
             
            - lib/fresh_redis/version.rb
         
     | 
| 
       65 
     | 
    
         
            -
            - spec/fresh_redis/ 
     | 
| 
      
 99 
     | 
    
         
            +
            - spec/fresh_redis/hash_spec.rb
         
     | 
| 
      
 100 
     | 
    
         
            +
            - spec/fresh_redis/key_spec.rb
         
     | 
| 
      
 101 
     | 
    
         
            +
            - spec/fresh_redis/string_spec.rb
         
     | 
| 
       66 
102 
     | 
    
         
             
            - spec/fresh_redis_spec.rb
         
     | 
| 
       67 
103 
     | 
    
         
             
            homepage: ''
         
     | 
| 
       68 
104 
     | 
    
         
             
            licenses: []
         
     | 
| 
         @@ -76,25 +112,21 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       76 
112 
     | 
    
         
             
              - - ! '>='
         
     | 
| 
       77 
113 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       78 
114 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       79 
     | 
    
         
            -
                  segments:
         
     | 
| 
       80 
     | 
    
         
            -
                  - 0
         
     | 
| 
       81 
     | 
    
         
            -
                  hash: 107948125749487774
         
     | 
| 
       82 
115 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       83 
116 
     | 
    
         
             
              none: false
         
     | 
| 
       84 
117 
     | 
    
         
             
              requirements:
         
     | 
| 
       85 
118 
     | 
    
         
             
              - - ! '>='
         
     | 
| 
       86 
119 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       87 
120 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       88 
     | 
    
         
            -
                  segments:
         
     | 
| 
       89 
     | 
    
         
            -
                  - 0
         
     | 
| 
       90 
     | 
    
         
            -
                  hash: 107948125749487774
         
     | 
| 
       91 
121 
     | 
    
         
             
            requirements: []
         
     | 
| 
       92 
122 
     | 
    
         
             
            rubyforge_project: 
         
     | 
| 
       93 
     | 
    
         
            -
            rubygems_version: 1.8. 
     | 
| 
      
 123 
     | 
    
         
            +
            rubygems_version: 1.8.23
         
     | 
| 
       94 
124 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       95 
125 
     | 
    
         
             
            specification_version: 3
         
     | 
| 
       96 
126 
     | 
    
         
             
            summary: Use redis for working with recent temporal based data that can expiry gradually.
         
     | 
| 
       97 
127 
     | 
    
         
             
              Useful for things like "get a count all failed login attempts for the last hour"
         
     | 
| 
       98 
128 
     | 
    
         
             
            test_files:
         
     | 
| 
       99 
     | 
    
         
            -
            - spec/fresh_redis/ 
     | 
| 
      
 129 
     | 
    
         
            +
            - spec/fresh_redis/hash_spec.rb
         
     | 
| 
      
 130 
     | 
    
         
            +
            - spec/fresh_redis/key_spec.rb
         
     | 
| 
      
 131 
     | 
    
         
            +
            - spec/fresh_redis/string_spec.rb
         
     | 
| 
       100 
132 
     | 
    
         
             
            - spec/fresh_redis_spec.rb
         
     | 
| 
         @@ -1,17 +0,0 @@ 
     | 
|
| 
       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
         
     | 
| 
         @@ -1,49 +0,0 @@ 
     | 
|
| 
       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
         
     |