cachesrb 0.2.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.
- data/README +24 -0
- data/Rakefile +52 -0
- data/init.rb +7 -0
- data/lib/caches.rb +68 -0
- data/test/caches_test.rb +178 -0
- data/test/time_mock.rb +23 -0
- metadata +51 -0
data/README
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Caches.rb
|
2
|
+
=========
|
3
|
+
|
4
|
+
Caches.rb is a simplistic method cache for Ruby
|
5
|
+
|
6
|
+
Simply do `BaseClass.extend Caches' and you will be able to specify
|
7
|
+
|
8
|
+
caches :method_name
|
9
|
+
or
|
10
|
+
caches :method_name, :timeout => 2.minutes
|
11
|
+
|
12
|
+
Default timeout is 60 seconds.
|
13
|
+
|
14
|
+
What is important is that this solution caches calls with arguments. For example, if you will make a method_name(“Hello”) call you’ll get next method_name(“Hello”) cached, but method_name(“Bye”) will not be cached.
|
15
|
+
|
16
|
+
Also you will be able to invalidate cache explicitly with invalidate_method_name_cache and invalidate_all_method_name_caches calls.
|
17
|
+
|
18
|
+
Also you can invalidate caches with
|
19
|
+
|
20
|
+
invalidate_all_caches
|
21
|
+
or
|
22
|
+
invalidate_all_caches :except => :name
|
23
|
+
or
|
24
|
+
invalidate_all_caches :except => [:name, :name1]
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test the request_routing plugin.'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the Caches.rb plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'Caches.rb'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
PKG_VERSION = "0.2.0"
|
26
|
+
PKG_NAME = "cachesrb"
|
27
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
28
|
+
|
29
|
+
PKG_FILES = FileList[
|
30
|
+
"lib/**/*", "test/**/*", "[A-Z]*", "Rakefile", "init.rb"
|
31
|
+
].exclude(/\bCVS\b|~$|\.svn/)
|
32
|
+
|
33
|
+
spec = Gem::Specification.new do |s|
|
34
|
+
s.name = PKG_NAME
|
35
|
+
s.version = PKG_VERSION
|
36
|
+
s.summary = "Caches.rb -- simple Ruby method caching"
|
37
|
+
s.has_rdoc = true
|
38
|
+
s.files = PKG_FILES
|
39
|
+
|
40
|
+
s.require_path = 'lib'
|
41
|
+
s.autorequire = 'caches'
|
42
|
+
s.author = "Yurii Rashkovskii"
|
43
|
+
s.email = "yrashk@verbdev.com"
|
44
|
+
s.homepage = "http://rashkovskii.com/articles/tag/caches"
|
45
|
+
s.rubyforge_project = "cachesrb"
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::GemPackageTask.new(spec) do |p|
|
49
|
+
p.gem_spec = spec
|
50
|
+
p.need_tar = true
|
51
|
+
p.need_zip = true
|
52
|
+
end
|
data/init.rb
ADDED
data/lib/caches.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
class String
|
2
|
+
def starts_with?(prefix)
|
3
|
+
prefix = prefix.to_s
|
4
|
+
self[0, prefix.length] == prefix
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Caches
|
9
|
+
def caches(name,options = { :timeout => 60})
|
10
|
+
sanitized_name = name.to_s.delete('?')
|
11
|
+
saved_getter = "getter_#{name}"
|
12
|
+
saved_setter = "setter_#{name}"
|
13
|
+
setter = "#{name}="
|
14
|
+
|
15
|
+
has_setter = new.respond_to? setter.to_sym
|
16
|
+
|
17
|
+
module_eval "alias #{saved_getter} #{name}"
|
18
|
+
module_eval "alias #{saved_setter} #{setter.to_sym}" if has_setter
|
19
|
+
module_eval do
|
20
|
+
define_method("invalidate_all_caches") do |*opts|
|
21
|
+
unless opts.empty?
|
22
|
+
opthash = opts.first
|
23
|
+
except = opthash[:except]
|
24
|
+
if except
|
25
|
+
except = [except] unless except.kind_of? Enumerable
|
26
|
+
@propcache.each_pair do |k,v|
|
27
|
+
@propcache[k] = nil unless except.any? {|exception| k.starts_with?(exception.to_s)}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
@propcache = {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
define_method("invalidate_#{sanitized_name}_cache") do |*args|
|
35
|
+
@propcache ||= {}
|
36
|
+
key = name.to_s+args.hash.to_s
|
37
|
+
@propcache[key] = nil
|
38
|
+
end
|
39
|
+
define_method("invalidate_all_#{sanitized_name}_caches") do
|
40
|
+
@propcache ||= {}
|
41
|
+
key = name.to_s
|
42
|
+
@propcache.keys.each {|k| @propcache[k] = nil if k.starts_with?(key) }
|
43
|
+
end
|
44
|
+
define_method("#{name}") do |*args| # FIXME: this implementation smells bad
|
45
|
+
@propcache ||= {}
|
46
|
+
key = name.to_s+args.hash.to_s
|
47
|
+
cached = @propcache[key]
|
48
|
+
unless cached
|
49
|
+
@propcache[key] = { :value => self.send(saved_getter.to_sym,*args), :expires_at => Time.now.to_i + options[:timeout]}
|
50
|
+
return @propcache[key][:value]
|
51
|
+
else
|
52
|
+
unless Time.now.to_i > cached[:expires_at]
|
53
|
+
cached[:value]
|
54
|
+
else
|
55
|
+
self.send "invalidate_#{sanitized_name}_cache".to_sym, *args
|
56
|
+
self.send name.to_sym, *args
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if has_setter
|
61
|
+
define_method("#{setter}") do |new_val|
|
62
|
+
self.send "invalidate_#{sanitized_name}_cache".to_sym
|
63
|
+
self.send saved_setter.to_sym, new_val
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/test/caches_test.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/time_mock'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + "/../init"
|
5
|
+
|
6
|
+
class CachedBase ; end ; CachedBase.extend Caches
|
7
|
+
|
8
|
+
|
9
|
+
class CachedClass < CachedBase
|
10
|
+
|
11
|
+
attr_reader :accessor_counter
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@accessor = "Value"
|
15
|
+
@accessor_counter = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def accessor
|
19
|
+
@accessor_counter += 1
|
20
|
+
@accessor
|
21
|
+
end
|
22
|
+
|
23
|
+
def accessor=(new_val)
|
24
|
+
@accessor = new_val
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :accessor2 :accessor
|
28
|
+
|
29
|
+
caches :accessor
|
30
|
+
caches :accessor2, :timeout => 120
|
31
|
+
|
32
|
+
def method_with_args(a,b)
|
33
|
+
"#{a}#{b}#{Time.now}"
|
34
|
+
end
|
35
|
+
|
36
|
+
caches :method_with_args
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class CachesTest < Test::Unit::TestCase
|
41
|
+
|
42
|
+
def setup
|
43
|
+
@cached = CachedClass.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def teardwon
|
47
|
+
Time.forced_now_time = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_default_timeout
|
51
|
+
val = @cached.accessor
|
52
|
+
assert_equal 1, @cached.accessor_counter
|
53
|
+
Time.forced_now_time = Time.now
|
54
|
+
10.times { @cached.accessor }
|
55
|
+
assert_equal 1, @cached.accessor_counter
|
56
|
+
# In 60 seconds, we still cache
|
57
|
+
Time.forced_now_time = Time.now + 60
|
58
|
+
val = @cached.accessor
|
59
|
+
assert_equal 1, @cached.accessor_counter
|
60
|
+
# In 61, cache is invalidated
|
61
|
+
Time.forced_now_time = Time.now + 61
|
62
|
+
val = @cached.accessor
|
63
|
+
assert_equal 2, @cached.accessor_counter
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_not_default_timeout
|
67
|
+
val = @cached.accessor2
|
68
|
+
assert_equal 1, @cached.accessor_counter
|
69
|
+
Time.forced_now_time = Time.now
|
70
|
+
10.times { @cached.accessor2 }
|
71
|
+
assert_equal 1, @cached.accessor_counter
|
72
|
+
# In 2 minutes, we still cache
|
73
|
+
Time.forced_now_time = Time.now + 120
|
74
|
+
val = @cached.accessor2
|
75
|
+
assert_equal 1, @cached.accessor_counter
|
76
|
+
# In 2m1sec, cache is invalidated
|
77
|
+
Time.forced_now_time = Time.now + 121
|
78
|
+
val = @cached.accessor2
|
79
|
+
assert_equal 2, @cached.accessor_counter
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_invalidate_accessor_by_assignment
|
83
|
+
val = @cached.accessor
|
84
|
+
assert_equal 1, @cached.accessor_counter
|
85
|
+
|
86
|
+
@cached.accessor = "Hello"
|
87
|
+
val = @cached.accessor
|
88
|
+
assert_equal 2, @cached.accessor_counter
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_invalidate_accessor_explicitely
|
92
|
+
val = @cached.accessor
|
93
|
+
assert_equal 1, @cached.accessor_counter
|
94
|
+
|
95
|
+
@cached.invalidate_accessor_cache
|
96
|
+
val = @cached.accessor
|
97
|
+
assert_equal 2, @cached.accessor_counter
|
98
|
+
|
99
|
+
@cached.invalidate_all_accessor_caches
|
100
|
+
val = @cached.accessor
|
101
|
+
assert_equal 3, @cached.accessor_counter
|
102
|
+
|
103
|
+
@cached.invalidate_all_caches
|
104
|
+
val = @cached.accessor
|
105
|
+
assert_equal 4, @cached.accessor_counter
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_caches_method_with_args
|
109
|
+
val = @cached.method_with_args("a","b")
|
110
|
+
valA = @cached.method_with_args("a","C")
|
111
|
+
assert !(val==valA)
|
112
|
+
Time.forced_now_time = Time.now + 1
|
113
|
+
val1 = @cached.method_with_args("a","b")
|
114
|
+
Time.forced_now_time = nil
|
115
|
+
assert_equal val, val1
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def test_invalidate_method_with_args_explicitely
|
120
|
+
# Invalidating with another arguments does not work:
|
121
|
+
val = @cached.method_with_args("a","b")
|
122
|
+
@cached.invalidate_method_with_args_cache("A","B")
|
123
|
+
Time.forced_now_time = Time.now + 1
|
124
|
+
val1 = @cached.method_with_args("a","b")
|
125
|
+
Time.forced_now_time = nil
|
126
|
+
assert (val==val1)
|
127
|
+
|
128
|
+
@cached.invalidate_all_caches
|
129
|
+
|
130
|
+
val = @cached.method_with_args("a","b")
|
131
|
+
@cached.invalidate_method_with_args_cache("a","b")
|
132
|
+
Time.forced_now_time = Time.now + 1
|
133
|
+
val1 = @cached.method_with_args("a","b")
|
134
|
+
Time.forced_now_time = nil
|
135
|
+
assert !(val==val1)
|
136
|
+
|
137
|
+
@cached.invalidate_all_caches
|
138
|
+
|
139
|
+
val = @cached.method_with_args("a","b")
|
140
|
+
@cached.invalidate_all_method_with_args_caches
|
141
|
+
Time.forced_now_time = Time.now + 1
|
142
|
+
val1 = @cached.method_with_args("a","b")
|
143
|
+
Time.forced_now_time = nil
|
144
|
+
assert !(val==val1)
|
145
|
+
|
146
|
+
@cached.invalidate_all_caches
|
147
|
+
|
148
|
+
val = @cached.method_with_args("a","b")
|
149
|
+
@cached.invalidate_all_caches
|
150
|
+
Time.forced_now_time = Time.now + 1
|
151
|
+
val1 = @cached.method_with_args("a","b")
|
152
|
+
Time.forced_now_time = nil
|
153
|
+
assert !(val==val1)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_invalidate_all_caches_except
|
157
|
+
val = @cached.accessor
|
158
|
+
val2 = @cached.accessor2
|
159
|
+
valM = @cached.method_with_args("a","b")
|
160
|
+
@cached.invalidate_all_caches :except => :method_with_args
|
161
|
+
Time.forced_now_time = Time.now + 1
|
162
|
+
valM1 = @cached.method_with_args("a","b")
|
163
|
+
Time.forced_now_time = nil
|
164
|
+
assert (valM==valM1)
|
165
|
+
|
166
|
+
@cached.invalidate_all_caches
|
167
|
+
|
168
|
+
val = @cached.accessor
|
169
|
+
val2 = @cached.accessor2
|
170
|
+
valM = @cached.method_with_args("a","b")
|
171
|
+
@cached.invalidate_all_caches :except => [:method_with_args]
|
172
|
+
Time.forced_now_time = Time.now + 1
|
173
|
+
valM1 = @cached.method_with_args("a","b")
|
174
|
+
Time.forced_now_time = nil
|
175
|
+
assert (valM==valM1)
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/test/time_mock.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Time
|
2
|
+
@@forced_now_time = nil
|
3
|
+
|
4
|
+
def self.forced_now_time
|
5
|
+
@@forced_now_time
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.forced_now_time=(time)
|
9
|
+
@@forced_now_time = time
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def now_with_forcing
|
14
|
+
if @@forced_now_time
|
15
|
+
@@forced_now_time
|
16
|
+
else
|
17
|
+
now_without_forcing
|
18
|
+
end
|
19
|
+
end
|
20
|
+
alias_method :now_without_forcing, :now
|
21
|
+
alias_method :now, :now_with_forcing
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: cachesrb
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2006-09-25 00:00:00 +03:00
|
8
|
+
summary: Caches.rb -- simple Ruby method caching
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: yrashk@verbdev.com
|
12
|
+
homepage: http://rashkovskii.com/articles/tag/caches
|
13
|
+
rubyforge_project: cachesrb
|
14
|
+
description:
|
15
|
+
autorequire: caches
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Yurii Rashkovskii
|
31
|
+
files:
|
32
|
+
- lib/caches.rb
|
33
|
+
- test/caches_test.rb
|
34
|
+
- test/time_mock.rb
|
35
|
+
- Rakefile
|
36
|
+
- README
|
37
|
+
- init.rb
|
38
|
+
test_files: []
|
39
|
+
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
requirements: []
|
49
|
+
|
50
|
+
dependencies: []
|
51
|
+
|