cachy 0.1.2

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.
@@ -0,0 +1,120 @@
1
+ Caching library to simplify and organize caching.
2
+
3
+ - I18n (seperate caches by locale / expires all locales)
4
+ - Generation based (expire all caches of one type)
5
+ - Simultanouse caching (multiple processes trying to write same cache at once)
6
+ - Dependent caches (x caches result of cache z+y -> z changes -> x changes)
7
+ - Hashed keys (optional -> short/unreadable)
8
+ - Global cache_version (expire everything Cachy cached)
9
+ - ...
10
+ - works out of the box with Rails
11
+ - works with pure Memcache and [Moneta](http://github.com/wycats/moneta/tree/master)(-> Tokyo Cabinet / CouchDB / S3 / Berkeley DB / DataMapper / Memory store)
12
+
13
+ Install
14
+ =======
15
+ As Gem: ` sudo gem install cachy `
16
+ Or as Rails plugin: ` script/plugins install git://github.com/grosser/cachy.git `
17
+
18
+
19
+ Usage
20
+ =====
21
+ ###Cachy.cache
22
+ result = Cachy.cache(:a_key){ expensive() }
23
+ result = Cachy.cache(:a_key, :expires_in => 1.minute){ expensive() }
24
+ result = Cachy.cache(:a_key, 'something else', Date.today.day){ expensive() }
25
+
26
+ ####Cache expensive operation that is run many times by many processes
27
+ Example scenario: at application Startup 20 processes try to set the same cache -> 20 heavy database requests -> database timeout -> cache still empty -> ... -> death
28
+
29
+ # 19 Processes get [], 1 makes the request -- when cached all get the same result
30
+ result = Cachy.cache(:a_key, :while_running=>[]){ block_db_for_5_seconds }
31
+
32
+
33
+ ####Seperate version for each key
34
+ Expire all all caches of one kind when e.g. codebase has been updated
35
+
36
+ 100.times{ Cachy.cache(:a_key, rand(100000) ){ expensive() } }
37
+ Cachy.increment_key(:a_key) --> everything expired
38
+
39
+
40
+ ####Uses I18n.locale if available
41
+ Cachy.cache(:a_key){ 'English' }
42
+ I18n.locale = :de
43
+ Cachy.cache(:a_key){ 'German' } != 'English'
44
+
45
+ ####Explicitly not use I18n.locale
46
+ Cachy.cache(:a_key, :witout_locale=>true){ 'English' }
47
+ I18n.locale = :de
48
+ Cachy.cache(:a_key, :witout_locale=>true){ 'German' } == 'English'
49
+
50
+ ####Caching results of other caches
51
+ When inner cache is expired outer cache would normally still shows old results.
52
+ --> expire outer cache when inner cache is expired.
53
+
54
+ a = Cachy.cache(:a, :expires_in=>1.day){ expensive() }
55
+ b = Cachy.cache(:b, :expires_in=>1.week){ expensive_2() }
56
+ Cachy.cache(:surrounding, :expires_in=>5.hours, :keys=>[:a, :b]){ a + b * c }
57
+ Cachy.increment_key(:b) --> expires :b and :surrounding
58
+
59
+ ####Hashing keys
60
+ In case they get to long for your caching backend, makes them short but unreadable.
61
+
62
+ Cachy.hash_keys = true # global
63
+ Cachy.cache(:a_key, :hash_key=>true){ expensive } # per call
64
+
65
+ #### Uses .cache_key when available
66
+ E.g. ActiveRecord objects are stored in the key with their updated_at timestamp.
67
+ When they are updated the cache is automatically expired.
68
+
69
+ Cachy.cache(:my_key, User.first){ expensive }
70
+
71
+ #### Uses CACHE_VERSION if defined
72
+ Use a global `CACHE_VERSION=1` so that all caches can be expired when something big changes.
73
+ The cache server does not need to be restarted and session data(Rails) is saved.
74
+
75
+
76
+ #### Does not cache nil
77
+ If you want to cache a falsy result, use false (same goes for :while_running)
78
+ Cachy.cache(:x){ expensive || false }
79
+ Cachy.cache(:x, :while_running=>false){ expensive }
80
+
81
+ ###Cachy.expire / .expire_view
82
+ Expires all locales of a key
83
+ Cachy.locales = [:de, :en] # by default filled with I18n.available_locales
84
+ Cachy.expire(:my_key) -> expires for :de, :en and no-locale
85
+
86
+ #expire "views/#{key}" (counterpart for Rails-view-caching)
87
+ Cachy.expire_view(:my_key)
88
+ Cachy.expire(:my_key, :prefix=>'views/')
89
+
90
+
91
+ ###Cachy.key
92
+ Use to cache e.g. Erb output
93
+ <% cache Cachy.key(:a_key), :expires_in=>1.hour do %>
94
+ More html ...
95
+ <% end %>
96
+
97
+
98
+ ###Cachy.cache_store
99
+ No ActionController::Base.cache_store ?
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)
101
+ Cachy.cache_store = some_cache
102
+
103
+
104
+ ###Cachy.locales
105
+ No I18n.available_locales ?
106
+ Cachy.locales = [:de, :en, :fr]
107
+
108
+ TODO
109
+ ====
110
+ - optionally store dependent keys (:keys=>xxx), so that they can be setup up once and dont need to be remembered
111
+
112
+ Authors
113
+ =======
114
+
115
+ ###Contributors
116
+ - [mindreframer](http://www.simplewebapp.de/roman)
117
+
118
+ [Michael Grosser](http://pragmatig.wordpress.com)
119
+ grosser.michael@gmail.com
120
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
@@ -0,0 +1,30 @@
1
+ desc "Run all specs in spec directory"
2
+ task :default do
3
+ options = "--colour --format progress --loadby --reverse"
4
+ files = FileList['spec/**/*_spec.rb']
5
+ system("spec #{options} #{files}")
6
+ end
7
+
8
+
9
+ begin
10
+ require 'jeweler'
11
+ project_name = 'cachy'
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = project_name
14
+ gem.summary = "Caching library for projects that have many processes or many caches"
15
+ gem.email = "grosser.michael@gmail.com"
16
+ gem.homepage = "http://github.com/grosser/#{project_name}"
17
+ gem.authors = ["Michael Grosser"]
18
+ gem.rubyforge_project = project_name
19
+ end
20
+
21
+ # fake task so that rubyforge:release works
22
+ task :rdoc do
23
+ `mkdir rdoc`
24
+ `echo documentation is at http://github.com/grosser/#{project_name} > rdoc/README.rdoc`
25
+ end
26
+
27
+ Jeweler::RubyforgeTasks.new
28
+ rescue LoadError
29
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
30
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cachy}
8
+ s.version = "0.1.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2009-10-16}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "README.markdown"
16
+ ]
17
+ s.files = [
18
+ "README.markdown",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "cachy.gemspec",
22
+ "lib/cachy.rb",
23
+ "lib/cachy/memcached_wrapper.rb",
24
+ "lib/cachy/moneta_wrapper.rb",
25
+ "lib/cachy/wrapper.rb",
26
+ "spec/cachy/memcached_wrapper_spec.rb",
27
+ "spec/cachy/moneta_wrapper_spec.rb",
28
+ "spec/cachy_spec.rb",
29
+ "spec/spec_helper.rb",
30
+ "spec/test_cache.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/grosser/cachy}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubyforge_project = %q{cachy}
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{Caching library for projects that have many processes or many caches}
38
+ s.test_files = [
39
+ "spec/test_cache.rb",
40
+ "spec/cachy/memcached_wrapper_spec.rb",
41
+ "spec/cachy/moneta_wrapper_spec.rb",
42
+ "spec/spec_helper.rb",
43
+ "spec/cachy_spec.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
@@ -0,0 +1,194 @@
1
+ class Cachy
2
+ WHILE_RUNNING_TMEOUT = 5*60 #seconds
3
+ KEY_VERSION_TIMEOUT = 30 #seconds
4
+
5
+ # Cache the result of a block
6
+ #
7
+ # Cachy.cache(:my_key){ expensive() }
8
+ # Cachy.cache(:my_key, :expires_in=>1.hour){ expensive() }
9
+ # Cachy.cache(:my_key, :keys=>[:dependent_key]){ expensive() }
10
+ # Cachy.cache(:my_key, :without_locale=>true){ expensive() }
11
+ # Cachy.cache(:my_key, :hash_key=>true){ expensive() }
12
+ def self.cache(*args)
13
+ key = key(*args)
14
+ options = extract_options!(args)
15
+
16
+ # Cached result?
17
+ result = cache_store.read(key)
18
+ return result unless result == nil
19
+
20
+ # Calculate result!
21
+ set_while_running(key, options)
22
+
23
+ result = yield
24
+ cache_store.write key, result, options
25
+ result
26
+ end
27
+
28
+ # Constructs a cache-key (first argument must be a String/Symbol)
29
+ #
30
+ # Cachy.key :my_key
31
+ # Cachy.key :my_key, User.first, :locale=>:de
32
+ # Cachy.key :my_key, User.first, :without_locale=>true, :hash_key=>true
33
+ def self.key(*args)
34
+ options = extract_options!(args)
35
+ ensure_valid_keys options
36
+
37
+ key = (args + meta_key_parts(args.first, options)).compact.map do |part|
38
+ if part.respond_to? :cache_key
39
+ part.cache_key
40
+ else
41
+ part
42
+ end
43
+ end * "_"
44
+
45
+ key = (options[:hash_key] || hash_keys) ? hash(key) : key
46
+ options[:prefix].to_s + key + options[:suffix].to_s
47
+ end
48
+
49
+ # Expire all possible locales of a cache, use the same arguments as with cache
50
+ #
51
+ # Cachy.expire(:my_key, User.first)
52
+ # Cachy.expire(:my_key, User.first, :keys=>[:dependent_keys])
53
+ # Cachy.expire(:my_key, :prefix=>'views/')
54
+ def self.expire(*args)
55
+ options = extract_options!(args)
56
+
57
+ (locales+[false]).each do |locale|
58
+ without_locale = (locale==false)
59
+ args_with_locale = args + [options.merge(:locale=>locale, :without_locale=>without_locale)]
60
+ cache_store.delete key(*args_with_locale)
61
+ end
62
+ end
63
+
64
+ def self.expire_view(*args)
65
+ options = extract_options!(args)
66
+ args = args + [options.merge(:prefix=>'views/')]
67
+ expire(*args)
68
+ end
69
+
70
+ # Fetch key_versions from cache every KEY_VERSION_TIMEOUT seconds,
71
+ # otherwise every .key call would result in an cache request
72
+ @@key_versions = {:versions=>{}, :last_set=>0}
73
+ def self.key_versions
74
+ if key_versions_expired?
75
+ versions = cache_store.read("cachy_key_versions") || {}
76
+ @@key_versions = {:versions=>versions, :last_set=>Time.now.to_i}
77
+ end
78
+ @@key_versions[:versions]
79
+ end
80
+
81
+ def self.key_versions=(data)
82
+ @@key_versions[:last_set] = 0 #expire current key
83
+ cache_store.write("cachy_key_versions", data)
84
+ end
85
+
86
+ # Expires all caches that use this key
87
+ def self.increment_key(key)
88
+ key = key.to_sym
89
+ version = key_versions[key] || 0
90
+ version += 1
91
+ self.key_versions = key_versions.merge(key => version)
92
+ version
93
+ end
94
+
95
+ class << self
96
+ attr_accessor :hash_keys
97
+ end
98
+
99
+ # Wrap non ActiveSupport style cache stores,
100
+ # to get the same interface for all
101
+ def self.cache_store=(x)
102
+ if x.respond_to? :read and x.respond_to? :write
103
+ @cache_store=x
104
+ elsif x.respond_to? "[]" and x.respond_to? :set
105
+ require 'cachy/memcached_wrapper'
106
+ @cache_store = MemcachedWrapper.new(x)
107
+ elsif x.respond_to? "[]" and x.respond_to? :store
108
+ require 'cachy/moneta_wrapper'
109
+ @cache_store = MonetaWrapper.new(x)
110
+ else
111
+ raise "This cache_store type is not usable for Cachy!"
112
+ end
113
+ end
114
+
115
+ def self.cache_store
116
+ @cache_store || raise("Use: Cachy.cache_store = your_cache_store")
117
+ end
118
+
119
+ self.cache_store = ActionController::Base.cache_store if defined? ActionController::Base
120
+
121
+ # locales
122
+ class << self
123
+ attr_accessor :locales
124
+ end
125
+
126
+ self.locales = if defined?(I18n) and I18n.respond_to?(:available_locales)
127
+ I18n.available_locales
128
+ else
129
+ []
130
+ end
131
+
132
+ private
133
+
134
+ # Do we need to fetch fresh key_versions from cache ?
135
+ def self.key_versions_expired?
136
+ key_versions_timeout = Time.now.to_i - KEY_VERSION_TIMEOUT
137
+ @@key_versions[:last_set] < key_versions_timeout
138
+ end
139
+
140
+ # Temorarily store something else in the cache,
141
+ # so that a often-called and slow cache-block is not run by
142
+ # multiple processes in parallel
143
+ def self.set_while_running(key, options)
144
+ return unless options.key? :while_running
145
+ warn "You cannot set while_running to nil" if options[:while_running] == nil
146
+ cache_store.write key, options[:while_running], :expires_in=>WHILE_RUNNING_TMEOUT
147
+ end
148
+
149
+ def self.meta_key_parts(key, options)
150
+ unless [String, Symbol].include?(key.class)
151
+ raise ":key must be first argument of Cachy call"
152
+ end
153
+
154
+ parts = []
155
+ parts << "v#{key_version_for(key)}"
156
+ parts << global_cache_version
157
+ parts << (options[:locale] || locale) unless options[:without_locale]
158
+
159
+ keys = [*options[:keys]].compact # [*x] == .to_a without warnings
160
+ parts += keys.map{|k| "#{k}v#{key_version_for(k)}" }
161
+ parts
162
+ end
163
+
164
+ def self.key_version_for(key)
165
+ key = key.to_sym
166
+ key_versions[key] || increment_key(key)
167
+ end
168
+
169
+ def self.ensure_valid_keys(options)
170
+ invalid = options.keys - [:keys, :expires_in, :without_locale, :locale, :while_running, :hash_key, :prefix, :suffix]
171
+ raise "unknown keys #{invalid.inspect}" unless invalid.empty?
172
+ end
173
+
174
+ def self.hash(string)
175
+ require "digest/md5"
176
+ Digest::MD5.hexdigest(string)
177
+ end
178
+
179
+ def self.global_cache_version
180
+ defined?(CACHE_VERSION) ? CACHE_VERSION : nil
181
+ end
182
+
183
+ def self.locale
184
+ (defined?(I18n) and I18n.respond_to?(:locale)) ? I18n.locale : nil
185
+ end
186
+
187
+ def self.extract_options!(args)
188
+ if args.last.is_a? Hash
189
+ args.pop
190
+ else
191
+ {}
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,8 @@
1
+ require 'cachy/wrapper'
2
+
3
+ # Wrapper for Memcached
4
+ class Cachy::MemcachedWrapper < Cachy::Wrapper
5
+ def write(key, result, options={})
6
+ @wrapped.set(key, result, options[:expires_in].to_i)
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'cachy/wrapper'
2
+
3
+ # Wrapper for Moneta http://github.com/wycats/moneta/tree/master
4
+ class Cachy::MonetaWrapper < Cachy::Wrapper
5
+ def write(key, result, options={})
6
+ @wrapped.store(key, result, options)
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ class Cachy::Wrapper
2
+ def initialize(wrapped)
3
+ @wrapped = wrapped
4
+ end
5
+
6
+ def read(key)
7
+ @wrapped[key]
8
+ end
9
+
10
+ def method_missing(name, args)
11
+ @wrapped.send(name, *args)
12
+ end
13
+
14
+ def respond_to?(x)
15
+ super(x) || @wrapped.respond_to?(x)
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec/spec_helper'
2
+
3
+ class TestMemcached
4
+ def initialize
5
+ @wrapped = {}
6
+ end
7
+
8
+ def set(key, object, ttl = nil)
9
+ raise 'nope!' if ttl.is_a? Hash or (ttl and not ttl.is_a? Numeric)
10
+ @wrapped[key] = object
11
+ end
12
+
13
+ def get(key)
14
+ @wrapped[key]
15
+ end
16
+
17
+ def [](x)
18
+ @wrapped[x]
19
+ end
20
+
21
+ def clear
22
+ @wrapped.clear
23
+ end
24
+
25
+ def delete(key)
26
+ @wrapped.delete(key)
27
+ end
28
+ end
29
+
30
+ describe "Cachy::MemcachedWrapper" do
31
+ before :all do
32
+ @cache = TestMemcached.new
33
+ Cachy.cache_store = @cache
34
+ end
35
+
36
+ before do
37
+ @cache.clear
38
+ end
39
+
40
+ it "is wrapped" do
41
+ Cachy.cache_store.class.should == Cachy::MemcachedWrapper
42
+ end
43
+
44
+ it "can cache" do
45
+ Cachy.cache(:x){ 'SUCCESS' }
46
+ Cachy.cache(:x){ 'FAIL' }.should == 'SUCCESS'
47
+ end
48
+
49
+ it "can cache without expires" do
50
+ @cache.should_receive(:set).with('x_v1', 'SUCCESS', 0)
51
+ Cachy.cache(:x){ 'SUCCESS' }
52
+ end
53
+
54
+ it "can cache with expires" do
55
+ @cache.should_receive(:set).with('x_v1', 'SUCCESS', 1)
56
+ Cachy.cache(:x, :expires_in=>1){ 'SUCCESS' }
57
+ end
58
+
59
+ it "can expire" do
60
+ @cache.should_receive(:delete).with('x_v1')
61
+ Cachy.expire(:x)
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec/spec_helper'
2
+
3
+ class TestMoneta
4
+ def initialize
5
+ @wrapped = {}
6
+ end
7
+
8
+ def store(key, object, options={})
9
+ raise 'nope!' if options[:expires_in] and not options[:expires_in].is_a? Numeric
10
+ @wrapped[key] = object
11
+ end
12
+
13
+ def [](x)
14
+ @wrapped[x]
15
+ end
16
+
17
+ def clear
18
+ @wrapped.clear
19
+ end
20
+
21
+ def delete(key)
22
+ @wrapped.delete(key)
23
+ end
24
+ end
25
+
26
+ describe "Cachy::MonetaWrapper" do
27
+ before :all do
28
+ @cache = TestMoneta.new
29
+ Cachy.cache_store = @cache
30
+ end
31
+
32
+ before do
33
+ @cache.clear
34
+ end
35
+
36
+ it "is wrapped" do
37
+ Cachy.cache_store.class.should == Cachy::MonetaWrapper
38
+ end
39
+
40
+ it "can cache" do
41
+ Cachy.cache(:x){ 'SUCCESS' }
42
+ Cachy.cache(:x){ 'FAIL' }.should == 'SUCCESS'
43
+ end
44
+
45
+ it "can cache without expires" do
46
+ @cache.should_receive(:store).with('x_v1', 'SUCCESS', {})
47
+ Cachy.cache(:x){ 'SUCCESS' }
48
+ end
49
+
50
+ it "can cache with expires" do
51
+ @cache.should_receive(:store).with('x_v1', 'SUCCESS', :expires_in=>1)
52
+ Cachy.cache(:x, :expires_in=>1){ 'SUCCESS' }
53
+ end
54
+
55
+ it "can expire" do
56
+ @cache.should_receive(:delete).with('x_v1')
57
+ Cachy.expire(:x)
58
+ end
59
+ end
@@ -0,0 +1,247 @@
1
+ require 'spec/spec_helper'
2
+
3
+ TEST_CACHE = TestCache.new
4
+
5
+ describe Cachy do
6
+ before :all do
7
+ Cachy.cache_store = TEST_CACHE
8
+ end
9
+
10
+ before do
11
+ TEST_CACHE.clear
12
+ end
13
+
14
+ describe :cache do
15
+ it "caches" do
16
+ Cachy.cache(:my_key){ "X" }.should == "X"
17
+ Cachy.cache(:my_key){ "ABC" }.should == "X"
18
+ end
19
+
20
+ it "expires" do
21
+ Cachy.cache(:his_key, :expires_in=> -100){ 'X' }.should == 'X'
22
+ Cachy.cache(:his_key, :expires_in=> -100){ 'X' }.should == 'X'
23
+ end
24
+
25
+ it "sets cache to intermediate value while running expensive query" do
26
+ Cachy.cache(:my_key, :while_running=>'A') do
27
+ Cachy.cache(:my_key){ 'X' }.should == 'A'
28
+ end
29
+ end
30
+
31
+ it "can set while_running to false" do
32
+ Cachy.cache(:my_key, :while_running=>false) do
33
+ Cachy.cache(:my_key){ 'X' }.should == false
34
+ end
35
+ end
36
+
37
+ it "can not set while_running to nil" do
38
+ Cachy.should_receive(:warn)
39
+ Cachy.cache(:my_key, :while_running=>nil) do
40
+ Cachy.cache(:my_key){ 'X' }.should == "X"
41
+ end
42
+ end
43
+
44
+ it "can cache false" do
45
+ Cachy.cache(:x){ false }.should == false
46
+ Cachy.cache(:x){ true }.should == false
47
+ end
48
+
49
+ it "does not cache nil" do
50
+ Cachy.cache(:x){ nil }.should == nil
51
+ Cachy.cache(:x){ true }.should == true
52
+ end
53
+ end
54
+
55
+ describe :expire do
56
+ it "expires the cache for all languages" do
57
+ Cachy.cache(:my_key){ "without_locale" }
58
+
59
+ locales = [:de, :en, :fr]
60
+ locales.each do |l|
61
+ Cachy.stub!(:locale).and_return l
62
+ Cachy.cache(:my_key){ l }
63
+ end
64
+
65
+ TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 4
66
+ Cachy.stub!(:locales).and_return locales
67
+ Cachy.expire(:my_key)
68
+ TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 0
69
+ end
70
+
71
+ it "does not expire other keys" do
72
+ Cachy.cache(:another_key){ 'X' }
73
+ Cachy.cache(:my_key){ 'X' }
74
+ Cachy.cache(:yet_another_key){ 'X' }
75
+ Cachy.expire :my_key
76
+ TEST_CACHE.keys.should include("another_key_v1")
77
+ TEST_CACHE.keys.should include("yet_another_key_v1")
78
+ end
79
+
80
+ it "expires the cache when no available_locales are set" do
81
+ Cachy.cache(:another_key){ "X" }
82
+ Cachy.cache(:my_key){ "X" }
83
+
84
+ TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 1
85
+ Cachy.expire(:my_key)
86
+ TEST_CACHE.keys.select{|k| k=~ /my_key/}.size.should == 0
87
+ end
88
+
89
+ it "expires the cache with prefix" do
90
+ key = 'views/my_key_v1'
91
+ TEST_CACHE.write(key, 'x')
92
+ TEST_CACHE.read(key).should_not == nil
93
+ Cachy.expire(:my_key, :prefix=>'views/')
94
+ TEST_CACHE.read(key).should == nil
95
+ end
96
+ end
97
+
98
+ describe :expire_view do
99
+ it "expires the cache with prefix" do
100
+ key = 'views/my_key_v1'
101
+ TEST_CACHE.write(key, 'x')
102
+ TEST_CACHE.read(key).should_not == nil
103
+ Cachy.expire_view(:my_key)
104
+ TEST_CACHE.read(key).should == nil
105
+ end
106
+ end
107
+
108
+ describe :key do
109
+ it "builds based on cache_key" do
110
+ user = mock(:cache_key=>'XXX',:something_else=>'YYY')
111
+ Cachy.key(:my_key, 1, user, "abc").should == 'my_key_1_XXX_abc_v1'
112
+ end
113
+
114
+ it "adds current_language" do
115
+ Cachy.stub!(:locale).and_return :de
116
+ Cachy.key(:x).should == "x_v1_de"
117
+ end
118
+
119
+ it "raises on unknown options" do
120
+ lambda{Cachy.key(:x, :asdasd=>'asd')}.should raise_error
121
+ end
122
+
123
+ it "gets the locale from I18n" do
124
+ module I18n
125
+ def self.locale
126
+ :de
127
+ end
128
+ end
129
+ key = Cachy.key(:x)
130
+ Object.send :remove_const, :I18n #cleanup
131
+ key.should == "x_v1_de"
132
+ end
133
+
134
+ describe "with :hash_key" do
135
+ before do
136
+ @hash = '3b2b8f418849bc73071375529f8515be'
137
+ end
138
+
139
+ after do
140
+ Cachy.hash_keys = false
141
+ end
142
+
143
+ it "hashed the key to a stable value" do
144
+ Cachy.key(:xxx, :hash_key=>true).should == @hash
145
+ end
146
+
147
+ it "changes when key changes" do
148
+ Cachy.key(:xxx, :hash_key=>true).should_not == Cachy.key(:yyy, :hash_key=>true)
149
+ end
150
+
151
+ it "changes when arguments change" do
152
+ Cachy.key(:xxx, :hash_key=>true).should_not == Cachy.key(:xxx, 1, :hash_key=>true)
153
+ end
154
+
155
+ it "can be triggered by Cachy.hash_keys" do
156
+ Cachy.hash_keys = true
157
+ Cachy.key(:xxx).should == @hash
158
+ end
159
+ end
160
+
161
+ describe "with :keys" do
162
+ it "is stable" do
163
+ Cachy.key(:x, :keys=>'asd').should == Cachy.key(:x, :keys=>'asd')
164
+ end
165
+
166
+ it "changes when dependent key changes" do
167
+ lambda{
168
+ Cachy.increment_key('asd')
169
+ }.should change{Cachy.key(:x, :keys=>'asd')}
170
+ end
171
+
172
+ it "is different for different keys" do
173
+ Cachy.key(:x, :keys=>'xxx').should_not == Cachy.key(:x, :keys=>'yyy')
174
+ end
175
+ end
176
+
177
+ describe 'with :without_locale' do
178
+ it "is stable with same locale" do
179
+ Cachy.stub!(:locale).and_return :de
180
+ Cachy.key(:x, :without_locale=>true).should == Cachy.key(:x, :without_locale=>true)
181
+ end
182
+
183
+ it "is stable with different locales" do
184
+ Cachy.stub!(:locale).and_return :de
185
+ de_key = Cachy.key(:x, :without_locale=>true)
186
+ Cachy.stub!(:locale).and_return :en
187
+ Cachy.key(:x, :without_locale=>true).should == de_key
188
+ end
189
+ end
190
+
191
+ describe 'with :locale' do
192
+ it "changes the default key" do
193
+ Cachy.key(:x, :locale=>:de).should_not == Cachy.key(:x)
194
+ end
195
+
196
+ it "is a different key for different locales" do
197
+ Cachy.key(:x, :locale=>:de).should_not == Cachy.key(:x, :locale=>:en)
198
+ end
199
+
200
+ it "is the same key for the same locales" do
201
+ Cachy.key(:x, :locale=>:de).should == Cachy.key(:x, :locale=>:de)
202
+ end
203
+ end
204
+ end
205
+
206
+ describe :key_versions do
207
+ before do
208
+ Cachy.key_versions = {}
209
+ Cachy.key_versions.should == {}
210
+ end
211
+
212
+ it "adds a key when cache is called the first time" do
213
+ Cachy.cache(:xxx){1}
214
+ Cachy.key_versions[:xxx].should == 1
215
+ end
216
+
217
+ it "adds a string key as symbol" do
218
+ Cachy.cache('yyy'){1}
219
+ Cachy.key_versions[:yyy].should == 1
220
+ end
221
+
222
+ it "does not overwrite a key when it already exists" do
223
+ Cachy.key_versions = {:xxx => 3}
224
+ Cachy.cache(:xxx){1}
225
+ Cachy.cache(:xxx){1}
226
+ Cachy.key_versions[:xxx].should == 3
227
+ end
228
+
229
+ it "reloads when keys have expired" do
230
+ 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}
232
+ Cachy.key_versions.should == {:xx=>1}
233
+ end
234
+
235
+ it "does not reload when keys have not expired" do
236
+ 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}
238
+ Cachy.key_versions.should == {:xx=>2}
239
+ end
240
+
241
+ it "expires when key_versions is set" do
242
+ Cachy.send :class_variable_set, "@@key_versions", {:versions=>{:xx=>2}, :last_set=>Time.now.to_i}
243
+ Cachy.key_versions = {:xx=>1}
244
+ Cachy.key_versions[:xx].should == 1
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,5 @@
1
+ # ---- requirements
2
+ require 'spec/test_cache'
3
+ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
4
+
5
+ require 'cachy'
@@ -0,0 +1,25 @@
1
+ class TestCache
2
+ def initialize
3
+ @data = {}
4
+ end
5
+
6
+ def read(name)
7
+ @data[name]
8
+ end
9
+
10
+ def write(name, value, options = nil)
11
+ @data[name] = value.freeze
12
+ end
13
+
14
+ def delete(name, options = nil)
15
+ @data.delete(name)
16
+ end
17
+
18
+ def clear
19
+ @data.clear
20
+ end
21
+
22
+ def keys
23
+ @data.keys
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-16 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: grosser.michael@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - VERSION
28
+ - cachy.gemspec
29
+ - lib/cachy.rb
30
+ - lib/cachy/memcached_wrapper.rb
31
+ - lib/cachy/moneta_wrapper.rb
32
+ - lib/cachy/wrapper.rb
33
+ - spec/cachy/memcached_wrapper_spec.rb
34
+ - spec/cachy/moneta_wrapper_spec.rb
35
+ - spec/cachy_spec.rb
36
+ - spec/spec_helper.rb
37
+ - spec/test_cache.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/grosser/cachy
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project: cachy
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Caching library for projects that have many processes or many caches
66
+ test_files:
67
+ - spec/test_cache.rb
68
+ - spec/cachy/memcached_wrapper_spec.rb
69
+ - spec/cachy/moneta_wrapper_spec.rb
70
+ - spec/spec_helper.rb
71
+ - spec/cachy_spec.rb