redcache 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42b0aaf4e3410df46698c1dc6c4266a7ab085a82
4
+ data.tar.gz: 6168ec9abcac4a6331b313f50325d162a0cd760a
5
+ SHA512:
6
+ metadata.gz: 6d1aa979354e1fcaae9126555b8a2ce34bd5529e0770958a5f3df439865f0701fb15b879b99652d79a3b1f4753acb140d986827385352e66d23b31fd44280a11
7
+ data.tar.gz: 00d1ba914e1c8e0facb68c26b993c0c83d2ca9c5549dd4aaea68bfe0b3fe48a4125f3023427e088dca7a722920d8fa75144665b86958a6551ba40d692d0002af
data/.gitignore ADDED
@@ -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
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redcache.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Neil Middleton
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,96 @@
1
+ # Redcache
2
+
3
+ A gem for caching data in Redis.
4
+
5
+ This gem caches data from slow services in Redis, but ensures that the cache is
6
+ up to date.
7
+
8
+ Should the cache be cold, the result is cached. Should the cache be warm but
9
+ considered stale, the cache is still used, but updated subsequently via a
10
+ threaded request, thus not blocking the original request.
11
+
12
+ This gem is also able to encrypt the cached results with Fernet if required.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'redcache'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install redcache
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+
34
+ Redcache.configure do |c|
35
+ c.redis = $redis
36
+ c.secret = <some_long_hash>
37
+ c.encrypt = true
38
+ end
39
+
40
+ value = Redcache.cache "unique_cache_key" do
41
+ call_to_slow_service
42
+ end
43
+ ```
44
+
45
+ ### Configuration
46
+
47
+ Several configuration options are available to use that defined the behaviour of
48
+ redcache.
49
+
50
+ <table>
51
+ <tr>
52
+ <td>:redis</td>
53
+ <td>Connection to redis</td>
54
+ </tr>
55
+ <tr>
56
+ <td>:secret</td>
57
+ <td>If encrypting, what secret should be used. See [Fernet](https://github.com/fernet/fernet-rb) README for more
58
+ information</td>
59
+ </tr>
60
+ <tr>
61
+ <td>:encrypt</td>
62
+ <td>Should cached data be encrypted (boolean)</td>
63
+ </tr>
64
+ <tr>
65
+ <td>:skip_cache</td>
66
+ <td>Should the cache be skipped. Useful in test environments (boolean)</td>
67
+ </tr>
68
+ <tr>
69
+ <td>:logged</td>
70
+ <td>Standard logger object to use for logging</td>
71
+ </tr>
72
+ <tr>
73
+ <td>:log_prefix</td>
74
+ <td>String to prefix to l2met compatible log lines</td>
75
+ </tr>
76
+ <tr>
77
+ <td>:cache_time</td>
78
+ <td>Time (in seconds) to cache data for before expiring</td>
79
+ </tr>
80
+ <tr>
81
+ <td>:stale_time</td>
82
+ <td>Time (in seconds) before cached data should be considered stale</td>
83
+ </tr>
84
+ </table>
85
+
86
+ ## Testing
87
+
88
+ Specs to come ;)
89
+
90
+ ## Contributing
91
+
92
+ 1. Fork it ( https://github.com/[my-github-username]/redcache/fork )
93
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
94
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
95
+ 4. Push to the branch (`git push origin my-new-feature`)
96
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,22 @@
1
+ module Redcache
2
+ class Configuration
3
+ attr_accessor :redis
4
+ attr_accessor :secret
5
+ attr_accessor :cache_time
6
+ attr_accessor :stale_time
7
+ attr_accessor :encrypt
8
+ attr_accessor :skip_cache
9
+ attr_accessor :logger
10
+ attr_accessor :log_prefix
11
+
12
+ def initialize
13
+ @cache_time = 86400
14
+ @stale_time = 900
15
+ @encrypt = false
16
+ @secret = nil
17
+ @skip_cache = false
18
+ @logger = nil
19
+ @log_prefix = "redcache"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Redcache
2
+ VERSION = "0.0.1"
3
+ end
data/lib/redcache.rb ADDED
@@ -0,0 +1,125 @@
1
+ require "redcache/version"
2
+ require "redcache/configuration"
3
+
4
+ module Redcache
5
+ class << self
6
+ attr_writer :configuration
7
+
8
+ def configure
9
+ yield(configuration)
10
+ end
11
+
12
+ def cache(redis_key, &block)
13
+ return block.call if skip_cache?
14
+ if redis_up?
15
+ value = read_from_cache(redis_key, block) || write_into_cache(redis_key, block)
16
+ return value unless value.nil?
17
+ else
18
+ block.call
19
+ end
20
+ end
21
+
22
+ def configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+
26
+ def redis
27
+ configuration.redis
28
+ end
29
+
30
+ def redis_up?
31
+ begin
32
+ redis.ping
33
+ rescue Redis::CannotConnectError
34
+ puts "Redis is DOWN! :shitsonfire:"
35
+ return false
36
+ end
37
+ return true
38
+ end
39
+
40
+ def with_redis(&block)
41
+ block.call if redis_up?
42
+ end
43
+
44
+ def cache_time
45
+ configuration.cache_time
46
+ end
47
+
48
+ def stale_time
49
+ configuration.stale_time
50
+ end
51
+
52
+ def skip_cache?
53
+ configuration.skip_cache
54
+ end
55
+
56
+ def read_from_cache(redis_key, block)
57
+ value = get_value(redis_key)
58
+ value.nil? ? log("cache.miss") : log("cache.hit")
59
+ refresh_cache(redis_key, block) if key_stale?(redis_key) && !value.nil?
60
+ return value
61
+ end
62
+
63
+ def test?
64
+ ENV["RACK_ENV"] == 'test'
65
+ end
66
+
67
+ def refresh_cache(redis_key, block)
68
+ log("cache.stale_refresh")
69
+ Thread.new do
70
+ write_into_cache(redis_key, block)
71
+ end
72
+ end
73
+
74
+ def write_into_cache(redis_key, block)
75
+ json = block.call
76
+ with_redis do
77
+ log("cache.write")
78
+ set_value(redis_key, json)
79
+ end
80
+ json
81
+ end
82
+
83
+ def key_stale?(redis_key)
84
+ ttl = redis.ttl(redis_key)
85
+ return ttl < (configuration.cache_time - configuration.stale_time)
86
+ end
87
+
88
+ def get_value(key)
89
+ decrypt redis.get(key)
90
+ end
91
+
92
+ def set_value(key, value)
93
+ redis.setex key, configuration.cache_time, encrypt(value)
94
+ end
95
+
96
+ def encrypt(value)
97
+ return value unless encrypt?
98
+ Fernet.generate(secret, MultiJson.encode(value))
99
+ end
100
+
101
+ def encrypt?
102
+ configuration.encrypt
103
+ end
104
+
105
+ def decrypt(value)
106
+ return nil if value.nil?
107
+ return value unless encrypt?
108
+ verifier = Fernet.verifier(secret, value)
109
+ return MultiJson.load(verifier.message) if verifier.valid?
110
+ return nil
111
+ end
112
+
113
+ def secret
114
+ configuration.secret
115
+ end
116
+
117
+ def log(str)
118
+ configuration.logger.log(log_prefix(str) => 1)
119
+ end
120
+
121
+ def log_prefix(str)
122
+ [configuration.log_prefix, str].join(".")
123
+ end
124
+ end
125
+ end
data/redcache.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'redcache/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "redcache"
8
+ spec.version = Redcache::VERSION
9
+ spec.authors = ["Neil Middleton"]
10
+ spec.email = ["neil@neilmiddleton.com"]
11
+ spec.summary = %q{A gem for caching values in redis and encrypting them}
12
+ spec.description = %q{A wrapper for Redis, for caching and encryption with Fernet}
13
+ spec.homepage = "http://www.neilmiddleton.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "fakeredis", "~> 0.5"
25
+ spec.add_development_dependency "pliny", "~> 0.0"
26
+
27
+ spec.add_dependency "fernet", "~> 2.1"
28
+ spec.add_dependency "redis", "~> 3.1"
29
+ spec.add_dependency "multi_json", "~> 1.10"
30
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ Redcache.configure do |c|
4
+ c.redis = Redis.new
5
+ c.logger = Pliny
6
+ end
7
+
8
+ class Dummy
9
+ def self.run
10
+ Redcache.cache "foo" do
11
+ get_value
12
+ end
13
+ end
14
+
15
+ def self.get_value
16
+ "bar"
17
+ end
18
+ end
19
+
20
+ describe Redcache do
21
+
22
+ context 'when skipping caching' do
23
+ before do
24
+ allow(Redcache.configuration).to receive(:skip_cache){true}
25
+ end
26
+ it 'does not use the cache' do
27
+ expect(Redcache).to_not receive(:read_from_cache)
28
+ Dummy.run
29
+ end
30
+ end
31
+
32
+ context 'when caching' do
33
+ before do
34
+ allow(Redcache.configuration).to receive(:skip_cache){false}
35
+ end
36
+
37
+ it 'uses the cached' do
38
+ expect(Redcache).to receive(:read_from_cache){ {} }
39
+ Dummy.run
40
+ end
41
+
42
+ it 'triggers a cache write if the cache is cold' do
43
+ allow(Redcache).to receive(:read_from_cache){ nil }
44
+ expect(Redcache).to receive(:write_into_cache) { "" }
45
+ Dummy.run
46
+ end
47
+
48
+ it 'skips cache when redis is down' do
49
+ allow(Redcache).to receive(:redis_up?){ false }
50
+ expect(Redcache).to_not receive(:read_from_cache)
51
+ expect(Dummy).to receive(:get_value)
52
+ Dummy.run
53
+ end
54
+
55
+ it 'triggers a cache refresh with a stale warm cache' do
56
+ allow(Redcache).to receive(:key_stale?){ true }
57
+ allow(Redcache).to receive(:get_value){ "" }
58
+ expect(Redcache).to receive(:refresh_cache).once
59
+ Dummy.run
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,17 @@
1
+ require 'bundler/setup'
2
+ Bundler.require
3
+
4
+ require 'redcache'
5
+ require 'redis'
6
+ require 'fakeredis'
7
+ require 'pliny'
8
+
9
+ RSpec.configure do |config|
10
+ config.expect_with :rspec do |expectations|
11
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
12
+ end
13
+
14
+ config.mock_with :rspec do |mocks|
15
+ mocks.verify_partial_doubles = true
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redcache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Neil Middleton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-05 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: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakeredis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pliny
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: fernet
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: redis
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: multi_json
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.10'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.10'
125
+ description: A wrapper for Redis, for caching and encryption with Fernet
126
+ email:
127
+ - neil@neilmiddleton.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - lib/redcache.rb
139
+ - lib/redcache/configuration.rb
140
+ - lib/redcache/version.rb
141
+ - redcache.gemspec
142
+ - spec/redcache_spec.rb
143
+ - spec/spec_helper.rb
144
+ homepage: http://www.neilmiddleton.com
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.2.2
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: A gem for caching values in redis and encrypting them
168
+ test_files:
169
+ - spec/redcache_spec.rb
170
+ - spec/spec_helper.rb
171
+ has_rdoc: