cachecataz 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.8.7)
11
+ rcov (0.9.9)
12
+ rspec (2.3.0)
13
+ rspec-core (~> 2.3.0)
14
+ rspec-expectations (~> 2.3.0)
15
+ rspec-mocks (~> 2.3.0)
16
+ rspec-core (2.3.1)
17
+ rspec-expectations (2.3.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.3.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.5.2)
27
+ rcov
28
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Brandon Dewitt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,81 @@
1
+ = cachecataz
2
+
3
+ Cachecataz is for namespace expiry in a cache where the cache provider does not enable namespace based expiry (like memcached).
4
+
5
+ Cachecataz creates a namespace key based on a cache_scope defined for the class. Each cache namespace can be expired through
6
+ an instance or the class.
7
+
8
+ == Problem
9
+
10
+ Cache servers like Memcache only provide expiry for cache keys based on a unique value.
11
+ Don't allow you to specify an entire namespace to expire based on changes in the underlying data.
12
+
13
+ == Solution
14
+
15
+ Store a value in the cache that determines the namespace of the cache key and increment the value of the namespace key each time the
16
+ namespace needs to be expired.
17
+
18
+ = Using Cachecataz
19
+
20
+ Install it:
21
+
22
+ gem install cachecataz
23
+
24
+ Include it and define your scopes:
25
+
26
+ class Element < ActiveRecord::Base
27
+ include Cachecataz
28
+
29
+ cache_scope :name, [:user_id]
30
+ end
31
+
32
+ A cache_scope is the mechanism to create a unique scope for a namespace. The namespace key will be comprised of the cache_scope name
33
+ (in this example "name") and the runtime state of any value passed in the Array of symbols that make up a unique scope.
34
+
35
+ For the example above the namespace key would be "name:#{element.user_id}", which allows us to have a unique namespace for each
36
+ "name" key scoped by the user_id.
37
+
38
+ == Example (in Rails)
39
+
40
+ Basic premise: An Element belongs_to a User and a Widget belongs_to a User as well. The "something" partial displays data primarily related to
41
+ the Element but also displays data from the related Widget. I want to have a sweeper that observes changes in the Widget model and expires the
42
+ namespace for the Element. (can be kinda confusing, but makes sense in the context of a cache that is model dependent.
43
+
44
+ Generate a cache key:
45
+ # ** Model **
46
+ class Element < ActiveRecord::Base
47
+ include Cachecataz
48
+
49
+ cache_scope :user, [:user_id]
50
+ end
51
+
52
+ ** View **
53
+ <% cache(@element.cache_key(:user, :id)) do %>
54
+ <% render :partial => "something", :locals => { :widget => @element.widget } %>
55
+ <% end %>
56
+
57
+ # ** Observer **
58
+ class WidgetObserver < ActiveRecord::Observer
59
+ after_update(widget)
60
+ Element.expire_namespace(:user, widget.attributes) # can also just pass {:user_id => widget.user_id} as the scope_hash, scope requires :user_id
61
+ end
62
+ end
63
+
64
+ == TODO
65
+ post more info on how to use cachecataz with a cache that is not Rails.cache (it's very simple), but wanted to get the basics released
66
+
67
+ == Contributing to cachecataz
68
+
69
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
70
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
71
+ * Fork the project
72
+ * Start a feature/bugfix branch
73
+ * Commit and push until you are happy with your contribution
74
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
75
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
76
+
77
+ == Copyright
78
+
79
+ Copyright (c) 2011 Brandon Dewitt. See LICENSE.txt for
80
+ further details.
81
+
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "cachecataz"
16
+ gem.homepage = "http://github.com/bdewitt/cachecataz"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{cache namespace expiry gem for cache servers that do not have namespace expiry}
19
+ gem.description = %Q{Cachecataz is for namespace expiry in a cache where the cache provider does not enable namespace based expiry (like memcached!)}
20
+ gem.email = "brandon+cachecataz@myjibe.com"
21
+ gem.authors = ["Brandon Dewitt"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ gem.add_development_dependency 'rspec', '> 2'
26
+ end
27
+
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "cachecataz #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/cachecataz.rb ADDED
@@ -0,0 +1,208 @@
1
+ module Cachecataz
2
+ # Default config for Rails.cache, disabled, and [":", "/"] delims
3
+ #
4
+ # @example configure in an environment file (development.rb, production.rb) for Rails
5
+ #
6
+ # config.after_initialize do
7
+ # Cachecataz.enable = true
8
+ # Cachecataz.provider = Rails.cache
9
+ # end
10
+ Config = {:api => {:get => :read, :set => :write, :incr => :increment, :exist? => :exist?},
11
+ :enabled => false,
12
+ :ns_delim => ":",
13
+ :index_delim => "/" }
14
+
15
+ # Config method to enable Cachecataz
16
+ #
17
+ # @param [Boolean] val
18
+ def self.enable=(val)
19
+ Config[:enabled] = val
20
+ end
21
+
22
+ # Set custom delimiter if desired
23
+ def self.delim=(val)
24
+ Config[:ns_delim] = val.first rescue ":"
25
+ Config[:index_delim] = val.last rescue "/"
26
+ end
27
+
28
+ # Config method to assign the provider, for Rails this is Rails.cache
29
+ #
30
+ # @param [Object] val an object that responds to the api provided
31
+ def self.provider=(val)
32
+ Config[:provider] = val
33
+ end
34
+
35
+ # Config method that maps the api method calls from the provider to the caching server
36
+ # example is the mapping for the Rails.cache provider
37
+ #
38
+ # @example Cachecataz.api = {:get => :read, :set => :write, :incr => :increment, :exist? => :exist?}
39
+ #
40
+ # @param [Hash] api a hash of symbols or procs/lambdas mapped to each of [:get, :set, :incr, :exist?]
41
+ def self.api=(api)
42
+ validate_api(api)
43
+ Config[:api] = api
44
+ end
45
+
46
+ # [] operator to run the actual calls on the cache provider configured
47
+ def self.[](*api_args)
48
+ return false if !Config[:enabled]
49
+
50
+ api_method = api_args.slice!(0)
51
+
52
+ if Config[:api][api_method].respond_to?(:call)
53
+ Config[:api][api_method].call(*api_args)
54
+ else
55
+ Config[:provider].send(Config[:api][api_method], *api_args)
56
+ end
57
+ end
58
+
59
+ # Method that validates the api and provider if they are defined in configuration
60
+ def self.validate_api(api={})
61
+ unless api.include?(:get) && api.include?(:set) && api.include?(:incr) && api.include?(:exist?)
62
+ raise "Unknown api methods, define [:get, :set, :incr, :exist?] to use cachecataz with a non-standard provider"
63
+ end
64
+ end
65
+
66
+ # Method that includes and extends the appropriate modules
67
+ def self.included(inc_class)
68
+ inc_class.instance_variable_set(:@_point_keys, {})
69
+ inc_class.send(:include, Cachecataz::InstanceMethods)
70
+ inc_class.send(:extend, Cachecataz::ClassMethods)
71
+ end
72
+
73
+ module ClassMethods
74
+ # Takes the cache_point and the value from the cache
75
+ #
76
+ # @param [Symbol] point_key the symbol that identifies the namespace point
77
+ # @param [Hash] scope_hash the hash that provides the data for creating the namespace key
78
+ # @return [String] cachecataz namespaced cache key
79
+ def cache_key(point_key, scope_hash)
80
+ c_point = cache_point(point_key, scope_hash)
81
+ c_key = Cachecataz[:get, c_point].to_s.strip
82
+ return "#{c_key}#{Cachecataz::Config[:ns_delim]}" << c_point
83
+ end
84
+
85
+ # Determines and returns the cache_point
86
+ # putting i.to_s first in the scope_hash lookup because primarily using with self.attributes in rails which is string keyed
87
+ def cache_point(point_key, scope_hash)
88
+ c_scope = @_point_keys[point_key]
89
+ c_point = c_scope.inject(point_key.to_s){|s, i| s << Cachecataz::Config[:ns_delim] << (scope_hash[i.to_s] || scope_hash[i]).to_s }
90
+ Cachecataz[:set, c_point, "0"] if !Cachecataz[:exist?, c_point]
91
+ return c_point
92
+ end
93
+
94
+ # Method used in the Class to defined a cachecataz namespace
95
+ # assigns the scope to a class instance variable with the point_key as key
96
+ #
97
+ # @param [Symbol] point_key the name of the cachecataz namespace
98
+ # @param [Array] c_scope the symbols that defined the scope of the namespace
99
+ def cache_scope(point_key, *c_scope)
100
+ c_scope.flatten!
101
+ c_scope.uniq!
102
+ c_scope.sort!{|a, b| a.to_s <=> b.to_s}
103
+ @_point_keys[point_key] = c_scope
104
+ end
105
+
106
+ # Class level method that expires the namespace in the cache for the point_key and
107
+ # scope data provided
108
+ #
109
+ # @param [Symbol] point_key
110
+ # @param [Hash] scope_hash the data provider for the scope of the namespace
111
+ def expire_namespace(point_key, scope_hash={})
112
+ c_point = cache_point(point_key, scope_hash)
113
+ Cachecataz[:incr, c_point]
114
+ end
115
+
116
+ # Class level method to expire all the namespace of cachecataz for a given data provider
117
+ #
118
+ # @param [Hash] scope_hash the data provider for the scope of the namespace
119
+ def expire_all_namespaces(scope_hash={})
120
+ @_point_keys.keys.each{|k| expire_namespace(k, scope_hash)}
121
+ end
122
+
123
+ # Resets a cache namespace to 0, should be needed, but wanted to have something here to do it
124
+ def cachecataz_namespace_reset(point_key, scope_hash={})
125
+ c_point = cache_point(point_key, scope_hash)
126
+ Cachecataz[:set, c_point, "0"]
127
+ end
128
+
129
+ # provides access for the point_keys stored in the Class instance variable
130
+ def point_key(point_key)
131
+ @_point_keys[point_key]
132
+ end
133
+
134
+ # provides access to all point_keys for the Class
135
+ def point_keys
136
+ @_point_keys
137
+ end
138
+ end
139
+
140
+ module InstanceMethods
141
+ # Instance method for accessing the cachecataz namespace identifier
142
+ #
143
+ # @param [Symbol] point_key name of the cache_space defined on the Class
144
+ # @param [Hash] scope_hash provides the data for the namespace key
145
+ # @return [string] namespace key for the cachecataz namespace
146
+ def cachecataz_key(point_key, scope_hash={})
147
+ return "cachecataz disabled" if !Cachecataz::Config[:enabled]
148
+
149
+ scope_hash = self.attributes if scope_hash == {} && self.respond_to?(:attributes)
150
+ return self.class.cache_key(point_key, scope_hash)
151
+ end
152
+
153
+ # Instance method to return a cache_key for the class
154
+ #
155
+ # @note method removes any index that is already in the namespace definition as it can't be 2x as unique on the same key
156
+ #
157
+ # @example user.cache_key(:ck_name, [:id]) # => "0:ck_name/:id"
158
+ #
159
+ # @param [Symbol] point_key name of the cache_space defined on the Class
160
+ # @param [Array, []] indexes additional data elements that makeup the key for the instance
161
+ # @param [Hash, self.attributes] scope_hash provides the data for the namespace key
162
+ def cache_key(point_key, indexes=[], scope_hash={})
163
+ cache_key_point = cachecataz_key(point_key, scope_hash)
164
+ indexes = [indexes] if !indexes.respond_to?(:each)
165
+ indexes.uniq!
166
+ indexes.reject!{ |i| self.class.point_key(point_key).include?(i) }
167
+ return indexes.inject(cache_key_point){|s, n| s << Cachecataz::Config[:index_delim] << self.cachecataz_index_convert(n) }
168
+ end
169
+
170
+ # Determines the intended index conversion for index passed to cache_key
171
+ #
172
+ # @note if index responds to :call then it will check the arity to determine if it is 1, if so it passes self as the argument
173
+ #
174
+ # @param [Object] val the value passed to cache_key index
175
+ # @return [String] string to append to namespace key
176
+ def cachecataz_index_convert(val)
177
+ case
178
+ when val.kind_of?(Symbol) && self.respond_to?(val)
179
+ self.send(val).to_s
180
+ when val.respond_to?(:call)
181
+ val.arity == 1 ? val.call(self).to_s : val.call.to_s
182
+ else
183
+ val.to_s
184
+ end
185
+ end
186
+
187
+ # Instance method to expire a cachecataz namespace
188
+ #
189
+ # @param [Symbol] point_key name of the cache_space defined on the Class
190
+ # @param [Hash, self.attributes] scope_hash provides the data for the namespace key
191
+ def expire_namespace(point_key, scope_hash={})
192
+ scope_hash = self.attributes if scope_hash == {} && self.respond_to?(:attributes)
193
+ self.class.expire_namespace(point_key, scope_hash)
194
+ end
195
+
196
+ # Instance method to expire all namespaces for an object
197
+ def expire_all_namespaces(scope_hash={})
198
+ scope_hash = self.attributes if scope_hash == {} && self.respond_to?(:attributes)
199
+ self.class.expire_all_namespaces(scope_hash)
200
+ end
201
+
202
+ # Instance method to reset a namespace, shouldn't really be needed, but avail
203
+ def cachecataz_namespace_reset(point_key, scope_hash={})
204
+ scope_hash = self.attributes if scope_hash == {} && self.respond_to?(:attributes)
205
+ self.class.cachecataz_namespace_reset(point_key, scope_hash)
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,152 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe MockModel do
4
+ describe "disabled" do
5
+
6
+ before(:all) do
7
+ Cachecataz.enable = false
8
+ end
9
+
10
+ it "is disabled on initialization for cache_scope :user" do
11
+ subject.cache_key(:user).should include("cachecataz disabled")
12
+ end
13
+
14
+ it "is disabled on initialization for cache_scope :user, [:id, :user_id]" do
15
+ subject.cache_key(:user, [:id, :user_id]).should include("cachecataz disabled")
16
+ end
17
+ end
18
+
19
+ describe "basic namespacing" do
20
+ subject { MockModel.new }
21
+
22
+ before(:all) do
23
+ Cachecataz.enable = true
24
+ Cachecataz.provider = MockCache.new
25
+ end
26
+
27
+ it "returns a namespace key for an empty cache_scope" do
28
+ subject.cache_key(:empty).should eq("0:empty")
29
+ end
30
+
31
+ it "returns a namespace key for cache_scope :user" do
32
+ subject.cache_key(:user).should eq("0:user:#{subject.user_id}")
33
+ end
34
+
35
+ it "returns a multi namespace key for cache_scope :multi" do
36
+ subject.cache_key(:multi).should eq("0:multi:#{subject.id}:#{subject.user_id}")
37
+ end
38
+
39
+ it "returns a namespace key with index for and empty cache_scope :empty, :id" do
40
+ subject.cache_key(:empty, :id).should eq("0:empty/#{subject.id}")
41
+ end
42
+
43
+ it "returns a namespace key with index for cache_scope :user, :id" do
44
+ subject.cache_key(:user, :id).should eq("0:user:#{subject.user_id}/#{subject.id}")
45
+ end
46
+
47
+ it "returns a multi namespace key for cache_scope :multi, :assoc_id" do
48
+ subject.cache_key(:multi, :assoc_id).should eq("0:multi:#{subject.id}:#{subject.user_id}/#{subject.assoc_id}")
49
+ end
50
+
51
+ it "returns the same value for each key request on single variable non indexed cache_scope :user" do
52
+ 10.times do
53
+ subject.cache_key(:user).should eq(subject.cache_key(:user))
54
+ end
55
+ end
56
+
57
+ it "returns the same value for each key request on mutli variable non indexed cache_scope :multi" do
58
+ 10.times do
59
+ subject.cache_key(:multi).should eq(subject.cache_key(:multi))
60
+ end
61
+ end
62
+
63
+ it "returns the same value for each key request on single variable indexed cache_scope :user, :id" do
64
+ 10.times do
65
+ subject.cache_key(:user, :id).should eq(subject.cache_key(:user, :id))
66
+ end
67
+ end
68
+
69
+ it "returns the same value for each key request on multi variable indexed cache_scope :multi, :assoc_id" do
70
+ 10.times do
71
+ subject.cache_key(:multi, :assoc_id).should eq(subject.cache_key(:multi, :assoc_id))
72
+ end
73
+ end
74
+
75
+ it "expires a namespace key for a single variable cache_scope :user" do
76
+ subject.expire_namespace(:user)
77
+ subject.cache_key(:user).should eq("1:user:#{subject.user_id}")
78
+ end
79
+
80
+ it "expires a namespace with index key for a mutli variable cache_scope :multi" do
81
+ subject.expire_namespace(:multi)
82
+ subject.cache_key(:multi).should eq("1:multi:#{subject.id}:#{subject.user_id}")
83
+ end
84
+
85
+ it "expires a namespace key with index for cache_scope :user, :id" do
86
+ subject.expire_namespace(:user)
87
+ subject.cache_key(:user, :id).should eq("2:user:#{subject.user_id}/#{subject.id}")
88
+ end
89
+
90
+ it "expires a multi namespace key with index for cache_scope :multi, :assoc_id" do
91
+ subject.expire_namespace(:multi)
92
+ subject.cache_key(:multi, :assoc_id).should eq("2:multi:#{subject.id}:#{subject.user_id}/#{subject.assoc_id}")
93
+ end
94
+
95
+ describe "namespace delim change" do
96
+ before(:all) do
97
+ Cachecataz::Config[:provider].clear # not part of api, just an easy way to clear the mock cache
98
+ Cachecataz.delim = ["|", "/"]
99
+ end
100
+
101
+ it "returns a namespace key with index for cache_scope :user, :id" do
102
+ subject.cache_key(:user, :id).should eq("0|user|#{subject.user_id}/#{subject.id}")
103
+ end
104
+
105
+ it "returns a multi namespace key for cache_scope :multi, :assoc_id" do
106
+ subject.cache_key(:multi, :assoc_id).should eq("0|multi|#{subject.id}|#{subject.user_id}/#{subject.assoc_id}")
107
+ end
108
+ end
109
+
110
+ describe "index delim change" do
111
+ before(:all) do
112
+ Cachecataz::Config[:provider].clear # not part of api, just an easy way to clear the mock cache
113
+ Cachecataz.delim = [":", "|"]
114
+ end
115
+
116
+ it "returns a namespace key with index for cache_scope :user, :id" do
117
+ subject.cache_key(:user, :id).should eq("0:user:#{subject.user_id}|#{subject.id}")
118
+ end
119
+
120
+ it "returns a multi namespace key for cache_scope :multi, :assoc_id" do
121
+ subject.cache_key(:multi, :assoc_id).should eq("0:multi:#{subject.id}:#{subject.user_id}|#{subject.assoc_id}")
122
+ end
123
+ end
124
+
125
+ describe "redefine api respond_to :call" do
126
+ before(:all) do
127
+ Cachecataz::Config[:provider].clear
128
+ Cachecataz.delim = [":", "/"]
129
+ Cachecataz.api = {:get => lambda{ |*args| Cachecataz::Config[:provider].all(:read, *args) },
130
+ :set => lambda{ |*args| Cachecataz::Config[:provider].all(:write, *args) },
131
+ :incr => lambda{ |*args| Cachecataz::Config[:provider].all(:increment, *args) },
132
+ :exist? => lambda{ |*args| Cachecataz::Config[:provider].all(:exist?, *args) }}
133
+ end
134
+
135
+ it "returns a namespace key for an empty cache_scope" do
136
+ subject.cache_key(:empty).should eq("0:empty")
137
+ end
138
+
139
+ it "returns a namespace key for cache_scope :user" do
140
+ subject.cache_key(:user).should eq("0:user:#{subject.user_id}")
141
+ end
142
+
143
+ it "returns a multi namespace key for cache_scope :multi" do
144
+ subject.cache_key(:multi).should eq("0:multi:#{subject.id}:#{subject.user_id}")
145
+ end
146
+
147
+ it "returns a namespace key with index for and empty cache_scope :empty, :id" do
148
+ subject.cache_key(:empty, :id).should eq("0:empty/#{subject.id}")
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'cachecataz'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,29 @@
1
+ class MockCache
2
+ def initialize
3
+ @cache = {}
4
+ end
5
+
6
+ def read(key)
7
+ @cache[key].to_s
8
+ end
9
+
10
+ def write(key, val)
11
+ @cache[key] = val.to_s
12
+ end
13
+
14
+ def increment(key)
15
+ @cache[key] = @cache[key].to_i + 1
16
+ end
17
+
18
+ def exist?(key)
19
+ !!@cache[key]
20
+ end
21
+
22
+ def all(action, *args)
23
+ self.send(action, *args)
24
+ end
25
+
26
+ def clear
27
+ @cache = {}
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ class MockModel
2
+ include Cachecataz
3
+
4
+ cache_scope :empty
5
+ cache_scope :test, :id
6
+ cache_scope :user, :user_id
7
+ cache_scope :multi, [:id, :user_id]
8
+
9
+ def initialize
10
+ @id = "1"
11
+ @user_id = "2"
12
+ @assoc_id = "3"
13
+ end
14
+
15
+ def id
16
+ @id
17
+ end
18
+
19
+ def user_id
20
+ @user_id
21
+ end
22
+
23
+ def assoc_id
24
+ @assoc_id
25
+ end
26
+
27
+ def attributes
28
+ {:user_id => @user_id, :id => @id, "assoc_id" => @assoc_id, "extra" => "attributes", "not" => "important", :but => :present}
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachecataz
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Brandon Dewitt
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-14 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 2
30
+ - 3
31
+ - 0
32
+ version: 2.3.0
33
+ requirement: *id001
34
+ prerelease: false
35
+ type: :development
36
+ name: rspec
37
+ - !ruby/object:Gem::Dependency
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 23
44
+ segments:
45
+ - 1
46
+ - 0
47
+ - 0
48
+ version: 1.0.0
49
+ requirement: *id002
50
+ prerelease: false
51
+ type: :development
52
+ name: bundler
53
+ - !ruby/object:Gem::Dependency
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 7
60
+ segments:
61
+ - 1
62
+ - 5
63
+ - 2
64
+ version: 1.5.2
65
+ requirement: *id003
66
+ prerelease: false
67
+ type: :development
68
+ name: jeweler
69
+ - !ruby/object:Gem::Dependency
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirement: *id004
80
+ prerelease: false
81
+ type: :development
82
+ name: rcov
83
+ - !ruby/object:Gem::Dependency
84
+ version_requirements: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">"
88
+ - !ruby/object:Gem::Version
89
+ hash: 7
90
+ segments:
91
+ - 2
92
+ version: "2"
93
+ requirement: *id005
94
+ prerelease: false
95
+ type: :development
96
+ name: rspec
97
+ description: Cachecataz is for namespace expiry in a cache where the cache provider does not enable namespace based expiry (like memcached!)
98
+ email: brandon+cachecataz@myjibe.com
99
+ executables: []
100
+
101
+ extensions: []
102
+
103
+ extra_rdoc_files:
104
+ - LICENSE.txt
105
+ - README.rdoc
106
+ files:
107
+ - .document
108
+ - .rspec
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - LICENSE.txt
112
+ - README.rdoc
113
+ - Rakefile
114
+ - VERSION
115
+ - lib/cachecataz.rb
116
+ - spec/cachecataz_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/support/mock_cache.rb
119
+ - spec/support/mock_model.rb
120
+ has_rdoc: true
121
+ homepage: http://github.com/bdewitt/cachecataz
122
+ licenses:
123
+ - MIT
124
+ post_install_message:
125
+ rdoc_options: []
126
+
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ requirements: []
148
+
149
+ rubyforge_project:
150
+ rubygems_version: 1.3.7
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: cache namespace expiry gem for cache servers that do not have namespace expiry
154
+ test_files:
155
+ - spec/cachecataz_spec.rb
156
+ - spec/spec_helper.rb
157
+ - spec/support/mock_cache.rb
158
+ - spec/support/mock_model.rb