perma_cache 0.0.3
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 +21 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +10 -0
- data/lib/perma_cache.rb +98 -0
- data/lib/perma_cache/version.rb +3 -0
- data/perma_cache.gemspec +29 -0
- data/test/perma_cache_test.rb +154 -0
- data/test/test_helper.rb +9 -0
- metadata +156 -0
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
perma_cache
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p484
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Josh Sharpe
|
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,64 @@
|
|
1
|
+
# PermaCache
|
2
|
+
|
3
|
+
Provides a dsl to add pull-through caching to a given method while
|
4
|
+
also provding an interface to overwrite that cache when necessary.
|
5
|
+
|
6
|
+
Useful for expensive objects that you want to write-once on the backend
|
7
|
+
while still allowing your frontend to rebuild the object if the
|
8
|
+
cache clears for any reason
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'perma_cache'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install perma_cache
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
```
|
27
|
+
class SomeKlass
|
28
|
+
include PermaCache
|
29
|
+
def slow_method
|
30
|
+
sleep 2
|
31
|
+
1
|
32
|
+
end
|
33
|
+
perma_cache :slow_method
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
```
|
38
|
+
> Benchmark.measure{ puts SomeKlass.new.slow_method }.real
|
39
|
+
1
|
40
|
+
=> 2.003525972366333
|
41
|
+
> Benchmark.measure{ puts SomeKlass.new.slow_method }.real
|
42
|
+
1
|
43
|
+
=> 0.001032114028930664
|
44
|
+
> Benchmark.measure{ puts SomeKlass.new.slow_method! }.real
|
45
|
+
1
|
46
|
+
=> 2.0027248859405518
|
47
|
+
```
|
48
|
+
|
49
|
+
## Testing
|
50
|
+
|
51
|
+
Run tests with:
|
52
|
+
```
|
53
|
+
rake
|
54
|
+
```
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Run the test Suite
|
61
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
62
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
6. Create new Pull Request
|
64
|
+
|
data/Rakefile
ADDED
data/lib/perma_cache.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'active_support/core_ext/module/aliasing'
|
2
|
+
|
3
|
+
require "perma_cache/version"
|
4
|
+
|
5
|
+
module PermaCache
|
6
|
+
class UndefinedCache < StandardError ; end
|
7
|
+
|
8
|
+
def self.version= v
|
9
|
+
@version = v
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.version
|
13
|
+
@version || 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cache
|
17
|
+
@cache || raise(UndefinedCache, "Please define a cache object: (PermaCache.cache = Rails.cache)")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.cache= c
|
21
|
+
@cache = c
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.build_key_from_object(obj)
|
25
|
+
# Don't want to add this to Object
|
26
|
+
Array.new.tap do |arr|
|
27
|
+
if obj.respond_to?(:cache_key)
|
28
|
+
arr << obj.cache_key
|
29
|
+
else
|
30
|
+
arr << obj.class.name
|
31
|
+
|
32
|
+
if obj.respond_to?(:id)
|
33
|
+
arr << obj.id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.included(base)
|
40
|
+
base.extend ClassMethods
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def perma_cache(method_name, options = {})
|
45
|
+
class_eval do
|
46
|
+
define_method "#{method_name}_base_key" do
|
47
|
+
key = []
|
48
|
+
key << "perma_cache"
|
49
|
+
key << "v#{PermaCache.version}"
|
50
|
+
|
51
|
+
key << PermaCache.build_key_from_object(self)
|
52
|
+
|
53
|
+
if options[:obj]
|
54
|
+
key << PermaCache.build_key_from_object(send(options[:obj]))
|
55
|
+
end
|
56
|
+
|
57
|
+
key << method_name
|
58
|
+
|
59
|
+
if options[:version]
|
60
|
+
key << "v#{options[:version]}"
|
61
|
+
end
|
62
|
+
|
63
|
+
key = key.flatten.reject do |k|
|
64
|
+
(k.empty? rescue nil) ||
|
65
|
+
(k.nil? rescue nil)
|
66
|
+
end.join('/')
|
67
|
+
|
68
|
+
key
|
69
|
+
end
|
70
|
+
|
71
|
+
define_method "#{method_name}_perma_cache_key" do
|
72
|
+
[
|
73
|
+
send("#{method_name}_base_key"),
|
74
|
+
(send("#{method_name}_key") rescue nil)
|
75
|
+
].compact.join('/').gsub(' ','_')
|
76
|
+
end
|
77
|
+
|
78
|
+
define_method "#{method_name}!" do
|
79
|
+
send("#{method_name}_without_perma_cache").tap do |result|
|
80
|
+
PermaCache.cache.write(send("#{method_name}_perma_cache_key"), result, :expires_in => options[:expires_in])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method "#{method_name}_get_perma_cache" do
|
85
|
+
PermaCache.cache.read(send("#{method_name}_perma_cache_key"))
|
86
|
+
end
|
87
|
+
|
88
|
+
define_method "#{method_name}_with_perma_cache" do
|
89
|
+
send("#{method_name}_get_perma_cache") ||
|
90
|
+
send("#{method_name}!")
|
91
|
+
end
|
92
|
+
|
93
|
+
alias_method_chain method_name, :perma_cache
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
data/perma_cache.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'perma_cache/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "perma_cache"
|
8
|
+
spec.version = PermaCache::VERSION
|
9
|
+
spec.authors = ["Josh Sharpe"]
|
10
|
+
spec.email = ["josh.m.sharpe@gmail.com"]
|
11
|
+
spec.description = %q{It's a perma cache, duh}
|
12
|
+
spec.summary = %q{It's a perma cache, duh}
|
13
|
+
spec.homepage = ""
|
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 "rails"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "shoulda"
|
26
|
+
spec.add_development_dependency "mocha", "< 1.0"
|
27
|
+
spec.add_development_dependency "debugger"
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class KlassOne
|
4
|
+
include PermaCache
|
5
|
+
|
6
|
+
def method1
|
7
|
+
sleep 1
|
8
|
+
1
|
9
|
+
end
|
10
|
+
perma_cache :method1
|
11
|
+
|
12
|
+
def method2
|
13
|
+
sleep 1
|
14
|
+
2
|
15
|
+
end
|
16
|
+
perma_cache :method2, :obj => :other_klass
|
17
|
+
def method2_key
|
18
|
+
"more things"
|
19
|
+
end
|
20
|
+
|
21
|
+
def method3
|
22
|
+
sleep 1
|
23
|
+
3
|
24
|
+
end
|
25
|
+
perma_cache :method3, :version => 2, :expires_in => 5
|
26
|
+
|
27
|
+
def other_klass
|
28
|
+
KlassTwo.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class KlassTwo
|
33
|
+
def cache_key
|
34
|
+
"some_other_class/123"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class KlassThree
|
39
|
+
def id
|
40
|
+
234
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PermaCacheTest < Test::Unit::TestCase
|
45
|
+
|
46
|
+
context "build_key_from_object" do
|
47
|
+
context "for a class" do
|
48
|
+
context "That responds to cache_key" do
|
49
|
+
should "have a correct key" do
|
50
|
+
klass = KlassTwo
|
51
|
+
assert klass.new.respond_to?(:cache_key)
|
52
|
+
assert_equal ["some_other_class/123"], PermaCache.build_key_from_object(klass.new)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
context "that doesn't respond to cache_key" do
|
56
|
+
should "have a correct key" do
|
57
|
+
klass = KlassOne
|
58
|
+
assert !klass.new.respond_to?(:cache_key)
|
59
|
+
assert_equal ["KlassOne"], PermaCache.build_key_from_object(klass.new)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
context "that doesn't respond to cache key and responds to id" do
|
63
|
+
should "have a correct key" do
|
64
|
+
klass = KlassThree
|
65
|
+
assert !klass.new.respond_to?(:cache_key)
|
66
|
+
assert klass.new.respond_to?(:id)
|
67
|
+
assert_equal ["KlassThree", 234], PermaCache.build_key_from_object(klass.new)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "calling cache" do
|
74
|
+
context "without setting a cache source" do
|
75
|
+
setup do
|
76
|
+
PermaCache.send :remove_instance_variable, :@cache
|
77
|
+
end
|
78
|
+
should "raise" do
|
79
|
+
assert_raises PermaCache::UndefinedCache do
|
80
|
+
PermaCache.cache
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "after setting a cache source" do
|
85
|
+
setup do
|
86
|
+
PermaCache.cache = 123
|
87
|
+
end
|
88
|
+
should "return that cache source" do
|
89
|
+
assert_equal 123, PermaCache.cache
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "KlassOne" do
|
95
|
+
should "have some additional methods defined" do
|
96
|
+
obj = KlassOne.new
|
97
|
+
assert obj.respond_to?(:method1_perma_cache_key)
|
98
|
+
assert obj.respond_to?(:method1!)
|
99
|
+
assert obj.respond_to?(:method1_with_perma_cache)
|
100
|
+
end
|
101
|
+
|
102
|
+
should "calling #method1 should write and return the result if the cache is empty" do
|
103
|
+
obj = KlassOne.new
|
104
|
+
cache_obj = mock
|
105
|
+
cache_obj.expects(:read).with(obj.method1_perma_cache_key).once.returns(nil)
|
106
|
+
cache_obj.expects(:write).with(obj.method1_perma_cache_key, 1, :expires_in => nil).once
|
107
|
+
PermaCache.cache = cache_obj
|
108
|
+
obj.expects(:sleep).with(1).once
|
109
|
+
assert_equal 1, obj.method1
|
110
|
+
end
|
111
|
+
|
112
|
+
should "calling #method1 should read the cache, but not write it, if the cache is present" do
|
113
|
+
obj = KlassOne.new
|
114
|
+
cache_obj = mock
|
115
|
+
cache_obj.expects(:read).with(obj.method1_perma_cache_key).once.returns(123)
|
116
|
+
cache_obj.expects(:write).never
|
117
|
+
PermaCache.cache = cache_obj
|
118
|
+
obj.expects(:sleep).never
|
119
|
+
assert_equal 123, obj.method1
|
120
|
+
end
|
121
|
+
|
122
|
+
should "calling #method1! should write the cache, but not read from it" do
|
123
|
+
obj = KlassOne.new
|
124
|
+
cache_obj = mock
|
125
|
+
cache_obj.expects(:read).never
|
126
|
+
cache_obj.expects(:write).with(obj.method1_perma_cache_key, 1, :expires_in => nil).once
|
127
|
+
PermaCache.cache = cache_obj
|
128
|
+
obj.expects(:sleep).with(1).once
|
129
|
+
assert_equal 1, obj.method1!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
context "version option" do
|
133
|
+
should "add that key/value to the cache key" do
|
134
|
+
assert_equal "perma_cache/v1/KlassOne/method3/v2", KlassOne.new.method3_perma_cache_key
|
135
|
+
end
|
136
|
+
end
|
137
|
+
context "user defined keys" do
|
138
|
+
should "should append themselves to the cache key" do
|
139
|
+
assert_equal "perma_cache/v1/KlassOne/some_other_class/123/method2/more_things", KlassOne.new.method2_perma_cache_key
|
140
|
+
end
|
141
|
+
end
|
142
|
+
context "setting expires_in" do
|
143
|
+
should "pass the value through to #write" do
|
144
|
+
obj = KlassOne.new
|
145
|
+
cache_obj = mock
|
146
|
+
cache_obj.expects(:read).with(obj.method3_perma_cache_key).returns(nil).once
|
147
|
+
cache_obj.expects(:write).with(obj.method3_perma_cache_key, 3, :expires_in => 5).once
|
148
|
+
obj.expects(:sleep).with(1).once
|
149
|
+
PermaCache.cache = cache_obj
|
150
|
+
obj.method3
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: perma_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Sharpe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: shoulda
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: mocha
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - <
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - <
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: debugger
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: It's a perma cache, duh
|
111
|
+
email:
|
112
|
+
- josh.m.sharpe@gmail.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- .gitignore
|
118
|
+
- .ruby-gemset
|
119
|
+
- .ruby-version
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- lib/perma_cache.rb
|
125
|
+
- lib/perma_cache/version.rb
|
126
|
+
- perma_cache.gemspec
|
127
|
+
- test/perma_cache_test.rb
|
128
|
+
- test/test_helper.rb
|
129
|
+
homepage: ''
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.25
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: It's a perma cache, duh
|
154
|
+
test_files:
|
155
|
+
- test/perma_cache_test.rb
|
156
|
+
- test/test_helper.rb
|