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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in blockhole.gemspec
4
+ gemspec
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,3 @@
1
+ module Blockhole
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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