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