readthis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 076d20cf4f8bffa4b9a6b6ced603f65020bea46c
4
+ data.tar.gz: bcb7899c7aa4f98d206ae7fea3e6fc388c7036b4
5
+ SHA512:
6
+ metadata.gz: be88ade839408848ba227ece979dc2d4bb5b4621cdca98eb1f6a3b61fae3d00468940cb9c32eb02b3ca6438e882c680019035583f65c54ed58bd69e33f2c5e3d
7
+ data.tar.gz: dcb48683aaa8ef72cf0e6770f0334245db4f6198fa244486115123a497ee9bc897568687727ef582ec40b220f81f6b1c1c1221b9c1527a80df714f090a1c4fa5
data/.gitignore ADDED
@@ -0,0 +1,15 @@
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
15
+ TODO
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.1.0 2014-11-22
2
+
3
+ - Initial release! Working as a drop in replacement for `redis_store`.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in readthis.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Parker Selbert
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Readthis
2
+
3
+ An ActiveSupport::Cache compatible redis based cache focused on speed,
4
+ simplicity, and forced pooling.
5
+
6
+ The only dependencies are `redis` and `connection_pool`.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'readthis'
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Use it the same way as any other [ActiveSupport::Cache::Store][store]. Readthis
19
+ supports all of the standard cache methods except for the following (with
20
+ reasons):
21
+
22
+ * `cleanup` - redis does this with ttl for us already
23
+ * `delete_matched` - you really don't want to do perform key matching operations
24
+ in redis. They are linear time and only support basic globbing.
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it ( https://github.com/[my-github-username]/readthis/fork )
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create a new Pull Request
33
+
34
+ [store]: http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/rspec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,7 @@
1
+ require 'readthis'
2
+
3
+ module ActiveSupport
4
+ module Cache
5
+ ReadthisStore ||= Readthis::Cache
6
+ end
7
+ end
@@ -0,0 +1,140 @@
1
+ require 'redis'
2
+ require 'connection_pool'
3
+
4
+ module Readthis
5
+ class Cache
6
+ attr_reader :expires_in, :namespace, :pool
7
+
8
+ # Creates a new Readthis::Cache object with the given redis URL. The URL
9
+ # is parsed by the redis client directly.
10
+ def initialize(url, options = {})
11
+ @expires_in = options.fetch(:expires_in, nil)
12
+ @namespace = options.fetch(:namespace, nil)
13
+
14
+ @pool = ConnectionPool.new(pool_options(options)) do
15
+ Redis.new(url: url)
16
+ end
17
+ end
18
+
19
+ def read(key, options = {})
20
+ with do |store|
21
+ store.get(namespaced_key(key, merged_options(options)))
22
+ end
23
+ end
24
+
25
+ def write(key, value, options = {})
26
+ options = merged_options(options)
27
+ namespaced = namespaced_key(key, options)
28
+
29
+ with do |store|
30
+ if expiration = options[:expires_in]
31
+ store.setex(namespaced, expiration, value)
32
+ else
33
+ store.set(namespaced, value)
34
+ end
35
+ end
36
+ end
37
+
38
+ def delete(key, options = {})
39
+ with do |store|
40
+ store.del(namespaced_key(key, merged_options(options)))
41
+ end
42
+ end
43
+
44
+ def fetch(key, options = {})
45
+ value = read(key, options) unless options[:force]
46
+
47
+ if value.nil? && block_given?
48
+ value = yield(key)
49
+ write(key, value, options)
50
+ end
51
+
52
+ value
53
+ end
54
+
55
+ def increment(key, options = {})
56
+ with do |store|
57
+ store.incr(namespaced_key(key, merged_options(options)))
58
+ end
59
+ end
60
+
61
+ def decrement(key, options = {})
62
+ with do |store|
63
+ store.decr(namespaced_key(key, merged_options(options)))
64
+ end
65
+ end
66
+
67
+ def read_multi(*keys)
68
+ options = merged_options(extract_options!(keys))
69
+ results = []
70
+
71
+ with do |store|
72
+ results = store.pipelined do
73
+ keys.each { |key| store.get(namespaced_key(key, options)) }
74
+ end
75
+ end
76
+
77
+ keys.zip(results).to_h
78
+ end
79
+
80
+ # This must be done in two separate blocks. Redis pipelines return
81
+ # futures, which can not be resolved until the pipeline has exited.
82
+ def fetch_multi(*keys)
83
+ results = read_multi(*keys)
84
+ options = merged_options(extract_options!(keys))
85
+
86
+ with do |store|
87
+ store.pipelined do
88
+ results.each do |key, value|
89
+ if value.nil?
90
+ value = yield key
91
+ write(key, value, options)
92
+ results[key] = value
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ results
99
+ end
100
+
101
+ def exist?(key, options = {})
102
+ with do |store|
103
+ store.exists(namespaced_key(key, merged_options(options)))
104
+ end
105
+ end
106
+
107
+ def clear
108
+ with do |store|
109
+ store.flushdb
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def with(&block)
116
+ pool.with(&block)
117
+ end
118
+
119
+ def extract_options!(array)
120
+ array.last.is_a?(Hash) ? array.pop : {}
121
+ end
122
+
123
+ def merged_options(options)
124
+ options[:namespace] ||= namespace
125
+ options[:expires_in] ||= expires_in
126
+ options
127
+ end
128
+
129
+ def pool_options(options)
130
+ { size: options.fetch(:pool_size, 5),
131
+ timeout: options.fetch(:pool_timeout, 5) }
132
+ end
133
+
134
+ def namespaced_key(key, options)
135
+ namespace = options[:namespace]
136
+
137
+ [namespace, key].compact.join(':')
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ module Readthis
2
+ VERSION = '0.1.0'
3
+ end
data/lib/readthis.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'readthis/cache'
2
+ require 'readthis/version'
3
+
4
+ module Readthis
5
+ end
data/readthis.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'readthis/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'readthis'
8
+ spec.version = Readthis::VERSION
9
+ spec.authors = ['Parker Selbert']
10
+ spec.email = ['parker@sorentwo.com']
11
+ spec.summary = 'Pooled active support compliant caching with redis'
12
+ spec.description = 'Pooled active support compliant caching with redis'
13
+ spec.homepage = 'https://github.com/sorentwo/readthis'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{^(spec)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'redis', '~> 3.0'
21
+ spec.add_dependency 'connection_pool', '~> 2.1'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.1'
26
+ end
@@ -0,0 +1,153 @@
1
+ require 'readthis/cache'
2
+
3
+ RSpec.describe Readthis::Cache do
4
+ let(:url) { 'redis://localhost:6379/11' }
5
+ let(:cache) { Readthis::Cache.new(url) }
6
+
7
+ after do
8
+ cache.clear
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'accepts and persists a namespace' do
13
+ cache = Readthis::Cache.new(url, namespace: 'kash')
14
+
15
+ expect(cache.namespace).to eq('kash')
16
+ end
17
+
18
+ it 'accepts and persists an expiration' do
19
+ cache = Readthis::Cache.new(url, expires_in: 10)
20
+
21
+ expect(cache.expires_in).to eq(10)
22
+ end
23
+ end
24
+
25
+ describe '#write' do
26
+ it 'stores strings in the cache' do
27
+ cache.write('some-key', 'some-value')
28
+
29
+ expect(cache.read('some-key')).to eq('some-value')
30
+ end
31
+
32
+ it 'stores values within a namespace' do
33
+ cache.write('some-key', 'some-value', namespace: 'cache')
34
+
35
+ expect(cache.read('some-key')).to be_nil
36
+ expect(cache.read('some-key', namespace: 'cache')).to eq('some-value')
37
+ end
38
+
39
+ it 'uses a custom expiration' do
40
+ cache.write('some-key', 'some-value', expires_in: 1)
41
+
42
+ expect(cache.read('some-key')).not_to be_nil
43
+ sleep 1.01
44
+ expect(cache.read('some-key')).to be_nil
45
+ end
46
+ end
47
+
48
+ describe '#fetch' do
49
+ it 'gets an existing value' do
50
+ cache.write('great-key', 'great')
51
+ expect(cache.fetch('great-key')).to eq('great')
52
+ end
53
+
54
+ it 'sets the value from the provided block' do
55
+ value = 'value for you'
56
+ cache.fetch('missing-key') { value }
57
+ expect(cache.read('missing-key')).to eq(value)
58
+ end
59
+
60
+ it 'does not set for a missing key without a block' do
61
+ expect(cache.fetch('missing-key')).to be_nil
62
+ end
63
+
64
+ it 'forces a cache miss when `force` is passed' do
65
+ cache.write('short-key', 'stuff')
66
+ cache.fetch('short-key', force: true) { 'other stuff' }
67
+
68
+ expect(cache.read('short-key')).to eq('other stuff')
69
+ end
70
+ end
71
+
72
+ describe '#read_multi' do
73
+ it 'maps multiple values to keys' do
74
+ cache.write('a', 1)
75
+ cache.write('b', 2)
76
+ cache.write('c', 3)
77
+
78
+ expect(cache.read_multi('a', 'b', 'c')).to eq(
79
+ 'a' => '1',
80
+ 'b' => '2',
81
+ 'c' => '3',
82
+ )
83
+ end
84
+
85
+ it 'respects namespacing' do
86
+ cache.write('d', 1, namespace: 'cache')
87
+ cache.write('e', 2, namespace: 'cache')
88
+
89
+ expect(cache.read_multi('d', 'e', namespace: 'cache')).to eq(
90
+ 'd' => '1',
91
+ 'e' => '2',
92
+ )
93
+ end
94
+ end
95
+
96
+ describe '#fetch_multi' do
97
+ it 'reads multiple values, filling in missing keys from a block' do
98
+ cache.write('a', 1)
99
+ cache.write('c', 3)
100
+
101
+ results = cache.fetch_multi('a', 'b', 'c') { |key| key + key }
102
+
103
+ expect(results).to eq(
104
+ 'a' => '1',
105
+ 'b' => 'bb',
106
+ 'c' => '3',
107
+ )
108
+ end
109
+ end
110
+
111
+ describe '#exist?' do
112
+ it 'is true when the key has been set' do
113
+ cache.write('existing-key', 'stuff')
114
+ expect(cache.exist?('existing-key')).to be_truthy
115
+ end
116
+
117
+ it 'is false when the key has not been set' do
118
+ expect(cache.exist?('random-key')).to be_falsey
119
+ end
120
+ end
121
+
122
+ describe '#delete' do
123
+ it 'deletes an existing key' do
124
+ cache.write('not-long', 'for this world')
125
+ cache.delete('not-long')
126
+
127
+ expect(cache.read('not-long')).to be_nil
128
+ end
129
+ end
130
+
131
+ describe '#increment' do
132
+ it 'atomically increases the stored integer' do
133
+ cache.write('counter', 10)
134
+ expect(cache.increment('counter')).to eq(11)
135
+ expect(cache.read('counter')).to eq('11')
136
+ end
137
+
138
+ it 'defaults a missing key to 1' do
139
+ expect(cache.increment('unknown')).to eq(1)
140
+ end
141
+ end
142
+
143
+ describe '#decrement' do
144
+ it 'decrements a stored integer' do
145
+ cache.write('counter', 20)
146
+ expect(cache.decrement('counter')).to eq(19)
147
+ end
148
+
149
+ it 'defaults a missing key to -1' do
150
+ expect(cache.decrement('unknown')).to eq(-1)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,21 @@
1
+ RSpec.configure do |config|
2
+ config.expect_with :rspec do |expectations|
3
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
4
+ end
5
+
6
+ config.mock_with :rspec do |mocks|
7
+ mocks.verify_partial_doubles = true
8
+ end
9
+
10
+ config.filter_run :focus
11
+ config.run_all_when_everything_filtered = true
12
+
13
+ config.disable_monkey_patching!
14
+
15
+ if config.files_to_run.one?
16
+ config.default_formatter = 'doc'
17
+ end
18
+
19
+ config.order = :random
20
+ Kernel.srand config.seed
21
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: readthis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Parker Selbert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: connection_pool
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.1'
83
+ description: Pooled active support compliant caching with redis
84
+ email:
85
+ - parker@sorentwo.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - CHANGELOG.md
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/rspec
98
+ - lib/active_support/cache/readthis_store.rb
99
+ - lib/readthis.rb
100
+ - lib/readthis/cache.rb
101
+ - lib/readthis/version.rb
102
+ - readthis.gemspec
103
+ - spec/readthis/cache_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: https://github.com/sorentwo/readthis
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.2.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Pooled active support compliant caching with redis
129
+ test_files:
130
+ - spec/readthis/cache_spec.rb
131
+ - spec/spec_helper.rb