sane_memcached_ttl 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8eabf6e5afe588b8529425c18587573797b8bd3d
4
+ data.tar.gz: 04cf4fe55796da3690a4767775cd69b4917cd78e
5
+ SHA512:
6
+ metadata.gz: 531f55f7481de513547df1de895422e8a56d2b2fa2fba25565f8661e628618a0ff31884961ce472a7f1e638166cf1e42416363456304c1c9311a2237a37200b6
7
+ data.tar.gz: 9c3b47a6d878f630b94f62dea3392fb60a6a934e46b064022809d42f56fc22381dd43052c3d94211d4f23b6f1cb81ec92dd3e97a49f99fc6cdfd13fe0a6dff8a
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1 @@
1
+ sane_memcached_ttl
@@ -0,0 +1 @@
1
+ ruby-2.1.4
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ cache:
3
+ - bundler
4
+ - apt
5
+ before_install:
6
+ - curl http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
7
+ - echo "deb http://packages.couchbase.com/ubuntu precise precise/main" | sudo tee -a /etc/apt/sources.list
8
+ - sudo apt-get update -qq
9
+ - sudo apt-get install -y libcouchbase-dev
10
+ services:
11
+ - memcached
12
+ rvm:
13
+ - 1.9
14
+ - 2.0
15
+ - 2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sane_memcached_ttl.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard :minitest do
2
+ watch(%r{^spec/(.*)_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
5
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Errikos Koen
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.
@@ -0,0 +1,73 @@
1
+ # SaneMemcachedTtl
2
+
3
+ Work around memcached's [feature](https://code.google.com/p/memcached/wiki/NewProgramming#Expiration) of treating expiration times larger than one month as timestamps for Rails cache stores.
4
+
5
+ This is a memcached documented behaviour:
6
+
7
+ * Store a key with expiration time of 10 seconds
8
+ * Retrieve key ~> you get it
9
+ * Wait 10 seconds
10
+ * Retrieve key ~> you don't get it
11
+
12
+ makes sense... but:
13
+
14
+ * Store a key with expiration time of 3.000.000 seconds (about 35 days)
15
+ * Retrieve key ~> you **don't** get it
16
+
17
+ 3.000.000 is treated as timestamp, so the key expired at 1970-02-04 17:20:00 (3.000.000 seconds after the UNIX epoch). So when you want to store something with an expiration time larger than a month you have to convert the expiration time to a proper timestamp. This is annoying and it even becomes impossible when you want to set default expiration times on stores.
18
+
19
+ This gem provides additional cache stores, subclasses of the original ones altering this behaviour for sanity.
20
+
21
+ Supported cache stores: couchbase_store, dalli_store, mem_cache_store.
22
+
23
+ ## Dalli note
24
+
25
+ The dalli store actually fixed this on version 2.7.1 but it's kinda debated ([issue #436](https://github.com/mperham/dalli/issues/436)) on whether this should stay in as it's a documented memcached "feature" and clients should probably be not alter to these things.
26
+
27
+ So **don't** use this if your dalli gem version is >= 2.7.1.
28
+
29
+ ## Installation
30
+
31
+ Add this line to your application's Gemfile:
32
+
33
+ ```ruby
34
+ gem 'sane_memcached_ttl'
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install sane_memcached_ttl
44
+
45
+ ## Usage
46
+
47
+ When defining a Rails cache store like:
48
+
49
+ ```ruby
50
+ config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
51
+ ```
52
+
53
+ just change to:
54
+
55
+ ```ruby
56
+ config.cache_store = :mem_cache_store_with_sane_ttl, "cache-1.example.com", "cache-2.example.com"
57
+ ```
58
+
59
+ Same for all supported stores:
60
+
61
+ | Original store | Sane store |
62
+ | ----------------- | --------------------------------- |
63
+ | `mem_cache_store` | `mem_cache_store_with_sane_ttl` |
64
+ | `dalli_store` | `dalli_store_store_with_sane_ttl` |
65
+ | `couchbase_store` | `couchbase_store_with_sane_ttl` |
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it ( https://github.com/[my-github-username]/sane_memcached_ttl/fork )
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create a new Pull Request
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.name = 'spec'
6
+ t.libs << 'spec'
7
+ t.test_files = FileList['spec/**/*_spec.rb'].exclude(/^spec\/integration\//)
8
+ t.verbose = true
9
+ end
10
+
11
+ Rake::TestTask.new do |t|
12
+ t.name = 'integration'
13
+ t.libs << 'spec'
14
+ t.test_files = FileList['spec/integration**/*_spec.rb']
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Run tests'
19
+ task :default => [:spec, :integration]
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveSupport
4
+ module Cache
5
+ module Concerns
6
+ module SaneMemcachedTtlHelper
7
+ extend ActiveSupport::Concern
8
+ include SaneMemcachedTtl::Utils
9
+
10
+ included do
11
+ attr_accessor :large_default_ttl
12
+ end
13
+
14
+ def extract_large_default_ttl!(options, insane_options)
15
+ ttl = options[insane_options.find { |option| options[option] != nil }]
16
+
17
+ if ttl.present? && is_large_ttl?(ttl)
18
+ insane_options.each { |option| options.delete option }
19
+ self.large_default_ttl = ttl
20
+ end
21
+ end
22
+
23
+ def sanitize_ttl_options!(options, insane_options, default_option)
24
+ ttl = options[insane_options.find { |option| options[option] != nil }]
25
+
26
+ insane_options.each { |option| options.delete(option) }
27
+ options[default_option] = sanitize_ttl(ttl || large_default_ttl)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_support/cache'
2
+ require 'active_support/cache/couchbase_store'
3
+ require 'active_support/cache/concerns/sane_memcached_ttl_helper'
4
+
5
+ module ActiveSupport
6
+ module Cache
7
+ class CouchbaseStoreWithSaneTtl < CouchbaseStore
8
+
9
+ include ActiveSupport::Cache::Concerns::SaneMemcachedTtlHelper
10
+
11
+ def initialize(options = nil)
12
+ options = options || {}
13
+ extract_large_default_ttl! options, [:default_ttl, :expires_in]
14
+ super options
15
+ end
16
+
17
+ def increment(name, amount = 1, options = nil)
18
+ options = options || {}
19
+ sanitize_ttl_options! options, [:ttl, :expires_in], :expires_in
20
+ super name, amount, options
21
+ end
22
+
23
+ def decrement(name, amount = 1, options = nil)
24
+ options = options || {}
25
+ sanitize_ttl_options! options, [:ttl, :expires_in], :expires_in
26
+ super name, amount, options
27
+ end
28
+
29
+ protected
30
+
31
+ def write_entry(key, value, options)
32
+ options = options || {}
33
+ sanitize_ttl_options! options, [:ttl, :expires_in], :expires_in
34
+ super key, value, options
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ require 'active_support/cache'
2
+ require 'active_support/notifications'
3
+ require 'active_support/cache/dalli_store'
4
+ require 'active_support/cache/concerns/sane_memcached_ttl_helper'
5
+
6
+ module ActiveSupport
7
+ module Cache
8
+ class DalliStoreWithSaneTtl < DalliStore
9
+
10
+ include ActiveSupport::Cache::Concerns::SaneMemcachedTtlHelper
11
+
12
+ def initialize(*addresses)
13
+ addresses = addresses.flatten
14
+ options = addresses.extract_options!
15
+ extract_large_default_ttl! options, [:expires_in]
16
+ super addresses, options
17
+ end
18
+
19
+ def increment(name, amount = 1, options = nil)
20
+ options = options || {}
21
+ sanitize_ttl_options! options, [:expires_in], :expires_in
22
+ super name, amount, options
23
+ end
24
+
25
+ def decrement(name, amount = 1, options = nil)
26
+ options = options || {}
27
+ sanitize_ttl_options! options, [:expires_in], :expires_in
28
+ super name, amount, options
29
+ end
30
+
31
+ protected
32
+
33
+ def write_entry(key, value, options)
34
+ options = options || {}
35
+ sanitize_ttl_options! options, [:expires_in], :expires_in
36
+ super key, value, options
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'active_support/cache'
2
+ require 'active_support/cache/mem_cache_store'
3
+ require 'active_support/cache/concerns/sane_memcached_ttl_helper'
4
+ require 'active_support/notifications'
5
+
6
+ module ActiveSupport
7
+ module Cache
8
+ class MemCacheStoreWithSaneTtl < MemCacheStore
9
+
10
+ include ActiveSupport::Cache::Concerns::SaneMemcachedTtlHelper
11
+
12
+ def initialize(*addresses)
13
+ addresses = addresses.flatten
14
+ options = addresses.extract_options!
15
+ extract_large_default_ttl! options, [:expires_in]
16
+ super addresses, options
17
+ end
18
+
19
+ def increment(name, amount = 1, options = nil)
20
+ options = options || {}
21
+ sanitize_ttl_options! options, [:expires_in], :expires_in
22
+ super name, amount, options
23
+ end
24
+
25
+ def decrement(name, amount = 1, options = nil)
26
+ options = options || {}
27
+ sanitize_ttl_options! options, [:expires_in], :expires_in
28
+ super name, amount, options
29
+ end
30
+
31
+ protected
32
+
33
+ def write_entry(key, value, options)
34
+ options = options || {}
35
+ sanitize_ttl_options! options, [:expires_in], :expires_in
36
+ super key, value, options
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ require 'sane_memcached_ttl/version'
2
+
3
+ module SaneMemcachedTtl
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'sane_memcached_ttl/utils'
@@ -0,0 +1,17 @@
1
+ module SaneMemcachedTtl
2
+ module Utils
3
+ MEMCACHED_MAX_TTL = 30 * 24 * 60 * 60 # 30 days
4
+
5
+ def sanitize_ttl(ttl)
6
+ is_large_ttl?(ttl) ? ttl_to_timestamp(ttl) : ttl
7
+ end
8
+
9
+ def is_large_ttl?(ttl)
10
+ ttl.to_i > MEMCACHED_MAX_TTL
11
+ end
12
+
13
+ def ttl_to_timestamp(ttl)
14
+ Time.now.utc.to_i + ttl.to_i
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module SaneMemcachedTtl
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sane_memcached_ttl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sane_memcached_ttl'
8
+ spec.version = SaneMemcachedTtl::VERSION
9
+ spec.authors = ['Errikos Koen']
10
+ spec.email = ['eirc.eric@gmail.com']
11
+ spec.summary = %q{Add sanity to memcached expiration times.}
12
+ spec.homepage = 'https://github.com/eirc/sane_memcached_ttl'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+
23
+ spec.add_development_dependency 'activesupport'
24
+
25
+ # Cache stores
26
+ spec.add_development_dependency 'couchbase'
27
+ spec.add_development_dependency 'dalli', '< 2.7.1'
28
+
29
+ # Testing gems
30
+ spec.add_development_dependency 'minitest'
31
+ spec.add_development_dependency 'mocha'
32
+ spec.add_development_dependency 'guard'
33
+ spec.add_development_dependency 'guard-minitest'
34
+ end
@@ -0,0 +1,333 @@
1
+ require 'spec_helper'
2
+ require 'mocha/setup'
3
+ require 'active_support/cache/couchbase_store_with_sane_ttl'
4
+
5
+ module ActiveSupport::Cache
6
+ describe CouchbaseStoreWithSaneTtl do
7
+ let (:small_ttl) { 7 * 24 * 60 * 60 }
8
+ let (:large_ttl) { 6 * 30 * 24 * 60 * 60 }
9
+
10
+ describe '#initialize' do
11
+ it 'should remove from options and store large default_ttl value' do
12
+ Couchbase::Bucket.expects(:new).with do |options|
13
+ options[:default_ttl].must_be_nil
14
+ options[:expires_in].must_be_nil
15
+ end
16
+
17
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
18
+ :default_ttl => large_ttl
19
+
20
+ store.large_default_ttl.must_equal large_ttl
21
+ end
22
+
23
+ it 'should remove from options and store large expires_in value' do
24
+ Couchbase::Bucket.expects(:new).with do |options|
25
+ options[:default_ttl].must_be_nil
26
+ options[:expires_in].must_be_nil
27
+ end
28
+
29
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
30
+ :expires_in => large_ttl
31
+
32
+ store.large_default_ttl.must_equal large_ttl
33
+ end
34
+
35
+ it 'should not remove from options or store small default_ttl value' do
36
+ Couchbase::Bucket.expects(:new).with do |options|
37
+ options[:default_ttl].must_equal small_ttl
38
+ options[:expires_in].must_be_nil
39
+ end
40
+
41
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
42
+ :default_ttl => small_ttl
43
+
44
+ store.large_default_ttl.must_be_nil
45
+ end
46
+
47
+ it 'should not remove or store small expires_in value' do
48
+ Couchbase::Bucket.expects(:new).with do |options|
49
+ options[:default_ttl].must_equal small_ttl
50
+ options[:expires_in].must_be_nil
51
+ end
52
+
53
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
54
+ :expires_in => small_ttl
55
+
56
+ store.large_default_ttl.must_be_nil
57
+ end
58
+ end
59
+
60
+ describe '#increment' do
61
+ describe 'with no default tll' do
62
+ let(:bucket_mock) { mock }
63
+ subject do
64
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
65
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl
66
+ end
67
+
68
+ it 'should convert ttl to timestamp when set to large value' do
69
+ bucket_mock.expects(:incr).with do |key, amount, options|
70
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
71
+ end
72
+ subject.increment 'key', 1, :expires_in => large_ttl
73
+ end
74
+
75
+ it 'should keep ttl intact when set to small value' do
76
+ bucket_mock.expects(:incr).with do |key, amount, options|
77
+ options[:ttl].must_equal small_ttl
78
+ end
79
+ subject.increment 'key', 1, :expires_in => small_ttl
80
+ end
81
+
82
+ it 'should leave ttl nil when not set' do
83
+ bucket_mock.expects(:incr).with do |key, amount, options|
84
+ options[:ttl].must_be_nil
85
+ end
86
+ subject.increment 'key', 1
87
+ end
88
+ end
89
+
90
+ describe 'with large default tll' do
91
+ let(:bucket_mock) { mock }
92
+ subject do
93
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
94
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
95
+ :expires_in => large_ttl
96
+ end
97
+
98
+ it 'should convert ttl to timestamp when set to large value' do
99
+ bucket_mock.expects(:incr).with do |key, amount, options|
100
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
101
+ end
102
+ subject.increment 'key', 1, :expires_in => large_ttl
103
+ end
104
+
105
+ it 'should keep ttl intact when set to small value' do
106
+ bucket_mock.expects(:incr).with do |key, amount, options|
107
+ options[:ttl].must_equal small_ttl
108
+ end
109
+ subject.increment 'key', 1, :expires_in => small_ttl
110
+ end
111
+
112
+ it 'should calculate from default ttl when not set' do
113
+ bucket_mock.expects(:incr).with do |key, amount, options|
114
+ (options[:ttl]).must_equal Time.now.utc.to_i + large_ttl
115
+ end
116
+ subject.increment 'key', 1
117
+ end
118
+ end
119
+
120
+ describe 'with small default tll' do
121
+ let(:bucket_mock) { mock }
122
+ subject do
123
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
124
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
125
+ :default_ttl => small_ttl
126
+ end
127
+
128
+ it 'should convert ttl to timestamp when set to large value' do
129
+ bucket_mock.expects(:incr).with do |key, amount, options|
130
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
131
+ end
132
+ subject.increment 'key', 1, :expires_in => large_ttl
133
+ end
134
+
135
+ it 'should keep ttl intact when set to small value' do
136
+ bucket_mock.expects(:incr).with do |key, amount, options|
137
+ options[:ttl].must_equal small_ttl
138
+ end
139
+ subject.increment 'key', 1, :expires_in => small_ttl
140
+ end
141
+
142
+ it 'should leave ttl nil when not set' do
143
+ bucket_mock.expects(:incr).with do |key, amount, options|
144
+ options[:ttl].must_be_nil
145
+ end
146
+ subject.increment 'key', 1
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#decrement' do
152
+ describe 'with no default tll' do
153
+ let(:bucket_mock) { mock }
154
+ subject do
155
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
156
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl
157
+ end
158
+
159
+ it 'should convert ttl to timestamp when set to large value' do
160
+ bucket_mock.expects(:decr).with do |key, amount, options|
161
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
162
+ end
163
+ subject.decrement 'key', 1, :expires_in => large_ttl
164
+ end
165
+
166
+ it 'should keep ttl intact when set to small value' do
167
+ bucket_mock.expects(:decr).with do |key, amount, options|
168
+ options[:ttl].must_equal small_ttl
169
+ end
170
+ subject.decrement 'key', 1, :expires_in => small_ttl
171
+ end
172
+
173
+ it 'should leave ttl nil when not set' do
174
+ bucket_mock.expects(:decr).with do |key, amount, options|
175
+ options[:ttl].must_be_nil
176
+ end
177
+ subject.decrement 'key', 1
178
+ end
179
+ end
180
+
181
+ describe 'with large default tll' do
182
+ let(:bucket_mock) { mock }
183
+ subject do
184
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
185
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
186
+ :default_ttl => large_ttl
187
+ end
188
+
189
+ it 'should convert ttl to timestamp when set to large value' do
190
+ bucket_mock.expects(:decr).with do |key, amount, options|
191
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
192
+ end
193
+ subject.decrement 'key', 1, :expires_in => large_ttl
194
+ end
195
+
196
+ it 'should keep ttl intact when set to small value' do
197
+ bucket_mock.expects(:decr).with do |key, amount, options|
198
+ options[:ttl].must_equal small_ttl
199
+ end
200
+ subject.decrement 'key', 1, :expires_in => small_ttl
201
+ end
202
+
203
+ it 'should calculate from default ttl when not set' do
204
+ bucket_mock.expects(:decr).with do |key, amount, options|
205
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
206
+ end
207
+ subject.decrement 'key', 1
208
+ end
209
+ end
210
+
211
+ describe 'with small default tll' do
212
+ let(:bucket_mock) { mock }
213
+ subject do
214
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
215
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
216
+ :default_ttl => small_ttl
217
+ end
218
+
219
+ it 'should convert ttl to timestamp when set to large value' do
220
+ bucket_mock.expects(:decr).with do |key, amount, options|
221
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
222
+ end
223
+ subject.decrement 'key', 1, :expires_in => large_ttl
224
+ end
225
+
226
+ it 'should keep ttl intact when set to small value' do
227
+ bucket_mock.expects(:decr).with do |key, amount, options|
228
+ options[:ttl].must_equal small_ttl
229
+ end
230
+ subject.decrement 'key', 1, :expires_in => small_ttl
231
+ end
232
+
233
+ it 'should leave ttl nil when not set' do
234
+ bucket_mock.expects(:decr).with do |key, amount, options|
235
+ options[:ttl].must_be_nil
236
+ end
237
+ subject.decrement 'key', 1
238
+ end
239
+ end
240
+ end
241
+
242
+ describe '#write' do
243
+ describe 'with no default tll' do
244
+ let(:bucket_mock) { mock }
245
+ subject do
246
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
247
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl
248
+ end
249
+
250
+ it 'should convert ttl to timestamp when set to large value' do
251
+ bucket_mock.expects(:set).with do |key, value, options|
252
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
253
+ end
254
+ subject.write 'key', 'value', :expires_in => large_ttl
255
+ end
256
+
257
+ it 'should keep ttl intact when set to small value' do
258
+ bucket_mock.expects(:set).with do |key, value, options|
259
+ options[:ttl].must_equal small_ttl
260
+ end
261
+ subject.write 'key', 'value', :expires_in => small_ttl
262
+ end
263
+
264
+ it 'should leave ttl nil when not set' do
265
+ bucket_mock.expects(:set).with do |key, value, options|
266
+ options[:ttl].must_be_nil
267
+ end
268
+ subject.write 'key', 'value'
269
+ end
270
+ end
271
+
272
+ describe 'with large default tll' do
273
+ let(:bucket_mock) { mock }
274
+ subject do
275
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
276
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
277
+ :default_ttl => large_ttl
278
+ end
279
+
280
+ it 'should convert ttl to timestamp when set to large value' do
281
+ bucket_mock.expects(:set).with do |key, value, options|
282
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
283
+ end
284
+ subject.write 'key', 'value', :expires_in => large_ttl
285
+ end
286
+
287
+ it 'should keep ttl intact when set to small value' do
288
+ bucket_mock.expects(:set).with do |key, value, options|
289
+ options[:ttl].must_equal small_ttl
290
+ end
291
+ subject.write 'key', 'value', :expires_in => small_ttl
292
+ end
293
+
294
+ it 'should calculate from default ttl when not set' do
295
+ bucket_mock.expects(:set).with do |key, value, options|
296
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
297
+ end
298
+ subject.write 'key', 'value'
299
+ end
300
+ end
301
+
302
+ describe 'with small default tll' do
303
+ let(:bucket_mock) { mock }
304
+ subject do
305
+ Couchbase::Bucket.expects(:new).returns(bucket_mock)
306
+ ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
307
+ :default_ttl => small_ttl
308
+ end
309
+
310
+ it 'should convert ttl to timestamp when set to large value' do
311
+ bucket_mock.expects(:set).with do |key, value, options|
312
+ options[:ttl].must_equal Time.now.utc.to_i + large_ttl
313
+ end
314
+ subject.write 'key', 'value', :expires_in => large_ttl
315
+ end
316
+
317
+ it 'should keep ttl intact when set to small value' do
318
+ bucket_mock.expects(:set).with do |key, value, options|
319
+ options[:ttl].must_equal small_ttl
320
+ end
321
+ subject.write 'key', 'value', :expires_in => small_ttl
322
+ end
323
+
324
+ it 'should leave ttl nil when not set' do
325
+ bucket_mock.expects(:set).with do |key, value, options|
326
+ options[:ttl].must_be_nil
327
+ end
328
+ subject.write 'key', 'value'
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+ require 'active_support/cache'
3
+
4
+ module ActiveSupport::Cache
5
+ describe 'couchbase stores' do
6
+ let(:large_ttl) { 6 * 30 * 24 * 60 * 60 }
7
+
8
+ before do
9
+ begin
10
+ ActiveSupport::Cache.lookup_store :couchbase_store,
11
+ CACHE_SETTINGS[:couchbase]
12
+ rescue
13
+ skip 'Cannot connect to Couchbase server, skipping integration tests.'
14
+ end
15
+ end
16
+
17
+ describe 'with connection ttls' do
18
+ it 'couchbase_store_with_sane_ttl does store keys with large connection default_ttl' do
19
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
20
+ CACHE_SETTINGS[:couchbase].merge(:default_ttl => large_ttl)
21
+
22
+ store.read('key').must_be_nil
23
+ store.write 'key', 'value'
24
+ store.read('key').must_equal 'value'
25
+ store.delete 'key'
26
+ end
27
+
28
+ it 'couchbase_store does not store keys with large connection default_ttl' do
29
+ store = ActiveSupport::Cache.lookup_store :couchbase_store,
30
+ CACHE_SETTINGS[:couchbase].merge(:default_ttl => large_ttl)
31
+
32
+ store.read('key').must_be_nil
33
+ store.write 'key', 'value'
34
+ store.read('key').must_be_nil
35
+ store.delete 'key'
36
+ end
37
+
38
+ it 'couchbase_store_with_sane_ttl does store keys with large connection expires_in' do
39
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
40
+ CACHE_SETTINGS[:couchbase].merge(:expires_in => large_ttl)
41
+
42
+ store.read('key').must_be_nil
43
+ store.write 'key', 'value'
44
+ store.read('key').must_equal 'value'
45
+ store.delete 'key'
46
+ end
47
+
48
+ it 'couchbase_store does not store keys with large connection expires_in' do
49
+ store = ActiveSupport::Cache.lookup_store :couchbase_store,
50
+ CACHE_SETTINGS[:couchbase].merge(:expires_in => large_ttl)
51
+
52
+ store.read('key').must_be_nil
53
+ store.write 'key', 'value'
54
+ store.read('key').must_be_nil
55
+ store.delete 'key'
56
+ end
57
+ end
58
+
59
+ describe 'with ttls on write' do
60
+ it 'couchbase_store_with_sane_ttl does store keys with large expires_in' do
61
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
62
+ CACHE_SETTINGS[:couchbase]
63
+
64
+ store.read('key').must_be_nil
65
+ store.write 'key', 'value', :expires_in => large_ttl
66
+ store.read('key').must_equal 'value'
67
+ store.delete 'key'
68
+ end
69
+
70
+ it 'couchbase_store does not store keys with large expires_in' do
71
+ store = ActiveSupport::Cache.lookup_store :couchbase_store,
72
+ CACHE_SETTINGS[:couchbase]
73
+
74
+ store.read('key').must_be_nil
75
+ store.write 'key', 'value', :expires_in => large_ttl
76
+ store.read('key').must_be_nil
77
+ store.delete 'key'
78
+ end
79
+
80
+ it 'couchbase_store_with_sane_ttl does store keys with large ttl' do
81
+ store = ActiveSupport::Cache.lookup_store :couchbase_store_with_sane_ttl,
82
+ CACHE_SETTINGS[:couchbase]
83
+
84
+ store.read('key').must_be_nil
85
+ store.write 'key', 'value', :ttl => large_ttl
86
+ store.read('key').must_equal 'value'
87
+ store.delete 'key'
88
+ end
89
+
90
+ it 'couchbase_store does not store keys with large ttl' do
91
+ store = ActiveSupport::Cache.lookup_store :couchbase_store,
92
+ CACHE_SETTINGS[:couchbase]
93
+
94
+ store.read('key').must_be_nil
95
+ store.write 'key', 'value', :ttl => large_ttl
96
+ store.read('key').must_be_nil
97
+ store.delete 'key'
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'active_support/cache'
3
+ require 'active_support/notifications'
4
+ require 'dalli'
5
+
6
+ module ActiveSupport::Cache
7
+ describe 'dalli stores' do
8
+ let(:large_ttl) { 6 * 30 * 24 * 60 * 60 }
9
+
10
+ before do
11
+ # Silence all logging to make an initial check on whether server is up
12
+ Dalli.logger.level = Logger::UNKNOWN
13
+ store = ActiveSupport::Cache.lookup_store :dalli_store,
14
+ CACHE_SETTINGS[:dalli]
15
+
16
+ if store.stats.values.all? { |value| value == nil }
17
+ skip 'Cannot connect to Memcached server, skipping dalli stores integration specs.'
18
+ end
19
+
20
+ # Silence connection info messages from dalli
21
+ Dalli.logger.level = Logger::WARN
22
+ end
23
+
24
+ describe 'with connection ttls' do
25
+ it 'dalli_store_with_sane_ttl does store keys with large connection expires_in' do
26
+ store = ActiveSupport::Cache.lookup_store :dalli_store_with_sane_ttl,
27
+ CACHE_SETTINGS[:dalli],
28
+ :expires_in => large_ttl
29
+
30
+ store.read('key').must_be_nil
31
+ store.write 'key', 'value'
32
+ store.read('key').must_equal 'value'
33
+ store.delete 'key'
34
+ end
35
+
36
+ it 'dalli_store does not store keys with large connection expires_in' do
37
+ store = ActiveSupport::Cache.lookup_store :dalli_store,
38
+ CACHE_SETTINGS[:dalli],
39
+ :expires_in => large_ttl
40
+
41
+ store.read('key').must_be_nil
42
+ store.write 'key', 'value'
43
+ store.read('key').must_be_nil
44
+ store.delete 'key'
45
+ end
46
+ end
47
+
48
+ describe 'with ttls on write' do
49
+ it 'dalli_store_with_sane_ttl does store keys with large expires_in' do
50
+ store = ActiveSupport::Cache.lookup_store :dalli_store_with_sane_ttl,
51
+ CACHE_SETTINGS[:dalli]
52
+
53
+ store.read('key').must_be_nil
54
+ store.write 'key', 'value', :expires_in => large_ttl
55
+ store.read('key').must_equal 'value'
56
+ store.delete 'key'
57
+ end
58
+
59
+ it 'dalli_store does not store keys with large expires_in' do
60
+ store = ActiveSupport::Cache.lookup_store :dalli_store,
61
+ CACHE_SETTINGS[:dalli]
62
+
63
+ store.read('key').must_be_nil
64
+ store.write 'key', 'value', :expires_in => large_ttl
65
+ store.read('key').must_be_nil
66
+ store.delete 'key'
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'active_support/cache'
3
+ require 'dalli'
4
+
5
+ module ActiveSupport::Cache
6
+ describe 'mem cache stores' do
7
+ let(:large_ttl) { 6 * 30 * 24 * 60 * 60 }
8
+
9
+ before do
10
+ # Silence all logging to make an initial check on whether server is up (mem_cache_store is backed by a dalli client)
11
+ Dalli.logger.level = Logger::UNKNOWN
12
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store,
13
+ CACHE_SETTINGS[:mem_cache]
14
+
15
+ if store.stats.values.all? { |value| value == nil }
16
+ skip 'Cannot connect to Memcached server, skipping mem cache stores integration specs.'
17
+ end
18
+
19
+ # Silence connection info messages
20
+ Dalli.logger.level = Logger::WARN
21
+ end
22
+
23
+ describe 'with connection ttls' do
24
+ it 'mem_cache_store_with_sane_ttl does store keys with large connection expires_in' do
25
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store_with_sane_ttl,
26
+ CACHE_SETTINGS[:mem_cache],
27
+ :expires_in => large_ttl
28
+
29
+ store.read('key').must_be_nil
30
+ store.write 'key', 'value'
31
+ store.read('key').must_equal 'value'
32
+ store.delete 'key'
33
+ end
34
+
35
+ it 'mem_cache_store does not store keys with large connection expires_in' do
36
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store,
37
+ CACHE_SETTINGS[:mem_cache],
38
+ :expires_in => large_ttl
39
+
40
+ store.read('key').must_be_nil
41
+ store.write 'key', 'value'
42
+ store.read('key').must_be_nil
43
+ store.delete 'key'
44
+ end
45
+ end
46
+
47
+ describe 'with ttls on write' do
48
+ it 'mem_cache_store_with_sane_ttl does store keys with large expires_in' do
49
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store_with_sane_ttl,
50
+ CACHE_SETTINGS[:mem_cache]
51
+
52
+ store.read('key').must_be_nil
53
+ store.write 'key', 'value', :expires_in => large_ttl
54
+ store.read('key').must_equal 'value'
55
+ store.delete 'key'
56
+ end
57
+
58
+ it 'mem_cache_store does not store keys with large expires_in' do
59
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store,
60
+ CACHE_SETTINGS[:mem_cache]
61
+
62
+ store.read('key').must_be_nil
63
+ store.write 'key', 'value', :expires_in => large_ttl
64
+ store.read('key').must_be_nil
65
+ store.delete 'key'
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,10 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+
4
+ require 'sane_memcached_ttl'
5
+
6
+ CACHE_SETTINGS = {
7
+ :couchbase => {},
8
+ :dalli => [],
9
+ :mem_cache => []
10
+ }
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sane_memcached_ttl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Errikos Koen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: couchbase
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: dalli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.7.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.7.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mocha
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: guard-minitest
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - eirc.eric@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".ruby-gemset"
148
+ - ".ruby-version"
149
+ - ".travis.yml"
150
+ - Gemfile
151
+ - Guardfile
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - lib/active_support/cache/concerns/sane_memcached_ttl_helper.rb
156
+ - lib/active_support/cache/couchbase_store_with_sane_ttl.rb
157
+ - lib/active_support/cache/dalli_store_with_sane_ttl.rb
158
+ - lib/active_support/cache/mem_cache_store_with_sane_ttl.rb
159
+ - lib/sane_memcached_ttl.rb
160
+ - lib/sane_memcached_ttl/utils.rb
161
+ - lib/sane_memcached_ttl/version.rb
162
+ - sane_memcached_ttl.gemspec
163
+ - spec/active_support/cache/couchbase_store_with_sane_ttl_spec.rb
164
+ - spec/integration/couchbase_stores_spec.rb
165
+ - spec/integration/dalli_stores_spec.rb
166
+ - spec/integration/mem_cache_stores_spec.rb
167
+ - spec/spec_helper.rb
168
+ homepage: https://github.com/eirc/sane_memcached_ttl
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.4.2
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Add sanity to memcached expiration times.
192
+ test_files:
193
+ - spec/active_support/cache/couchbase_store_with_sane_ttl_spec.rb
194
+ - spec/integration/couchbase_stores_spec.rb
195
+ - spec/integration/dalli_stores_spec.rb
196
+ - spec/integration/mem_cache_stores_spec.rb
197
+ - spec/spec_helper.rb