cachy 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,12 +71,15 @@ When they are updated the cache is automatically expired.
71
71
  Use a global `CACHE_VERSION=1` so that all caches can be expired when something big changes.
72
72
  The cache server does not need to be restarted and session data(Rails) is saved.
73
73
 
74
-
75
74
  #### Does not cache nil
76
75
  If you want to cache a falsy result, use false (same goes for :while_running)
77
76
  Cachy.cache(:x){ expensive || false }
78
77
  Cachy.cache(:x, :while_running=>false){ expensive }
79
78
 
79
+ ###Cachy.cache_if
80
+ Only caches if condition is fulfilled
81
+ Cachy.cache_if(condition, :foo, 'bar', :expires_in => 1.minute){do_something}
82
+
80
83
  ###Cachy.expire / .expire_view
81
84
  Expires all locales of a key
82
85
  Cachy.locales = [:de, :en] # by default filled with I18n.available_locales
@@ -86,20 +89,17 @@ Expires all locales of a key
86
89
  Cachy.expire_view(:my_key)
87
90
  Cachy.expire(:my_key, :prefix=>'views/')
88
91
 
89
-
90
92
  ###Cachy.key
91
93
  Use to cache e.g. Erb output
92
94
  <% cache Cachy.key(:a_key), :expires_in=>1.hour do %>
93
95
  More html ...
94
96
  <% end %>
95
97
 
96
-
97
98
  ###Cachy.cache_store
98
99
  No ActionController::Base.cache_store ?
99
100
  Give me something that responds to read/write(Rails style) or []/store([Moneta](http://github.com/wycats/moneta/tree/master)) or get/set(Memcached)
100
101
  Cachy.cache_store = some_cache
101
102
 
102
-
103
103
  ###Cachy.locales
104
104
  No I18n.available_locales ?
105
105
  Cachy.locales = [:de, :en, :fr]
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  task :default => :spec
2
2
  require 'spec/rake/spectask'
3
- Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
3
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color --backtrace']}
4
4
 
5
5
  begin
6
6
  require 'jeweler'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cachy}
8
- s.version = "0.1.3"
8
+ s.version = "0.1.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Grosser"]
12
- s.date = %q{2009-11-22}
12
+ s.date = %q{2010-08-14}
13
13
  s.email = %q{grosser.michael@gmail.com}
