readthis 0.1.0

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.
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