cachy 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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