14
14
  s.extra_rdoc_files = [
15
15
  "README.markdown"
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
23
23
  "lib/cachy/memcached_wrapper.rb",
24
24
  "lib/cachy/moneta_wrapper.rb",
25
25
  "lib/cachy/wrapper.rb",
26
- "rdoc/README.rdoc",
27
26
  "spec/cachy/memcached_wrapper_spec.rb",
28
27
  "spec/cachy/moneta_wrapper_spec.rb",
29
28
  "spec/cachy_spec.rb",
@@ -33,14 +32,14 @@ Gem::Specification.new do |s|
33
32
  s.homepage = %q{http://github.com/grosser/cachy}
34
33
  s.rdoc_options = ["--charset=UTF-8"]
35
34
  s.require_paths = ["lib"]
36
- s.rubygems_version = %q{1.3.5}
35
+ s.rubygems_version = %q{1.3.6}
37
36
  s.summary = %q{Caching library for projects that have many processes or many caches}
38
37
  s.test_files = [
39
- "spec/spec_helper.rb",
40
- "spec/test_cache.rb",
41
- "spec/cachy/memcached_wrapper_spec.rb",
38
+ "spec/cachy/memcached_wrapper_spec.rb",
42
39
  "spec/cachy/moneta_wrapper_spec.rb",
43
- "spec/cachy_spec.rb"
40
+ "spec/cachy_spec.rb",
41
+ "spec/test_cache.rb",
42
+ "spec/spec_helper.rb"
44
43
  ]
45
44
 
46
45
  if s.respond_to? :specification_version then
@@ -1,6 +1,10 @@
1
1
  class Cachy
2
2
  WHILE_RUNNING_TMEOUT = 5*60 #seconds
3
3
  KEY_VERSION_TIMEOUT = 30 #seconds
4
+ HEALTH_CHECK_KEY = 'cachy_healthy'
5
+ KEY_VERSIONS_KEY = 'cachy_key_versions'
6
+
7
+ @@cache_error = false
4
8
 
5
9
  # Cache the result of a block
6
10
  #
@@ -13,7 +17,6 @@ class Cachy
13
17
  key = key(*args)
14
18
  options = extract_options!(args)
15
19
 
16
- # Cached result?
17
20
  result = cache_store.read(key)
18
21
  return result unless result == nil
19
22
 
@@ -24,6 +27,14 @@ class Cachy
24
27
  cache_store.write key, result, options
25
28
  result
26
29
  end
30
+
31
+ def self.cache_if(cond, *args, &block)
32
+ if cond
33
+ cache(*args, &block)
34
+ else
35
+ block.call
36
+ end
37
+ end
27
38
 
28
39
  # Constructs a cache-key (first argument must be a String/Symbol)
29
40
  #
@@ -43,7 +54,7 @@ class Cachy
43
54
  end * "_"
44
55
 
45
56
  key = (options[:hash_key] || hash_keys) ? hash(key) : key
46
- options[:prefix].to_s + key + options[:suffix].to_s
57
+ (options[:prefix].to_s + key + options[:suffix].to_s).gsub(' ', '_')
47
58
  end
48
59
 
49
60
  # Expire all possible locales of a cache, use the same arguments as with cache
@@ -72,7 +83,7 @@ class Cachy
72
83
  @@key_versions = {:versions=>{}, :last_set=>0}
73
84
  def self.key_versions
74
85
  if key_versions_expired?
75
- versions = cache_store.read("cachy_key_versions") || {}
86
+ versions = read_versions
76
87
  @@key_versions = {:versions=>versions, :last_set=>Time.now.to_i}
77
88
  end
78
89
  @@key_versions[:versions]
@@ -80,22 +91,29 @@ class Cachy
80
91
 
81
92
  def self.key_versions=(data)
82
93
  @@key_versions[:last_set] = 0 #expire current key
83
- cache_store.write("cachy_key_versions", data)
94
+ write_version(data)
84
95
  end
85
96
 
86
97
  # Expires all caches that use this key
87
98
  def self.increment_key(key)
88
99
  key = key.to_sym
89
- version = key_versions[key] || 0
100
+ current_versions = read_versions
101
+ version = current_versions[key] || 0
90
102
  version += 1
91
- self.key_versions = key_versions.merge(key => version)
103
+ self.key_versions = current_versions.merge(key => version)
92
104
  version
93
105
  end
94
106
 
107
+ def self.delete_key(key)
108
+ versions = key_versions.dup
109
+ versions.delete(key.to_sym)
110
+ self.key_versions = versions
111
+ end
112
+
95
113
  class << self
96
114
  attr_accessor :hash_keys
97
115
  end
98
-
116
+
99
117
  # Wrap non ActiveSupport style cache stores,
100
118
  # to get the same interface for all
101
119
  def self.cache_store=(x)
@@ -110,6 +128,7 @@ class Cachy
110
128
  else
111
129
  raise "This cache_store type is not usable for Cachy!"
112
130
  end
131
+ @cache_store.write HEALTH_CHECK_KEY, 'yes'
113
132
  end
114
133
 
115
134
  def self.cache_store
@@ -135,6 +154,25 @@ class Cachy
135
154
 
136
155
  private
137
156
 
157
+ def self.read_versions
158
+ data = cache_store.instance_variable_get('@data')
159
+ if data.respond_to? :read_error_occured # its a MemCache with read_error detection !
160
+ result = data[KEY_VERSIONS_KEY] || {}
161
+ @@cache_error = data.read_error_occured
162
+ result
163
+ else
164
+ cache_store.read(KEY_VERSIONS_KEY) || {}
165
+ end
166
+ end
167
+
168
+ def self.write_version(data)
169
+ cache_store.write(KEY_VERSIONS_KEY, data) unless @@cache_error
170
+ end
171
+
172
+ def self.cache_healthy?
173
+ cache_store.read(HEALTH_CHECK_KEY) == 'yes'
174
+ end
175
+
138
176
  # Do we need to fetch fresh key_versions from cache ?
139
177
  def self.key_versions_expired?
140
178
  key_versions_timeout = Time.now.to_i - KEY_VERSION_TIMEOUT
@@ -167,7 +205,7 @@ class Cachy
167
205
 
168
206
  def self.key_version_for(key)
169
207
  key = key.to_sym
170
- key_versions[key] || increment_key(key)
208
+ key_versions[key] || (cache_healthy? ? increment_key(key) : 1)
171
209
  end
172
210
 
173
211
  def self.ensure_valid_keys(options)
@@ -1,27 +1,25 @@
1
1
  require 'spec/spec_helper'
2
2
 
3
- TEST_CACHE = TestCache.new
4
-
5
3
  describe Cachy do
6
- before :all do
7
- Cachy.cache_store = TEST_CACHE
8
- end
9
-
10
4
  before do
11
- TEST_CACHE.clear
5
+ @cache = TestCache.new
6
+ Cachy.cache_store = @cache
7
+ Cachy.class_eval "@@key_versions = {:versions=>{}, :last_set=>0}"
8
+ @cache.write(Cachy::HEALTH_CHECK_KEY, 'yes')
9
+ Cachy.send(:class_variable_set, '@@cache_error', false)
12
10
  end
13
-
11
+
14
12
  describe :cache do
15
13
  it "caches" do
16
14
  Cachy.cache(:my_key){ "X" }.should == "X"
17
15
  Cachy.cache(:my_key){ "ABC" }.should == "X"
18
16
  end
19
-
17
+
20
18
  it "expires" do
21
19
  Cachy.cache(:his_key, :expires_in=> -100){ 'X' }.should == 'X'
22
20
  Cachy.cache(:his_key, :expires_in=> -100){ 'X' }.should == 'X'
23
21
  end
24
-
22
+
25
23
  it "sets cache to intermediate value while running expensive query" do
26
24
  Cachy.cache(:my_key, :while_running=>'A') do
27
25
  Cachy.cache(:my_key){ 'X' }.should == 'A'
@@ -51,6 +49,29 @@ describe Cachy do
51
49
  Cachy.cache(:x){ true }.should == true
52
50
  end
53
51
  end
52
+
53
+ describe :cache_if do
54
+ it "should not call the cache command if condition is wrong" do
55
+ Cachy.should_not_receive(:cache)
56
+ Cachy.cache_if(false, :x) do
57
+ "asd"
58
+ end
59
+ end
60
+
61
+ it "should call cache command if condition is true" do
62
+ Cachy.should_receive(:cache)
63
+ Cachy.cache_if(true, :x) do
64
+ "asd"
65
+ end
66
+ end
67
+
68
+ it "should pass params correctly" do
69
+ Cachy.should_receive(:cache).with(:x, {:y => 1}, :expires_in => 3)
70
+ Cachy.cache_if(true, :x, {:y => 1}, :expires_in => 3) do
71
+ "asd"
72
+ end
73
+ end
74
+ end
54
75
 
55
76
  describe :expire do
56
77
  it "expires the cache for all languages" do
@@ -62,10 +83,10 @@ describe Cachy do
62
83
  Cachy.cache(:my_key){ l }
63
84
  end
64
85
 
65
- TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 4
86
+ @cache.keys.select{|k| k=~ /my_key/}.size.should == 4
66
87
  Cachy.stub!(:locales).and_return locales
67
88
  Cachy.expire(:my_key)
68
- TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 0
89
+ @cache.keys.select{|k| k=~ /my_key/}.size.should == 0
69
90
  end
70
91
 
71
92
  it "does not expire other keys" do
@@ -73,35 +94,35 @@ describe Cachy do
73
94
  Cachy.cache(:my_key){ 'X' }
74
95
  Cachy.cache(:yet_another_key){ 'X' }
75
96
  Cachy.expire :my_key
76
- TEST_CACHE.keys.should include("another_key_v1")
77
- TEST_CACHE.keys.should include("yet_another_key_v1")
97
+ @cache.keys.should include("another_key_v1")
98
+ @cache.keys.should include("yet_another_key_v1")
78
99
  end
79
100
 
80
101
  it "expires the cache when no available_locales are set" do
81
102
  Cachy.cache(:another_key){ "X" }
82
103
  Cachy.cache(:my_key){ "X" }
83
104
 
84
- TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 1
105
+ @cache.keys.select{|k| k=~ /my_key/}.size.should == 1
85
106
  Cachy.expire(:my_key)
86
- TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 0
107
+ @cache.keys.select{|k| k=~ /my_key/}.size.should == 0
87
108
  end
88
109
 
89
110
  it "expires the cache with prefix" do
90
111
  key = 'views/my_key_v1'
91
- TEST_CACHE.write(key, 'x')
92
- TEST_CACHE.read(key).should_not == nil
112
+ @cache.write(key, 'x')
113
+ @cache.read(key).should_not == nil
93
114
  Cachy.expire(:my_key, :prefix=>'views/')
94
- TEST_CACHE.read(key).should == nil
115
+ @cache.read(key).should == nil
95
116
  end
96
117
  end
97
118
 
98
119
  describe :expire_view do
99
120
  it "expires the cache with prefix" do
100
121
  key = 'views/my_key_v1'
101
- TEST_CACHE.write(key, 'x')
102
- TEST_CACHE.read(key).should_not == nil
122
+ @cache.write(key, 'x')
123
+ @cache.read(key).should_not == nil
103
124
  Cachy.expire_view(:my_key)
104
- TEST_CACHE.read(key).should == nil
125
+ @cache.read(key).should == nil
105
126
  end
106
127
  end
107
128
 
@@ -121,11 +142,15 @@ describe Cachy do
121
142
  end
122
143
 
123
144
  it "gets the locale from I18n" do
124
- module I18n
145
+ # a bit weird but just module I18n does not work with 1.9.1
146
+ i18n = Module.new
147
+ i18n.class_eval do
125
148
  def self.locale
126
149
  :de
127
150
  end
128
151
  end
152
+ Object.const_set :I18n, i18n
153
+
129
154
  key = Cachy.key(:x)
130
155
  Object.send :remove_const, :I18n #cleanup
131
156
  key.should == "x_v1_de"
@@ -205,13 +230,24 @@ describe Cachy do
205
230
 
206
231
  describe :key_versions do
207
232
  before do
208
- Cachy.key_versions = {}
233
+ Cachy.key_versions = nil
209
234
  Cachy.key_versions.should == {}
210
235
  end
211
236
 
237
+ it "merges in old when setting new" do
238
+ pending '!!!!!'
239
+ end
240
+
212
241
  it "adds a key when cache is called the first time" do
213
242
  Cachy.cache(:xxx){1}
214
243
  Cachy.key_versions[:xxx].should == 1
244
+ @cache.read(Cachy::KEY_VERSIONS_KEY).should_not == nil
245
+ end
246
+
247
+ it "does not add a key when cache is called the first time and cache is not healthy" do
248
+ @cache.write(Cachy::HEALTH_CHECK_KEY, 'no')
249
+ Cachy.cache(:xxx){1}
250
+ @cache.read(Cachy::KEY_VERSIONS_KEY).should == nil
215
251
  end
216
252
 
217
253
  it "adds a string key as symbol" do
@@ -228,13 +264,13 @@ describe Cachy do
228
264
 
229
265
  it "reloads when keys have expired" do
230
266
  Cachy.send :class_variable_set, "@@key_versions", {:versions=>{:xx=>2}, :last_set=>(Time.now.to_i - 60)}
231
- TEST_CACHE.write 'cachy_key_versions', {:xx=>1}
267
+ @cache.write Cachy::KEY_VERSIONS_KEY, {:xx=>1}
232
268
  Cachy.key_versions.should == {:xx=>1}
233
269
  end
234
270
 
235
271
  it "does not reload when keys have not expired" do
236
272
  Cachy.send :class_variable_set, "@@key_versions", {:versions=>{:xx=>2}, :last_set=>Time.now.to_i}
237
- TEST_CACHE.write 'cachy_key_versions', {:xx=>1}
273
+ @cache.write Cachy::KEY_VERSIONS_KEY, {:xx=>1}
238
274
  Cachy.key_versions.should == {:xx=>2}
239
275
  end
240
276
 
@@ -244,4 +280,68 @@ describe Cachy do
244
280
  Cachy.key_versions[:xx].should == 1
245
281
  end
246
282
  end
283
+
284
+ describe :key_versions, "with timeout" do
285
+ before do
286
+ @mock = mock = {}
287
+ @cache.instance_eval{@data = mock}
288
+ def @mock.read_error_occured
289
+ false
290
+ end
291
+ def @mock.[](x)
292
+ {:x => 1}
293
+ end
294
+ Cachy.send(:class_variable_set, '@@cache_error', false)
295
+ end
296
+
297
+ it "reads normally" do
298
+ Cachy.send(:read_versions).should == {:x => 1}
299
+ end
300
+
301
+ it "reads empty when it crashes" do
302
+ @mock.should_receive(:[]).and_return nil # e.g. Timout happended
303
+ @mock.should_receive(:read_error_occured).and_return true
304
+ Cachy.send(:read_versions).should == {}
305
+ end
306
+
307
+ it "marks as error when it crashes" do
308
+ Cachy.send(:class_variable_get, '@@cache_error').should == false
309
+ @mock.should_receive(:read_error_occured).and_return true
310
+ Cachy.send(:read_versions)
311
+ Cachy.send(:class_variable_get, '@@cache_error').should == true
312
+ end
313
+
314
+ it "marks as error free when it reads successfully" do
315
+ Cachy.send(:class_variable_set, '@@cache_error', true)
316
+ Cachy.send(:class_variable_get, '@@cache_error').should == true
317
+ Cachy.send(:read_versions).should == {:x => 1}
318
+ Cachy.send(:class_variable_get, '@@cache_error').should == false
319
+ end
320
+
321
+ it "writes when it was not error before" do
322
+ Cachy.cache_store.should_receive(:write)
323
+ Cachy.send(:write_version, {})
324
+ end
325
+
326
+ it "does not write when it was error before" do
327
+ Cachy.send(:class_variable_set, '@@cache_error', true)
328
+ Cachy.cache_store.should_not_receive(:write)
329
+ Cachy.send(:write_version, {})
330
+ end
331
+ end
332
+
333
+ describe :delete_key do
334
+ it "removes a key from key versions" do
335
+ Cachy.cache(:xxx){1}
336
+ Cachy.key_versions.key?(:xxx).should == true
337
+ Cachy.delete_key :xxx
338
+ Cachy.key_versions.key?(:xxx).should == false
339
+ end
340
+
341
+ it "does not crash with unfound key" do
342
+ Cachy.delete_key :xxx
343
+ Cachy.delete_key :xxx
344
+ Cachy.key_versions.key?(:xxx).should == false
345
+ end
346
+ end
247
347
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cachy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 4
9
+ version: 0.1.4
5
10
  platform: ruby
6
11
  authors:
7
12
  - Michael Grosser
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-11-22 00:00:00 +01:00
17
+ date: 2010-08-14 00:00:00 +02:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -30,7 +35,6 @@ files:
30
35
  - lib/cachy/memcached_wrapper.rb
31
36
  - lib/cachy/moneta_wrapper.rb
32
37
  - lib/cachy/wrapper.rb
33
- - rdoc/README.rdoc
34
38
  - spec/cachy/memcached_wrapper_spec.rb
35
39
  - spec/cachy/moneta_wrapper_spec.rb
36
40
  - spec/cachy_spec.rb
@@ -49,24 +53,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
53
  requirements:
50
54
  - - ">="
51
55
  - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
52
58
  version: "0"
53
- version:
54
59
  required_rubygems_version: !ruby/object:Gem::Requirement
55
60
  requirements:
56
61
  - - ">="
57
62
  - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
58
65
  version: "0"
59
- version:
60
66
  requirements: []
61
67
 
62
68
  rubyforge_project:
63
- rubygems_version: 1.3.5
69
+ rubygems_version: 1.3.6
64
70
  signing_key:
65
71
  specification_version: 3
66
72
  summary: Caching library for projects that have many processes or many caches
67
73
  test_files:
68
- - spec/spec_helper.rb
69
- - spec/test_cache.rb
70
74
  - spec/cachy/memcached_wrapper_spec.rb
71
75
  - spec/cachy/moneta_wrapper_spec.rb
72
76
  - spec/cachy_spec.rb
77
+ - spec/test_cache.rb
78
+ - spec/spec_helper.rb
@@ -1 +0,0 @@
1
- documentation is at http://github.com/grosser/cachy