perma_cache 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
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
18
+ *.swo
19
+ *.swn
20
+ *.swp
21
+
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in perma_cache.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/*_test.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module PermaCache
2
+ VERSION = "0.0.3"
3
+ end
@@ -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
+
@@ -0,0 +1,9 @@
1
+ require "rubygems"
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'debugger'
6
+
7
+ require 'bundler/setup'
8
+ require 'perma_cache'
9
+
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