cachy 0.1.3 → 0.1.4

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.
@@ -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