rack-request_cache 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5fbb99d5069d5d499e79f9e72351d064a650728b
4
+ data.tar.gz: c57dd1abeb722eaee2cfb2db2826c9fa3b7b2411
5
+ SHA512:
6
+ metadata.gz: 0bd31c1a12294388ef66267cc11a0f5f09136e71b94cf3de7d402a953d7abeba1757a7dc61453c11aae65773b31c4da1d8f8e6854002fdcdffbb9ad370ada506
7
+ data.tar.gz: fd1e2669fb4871c9d762dc3b407515e072b5ad519dc19c7573a41f5c97db0d9f2d5ae221784b589fa3c4d8fda9c1329050b3bd2086598e80f67bc95b125bef4d
@@ -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
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-request_cache.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Marshall
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.
@@ -0,0 +1,44 @@
1
+ # RackRequestCache
2
+
3
+ [![Build Status](https://secure.travis-ci.org/amarshall/rack-request_cache.png?branch=master)](https://travis-ci.org/amarshall/rack-request_cache)
4
+ [![Code Climate rating](https://codeclimate.com/github/amarshall/rack-request_cache.png)](https://codeclimate.com/github/amarshall/rack-request_cache)
5
+ [![Gem Version](https://badge.fury.io/rb/rack-request_cache.png)](https://rubygems.org/gems/rack-request_cache)
6
+
7
+ Provides a caching layer that exists only within a single Rack request. The middleware itself is thread-safe; each thread has its own cache.
8
+
9
+ ## Installation
10
+
11
+ Install as usual: `gem install rack-request_cache` or add `gem 'rack-request_cache'` to your Gemfile. Note that Ruby 2.0 is required.
12
+
13
+ ## Usage
14
+
15
+ Add the middleware to your app (likely in your `config.ru`):
16
+
17
+ ```ruby
18
+ require 'rack/request_cache'
19
+
20
+ use Rack::RequestCache
21
+ run MyApp
22
+ ```
23
+
24
+ Then, whenever you wish to cache something:
25
+
26
+ ```ruby
27
+ Rack::RequestCache.cache do
28
+ expensive_operation
29
+ end
30
+ ```
31
+
32
+ ## Contributing
33
+
34
+ Contributions are welcome. Please be sure that your pull requests are atomic so they can be considered and accepted separately.
35
+
36
+ 1. Fork it
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create new Pull Request
41
+
42
+ ## Credits & License
43
+
44
+ Copyright © 2013 J. Andrew Marshall. License is available in the LICENSE file.
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run specs'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.ruby_opts = '-w'
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require 'rack/request_cache'
@@ -0,0 +1,26 @@
1
+ require 'rack/request_cache/version'
2
+ require 'rack/request_cache/cache'
3
+ require 'thread_variable'
4
+
5
+ module Rack
6
+ class RequestCache
7
+ extend ThreadVariable
8
+ thread_variable :cache_store
9
+
10
+ def initialize app
11
+ @app = app
12
+ end
13
+
14
+ def call env
15
+ self.class.cache_store ||= Cache.new
16
+ @app.call env
17
+ ensure
18
+ self.class.clear!
19
+ end
20
+
21
+ def self.cache *args, █ cache_store.cache(*args, &block); end
22
+ def self.clear!; cache_store.clear!; end
23
+ def self.fetch *args; cache_store.fetch(*args); end
24
+ def self.has_key? *args; cache_store.has_key?(*args); end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ module Rack
2
+ class RequestCache
3
+ class Cache
4
+ def initialize
5
+ @cache = {}
6
+ end
7
+
8
+ def cache key
9
+ raise ArgumentError, 'no block given' unless block_given?
10
+ return @cache[key] if @cache.has_key? key
11
+ @cache[key] = yield.freeze
12
+ end
13
+
14
+ def clear!
15
+ @cache.clear
16
+ end
17
+
18
+ def fetch key
19
+ @cache.fetch key
20
+ end
21
+
22
+ def has_key? key
23
+ @cache.has_key? key
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class RequestCache
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack/request_cache/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rack-request_cache'
8
+ spec.version = Rack::RequestCache::VERSION
9
+ spec.authors = ['Andrew Marshall']
10
+ spec.email = ['andrew@johnandrewmarshall.com']
11
+ spec.description = %q(Provides a caching layer that exists only within a single Rack request.)
12
+ spec.summary = %q(Provides a caching layer that exists only within a single Rack request.)
13
+ spec.homepage = 'http://johnandrewmarshall.com/projects/rack-request_cache'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
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_dependency 'rack'
22
+ spec.add_dependency 'thread_variable'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec'
27
+ end
@@ -0,0 +1,117 @@
1
+ require 'rack/request_cache/cache'
2
+
3
+ describe Rack::RequestCache::Cache do
4
+ it "can store multiple keys" do
5
+ cache = Rack::RequestCache::Cache.new
6
+
7
+ cache.cache(:foo) { :bar }
8
+ cache.cache(:baz) { :qux }
9
+
10
+ expect(cache.fetch(:foo)).to eq :bar
11
+ expect(cache.fetch(:baz)).to eq :qux
12
+ end
13
+
14
+ it "does not allow mutating the cached value" do
15
+ cache = Rack::RequestCache::Cache.new
16
+ value = 'foobar'
17
+
18
+ returned = cache.cache(:key) { value }
19
+ expect do
20
+ value << 'baz'
21
+ end.to raise_error RuntimeError, /frozen/
22
+ expect do
23
+ returned << 'baz'
24
+ end.to raise_error RuntimeError, /frozen/
25
+
26
+ expect(cache.fetch(:key)).to eq 'foobar'
27
+ end
28
+
29
+ describe "#cache" do
30
+ it "returns the result of the block" do
31
+ test_object = double
32
+ result = double
33
+ cache = Rack::RequestCache::Cache.new
34
+
35
+ expect(test_object).to receive(:expensive_operation).once
36
+ .and_return(result)
37
+ returned = cache.cache(:key) { test_object.expensive_operation }
38
+
39
+ expect(returned).to eq result
40
+ end
41
+
42
+ it "caches the return value of the block" do
43
+ test_object = double
44
+ result = double
45
+ cache = Rack::RequestCache::Cache.new
46
+
47
+ expect(test_object).to receive(:expensive_operation).once
48
+ .and_return(result)
49
+ returned_1 = cache.cache(:key) { test_object.expensive_operation }
50
+ returned_2 = cache.cache(:key) { test_object.expensive_operation }
51
+
52
+ expect(returned_1).to eq result
53
+ expect(returned_2).to eq result
54
+ end
55
+
56
+ it "caches falsey values" do
57
+ test_object = double
58
+ result = nil
59
+ cache = Rack::RequestCache::Cache.new
60
+
61
+ expect(test_object).to receive(:expensive_operation).once
62
+ .and_return(result)
63
+ cache.cache(:key) { test_object.expensive_operation }
64
+ cache.cache(:key) { test_object.expensive_operation }
65
+ end
66
+
67
+ it "raises an ArgumentError when no block is given" do
68
+ cache = Rack::RequestCache::Cache.new
69
+ expect do
70
+ cache.cache(:key)
71
+ end.to raise_error ArgumentError, 'no block given'
72
+ end
73
+ end
74
+
75
+ describe "#clear!" do
76
+ it "removes all keys & values from the cache" do
77
+ cache = Rack::RequestCache::Cache.new
78
+
79
+ cache.cache(:foo) {}
80
+ cache.cache(:bar) {}
81
+
82
+ cache.clear!
83
+
84
+ expect(cache.has_key?(:foo)).to eq false
85
+ expect(cache.has_key?(:bar)).to eq false
86
+ end
87
+ end
88
+
89
+ describe "#fetch" do
90
+ it "returns the cached result for the key when one exists" do
91
+ value = double
92
+ cache = Rack::RequestCache::Cache.new
93
+
94
+ cache.cache(:key) { value }
95
+
96
+ expect(cache.fetch(:key)).to eq value
97
+ end
98
+
99
+ it "raises a KeyError when the key does not exist" do
100
+ cache = Rack::RequestCache::Cache.new
101
+ expect{ cache.fetch(:key) }.to raise_error KeyError
102
+ end
103
+ end
104
+
105
+ describe "#has_key?" do
106
+ it "returns true when the key has been cached" do
107
+ cache = Rack::RequestCache::Cache.new
108
+ cache.cache(:key) {}
109
+ expect(cache.has_key?(:key)).to eq true
110
+ end
111
+
112
+ it "returns false when the key has not been cached" do
113
+ cache = Rack::RequestCache::Cache.new
114
+ expect(cache.has_key?(:key)).to eq false
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,86 @@
1
+ require 'rack/request_cache'
2
+
3
+ require 'rack/builder'
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+ require 'rack/response'
7
+
8
+ describe Rack::RequestCache do
9
+ def join_body body_obj
10
+ body = ''
11
+ body_obj.each { |s| body << s }
12
+ body
13
+ end
14
+
15
+ def unfreezable_double *args
16
+ double(*args).tap { |d| d.stub(:freeze).and_return d }
17
+ end
18
+
19
+ it "is a Rack middleware" do
20
+ app = Rack::Builder.app do
21
+ use Rack::Lint
22
+ use Rack::RequestCache
23
+ run ->(env) { [418, { 'A-Header' => 'Stuff' }, ['the body']] }
24
+ end
25
+
26
+ request = Rack::MockRequest.env_for '/'
27
+ status, headers, body_obj = app.call request
28
+ body = join_body(body_obj)
29
+
30
+ expect(status).to eq 418
31
+ expect(headers).to eq ({ 'A-Header' => 'Stuff' })
32
+ expect(body).to eq 'the body'
33
+ end
34
+
35
+ it "clears the cache after the request ends" do
36
+ app = ->(env) do
37
+ Rack::RequestCache.cache(:foo) { 'bar' }
38
+ expect(Rack::RequestCache.fetch(:foo)).to eq 'bar'
39
+ end
40
+
41
+ request_cache = Rack::RequestCache.new app
42
+ request_cache.call({})
43
+
44
+ expect(Rack::RequestCache.has_key?(:foo)).to eq false
45
+ end
46
+
47
+ it "clears the cache when the request raises an error, reraising it" do
48
+ exception_class = Class.new Exception
49
+ app = ->(env) do
50
+ Rack::RequestCache.cache(:foo) { 'bar' }
51
+ expect(Rack::RequestCache.fetch(:foo)).to eq 'bar'
52
+ raise exception_class
53
+ end
54
+
55
+ request_cache = Rack::RequestCache.new app
56
+ expect { request_cache.call({}) }.to raise_error exception_class
57
+
58
+ expect(Rack::RequestCache.has_key?(:foo)).to eq false
59
+ end
60
+
61
+ it "is thread safe" do
62
+ q1 = Queue.new
63
+ q2 = Queue.new
64
+ app = ->(env) do
65
+ Rack::RequestCache.cache(:foo) { env }
66
+ env.queue.pop
67
+ expect(Rack::RequestCache.fetch(:foo)).to eq env
68
+ env.queue.pop
69
+ end
70
+ env_1 = unfreezable_double 'one', queue: q1
71
+ env_2 = unfreezable_double 'two', queue: q2
72
+
73
+ request_cache = Rack::RequestCache.new app
74
+
75
+ thread_1 = Thread.new { request_cache.call env_1 }
76
+ thread_2 = Thread.new { request_cache.call env_2 }
77
+
78
+ q1 << nil
79
+ q2 << nil
80
+ q1 << nil
81
+ q2 << nil
82
+
83
+ thread_1.join
84
+ thread_2.join
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-request_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Marshall
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thread_variable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
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.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '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: '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'
83
+ description: Provides a caching layer that exists only within a single Rack request.
84
+ email:
85
+ - andrew@johnandrewmarshall.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .travis.yml
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - lib/rack-request_cache.rb
97
+ - lib/rack/request_cache.rb
98
+ - lib/rack/request_cache/cache.rb
99
+ - lib/rack/request_cache/version.rb
100
+ - rack-request_cache.gemspec
101
+ - spec/rack/request_cache/cache_spec.rb
102
+ - spec/rack/request_cache_spec.rb
103
+ homepage: http://johnandrewmarshall.com/projects/rack-request_cache
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.0.5
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Provides a caching layer that exists only within a single Rack request.
127
+ test_files:
128
+ - spec/rack/request_cache/cache_spec.rb
129
+ - spec/rack/request_cache_spec.rb
130
+ has_rdoc: