cachecataz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +81 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/cachecataz.rb +208 -0
- data/spec/cachecataz_spec.rb +152 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/mock_cache.rb +29 -0
- data/spec/support/mock_model.rb +30 -0
- metadata +158 -0
data/.document
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|