blockhole 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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +118 -0
- data/Rakefile +12 -0
- data/blockhole.gemspec +27 -0
- data/lib/blockhole.rb +49 -0
- data/lib/blockhole/version.rb +3 -0
- data/spec/blockhole_spec.rb +66 -0
- data/spec/spec_helper.rb +15 -0
- metadata +140 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Christian Schlensker
|
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,118 @@
|
|
1
|
+
# Blockhole (alpha)
|
2
|
+
|
3
|
+
Have an expensive block that you need to cache? Maybe an http response from an
|
4
|
+
expensive third party service?
|
5
|
+
|
6
|
+
Inspired heavily from the interface of [VCR](https://github.com/vcr/vcr)
|
7
|
+
Blockhole will suck up the response from the block so it only runs once.
|
8
|
+
|
9
|
+
Blockhole.use_hole('pie-hole') do
|
10
|
+
# some expensive operation that returns some value
|
11
|
+
end
|
12
|
+
|
13
|
+
It currently only supports redis for storage, but more options are forthcoming.
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
gem 'blockhole'
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install blockhole
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
|
34
|
+
### Configuration
|
35
|
+
|
36
|
+
The first step is to configure Blockhole to use your redis connection.
|
37
|
+
Instructions on setting up a redis connection can be found in the [redis-rb
|
38
|
+
docs](https://github.com/redis/redis-rb).
|
39
|
+
|
40
|
+
Blockhole.configure do |b|
|
41
|
+
b.storage = Redis.new(:host => 'localhost', :port => 6379)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
### Basic usage
|
46
|
+
|
47
|
+
Next record a block by passing the name of the redis key you would like to use.
|
48
|
+
|
49
|
+
my_pie = Blockhole.use_hole('pie-hole') do
|
50
|
+
# some expensive operation to get pie
|
51
|
+
'cherry pie'
|
52
|
+
end
|
53
|
+
|
54
|
+
puts my_pie
|
55
|
+
> 'cherry pie'
|
56
|
+
|
57
|
+
Blockhole will check redis cache for that key and return that value if it
|
58
|
+
exists (this is called a *cache hit*). If it doesn't find anything it will run
|
59
|
+
the block, store it's return value in the cache and return it to you.
|
60
|
+
|
61
|
+
You can also explicitly get the value back out without passing a block using
|
62
|
+
a get call.
|
63
|
+
|
64
|
+
my_pie = Blockhole.get('pie-hole') # still cherry
|
65
|
+
|
66
|
+
### What can be stored
|
67
|
+
|
68
|
+
Anything that can be fed into `MultiJson.dump` will work.
|
69
|
+
Strings will just be stored and returned as is. Arrays and Hashes will be
|
70
|
+
serialized before storage and then reparsed after retreival.
|
71
|
+
|
72
|
+
Blockhole.use_hole('pie-hole') do
|
73
|
+
[
|
74
|
+
{ name: 'cherry' },
|
75
|
+
{ name: 'peach' }
|
76
|
+
]
|
77
|
+
end
|
78
|
+
|
79
|
+
pies = Blockhole.get('pie-hole')
|
80
|
+
puts pies[1][:name]
|
81
|
+
|
82
|
+
> 'peach'
|
83
|
+
|
84
|
+
|
85
|
+
### Expiration
|
86
|
+
|
87
|
+
You can pass an optional expiration value (in seconds). The key will
|
88
|
+
automatically be deleted after that amount of time has passed.
|
89
|
+
|
90
|
+
lifespan = 3600 # 1 hour
|
91
|
+
|
92
|
+
my_pie = Blockhole.use_hole('pie-hole', lifespan) do
|
93
|
+
# some expensive operation to get pie
|
94
|
+
end
|
95
|
+
|
96
|
+
# an hour passes
|
97
|
+
|
98
|
+
my_pie = Blockhole.use_hole('pie-hole', lifespan) do
|
99
|
+
# Now it's blueberry!
|
100
|
+
end
|
101
|
+
|
102
|
+
Note that this command will refresh the expiration each time it is called.
|
103
|
+
|
104
|
+
### Busting the cache
|
105
|
+
|
106
|
+
If you'd had enough with the existing data you can collapse the hole and clear
|
107
|
+
the cache. This will result in the key being deleted from redis.
|
108
|
+
|
109
|
+
Blockhole.collapse('pie-hole')
|
110
|
+
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
1. Fork it
|
115
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
116
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
117
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
118
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
6
|
+
t.verbose = false
|
7
|
+
|
8
|
+
# we require spec_helper so we don't get an RSpec warning about
|
9
|
+
# examples being defined before configuration.
|
10
|
+
# t.ruby_opts = "-w -I./spec -r./spec/capture_warnings -rspec_helper"
|
11
|
+
t.rspec_opts = %w[--format progress]
|
12
|
+
end
|
data/blockhole.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'blockhole/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "blockhole"
|
8
|
+
gem.version = Blockhole::VERSION
|
9
|
+
gem.authors = ["Christian Schlensker"]
|
10
|
+
gem.email = ["christian@cswebartisan.com"]
|
11
|
+
gem.description = %q{This is a simple caching library inspired by the api of VCR. Currently Redis is the only storage mechanism supported but there are more forthcoming.}
|
12
|
+
gem.summary = %q{Caches the result of heavy blocks.}
|
13
|
+
gem.homepage = "https://github.com/wordofchristian/blockhole"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_dependency "redis", '~> 2.1'
|
22
|
+
gem.add_dependency "multi_json", '~> 1.5'
|
23
|
+
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency 'bundler', '>= 1.0.7'
|
26
|
+
gem.add_development_dependency 'rspec', '~> 2.11'
|
27
|
+
end
|
data/lib/blockhole.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require "blockhole/version"
|
2
|
+
require 'redis'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module Blockhole
|
6
|
+
class << self
|
7
|
+
attr_accessor :storage
|
8
|
+
|
9
|
+
def configure
|
10
|
+
yield self
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def use_hole(hole_name, lifespan = nil, &block)
|
15
|
+
if lifespan and lifespan < 1
|
16
|
+
raise ArgumentError,
|
17
|
+
"lifespan (if provided) must be >= 0, got #{lifespan}"
|
18
|
+
end
|
19
|
+
|
20
|
+
unless value = storage.get(hole_name)
|
21
|
+
value = block.call(hole_name)
|
22
|
+
storage.set(hole_name, encode(value))
|
23
|
+
storage.expire(hole_name, lifespan) if lifespan
|
24
|
+
end
|
25
|
+
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(name)
|
30
|
+
value = storage.get(name)
|
31
|
+
begin
|
32
|
+
return MultiJson.load value
|
33
|
+
rescue MultiJson::DecodeError
|
34
|
+
return value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def collapse(name)
|
39
|
+
storage.del name
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def encode(value)
|
45
|
+
return value if value.kind_of? String
|
46
|
+
MultiJson.dump value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'blockhole'
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe Blockhole do
|
5
|
+
it 'should have a version number' do
|
6
|
+
Blockhole::VERSION.should_not be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should cache things' do
|
10
|
+
Blockhole.use_hole('pie-hole') { 'blueberry pie' }
|
11
|
+
pie = Blockhole.use_hole('pie-hole') { 'cherry pie' }
|
12
|
+
pie.should == 'blueberry pie'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should support blocks that return hash objects' do
|
16
|
+
Blockhole.use_hole('pie-hole') do
|
17
|
+
{ 'name' => 'blueberry pie'}
|
18
|
+
end
|
19
|
+
|
20
|
+
pie = Blockhole.get('pie-hole')
|
21
|
+
pie.should be_kind_of Hash
|
22
|
+
|
23
|
+
pie['name'].should == 'blueberry pie'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should support blocks that return array objects' do
|
27
|
+
Blockhole.use_hole('pie-hole') do
|
28
|
+
['blueberry', 'cherry']
|
29
|
+
end
|
30
|
+
|
31
|
+
pies = Blockhole.get('pie-hole')
|
32
|
+
pies.should be_kind_of Array
|
33
|
+
pies[0].should == 'blueberry'
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with an optional lifespan' do
|
37
|
+
it 'expires values' do
|
38
|
+
Blockhole.use_hole('pie-hole', 1) { 'blueberry pie' }
|
39
|
+
sleep(2) #50 milliseconds
|
40
|
+
pie = Blockhole.use_hole('pie-hole') { 'cherry pie' }
|
41
|
+
pie.should == 'cherry pie'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'requires that lifespan be more than 0' do
|
45
|
+
lambda do
|
46
|
+
Blockhole.use_hole('pie-hole', -1) { 'blueberry pie' }
|
47
|
+
end.should raise_error ArgumentError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'get' do
|
52
|
+
it 'gets the values back out again' do
|
53
|
+
Blockhole.use_hole('pie-hole') { 'blueberry pie' }
|
54
|
+
Blockhole.get('pie-hole').should == 'blueberry pie'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'collapse' do
|
59
|
+
it 'should bust the cache' do
|
60
|
+
Blockhole.use_hole('pie-hole') { 'blueberry' }
|
61
|
+
Blockhole.collapse('pie-hole')
|
62
|
+
Blockhole.use_hole('pie-hole') { 'cherry' }
|
63
|
+
Blockhole.get('pie-hole').should == 'cherry'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'blockhole'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.before(:all) do
|
7
|
+
$redis = Redis.new(:host => 'localhost', :port => 6379)
|
8
|
+
Blockhole.configure do |b|
|
9
|
+
b.storage = $redis
|
10
|
+
end
|
11
|
+
end
|
12
|
+
config.before(:each) do
|
13
|
+
$redis.flushdb
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blockhole
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Schlensker
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.1'
|
22
|
+
none: false
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '2.1'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
prerelease: false
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.5'
|
38
|
+
none: false
|
39
|
+
type: :runtime
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '1.5'
|
45
|
+
none: false
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
prerelease: false
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
none: false
|
55
|
+
type: :development
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
none: false
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bundler
|
64
|
+
prerelease: false
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.0.7
|
70
|
+
none: false
|
71
|
+
type: :development
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.0.7
|
77
|
+
none: false
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
prerelease: false
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.11'
|
86
|
+
none: false
|
87
|
+
type: :development
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '2.11'
|
93
|
+
none: false
|
94
|
+
description: This is a simple caching library inspired by the api of VCR. Currently
|
95
|
+
Redis is the only storage mechanism supported but there are more forthcoming.
|
96
|
+
email:
|
97
|
+
- christian@cswebartisan.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- .gitignore
|
103
|
+
- .rspec
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- blockhole.gemspec
|
109
|
+
- lib/blockhole.rb
|
110
|
+
- lib/blockhole/version.rb
|
111
|
+
- spec/blockhole_spec.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
homepage: https://github.com/wordofchristian/blockhole
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
none: false
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
none: false
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 1.8.24
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: Caches the result of heavy blocks.
|
138
|
+
test_files:
|
139
|
+
- spec/blockhole_spec.rb
|
140
|
+
- spec/spec_helper.rb
|