markbates-cachetastic 3.0.0.20090611142033
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/LICENSE +21 -0
- data/README +89 -0
- data/lib/cachetastic/adapters/base.rb +178 -0
- data/lib/cachetastic/adapters/file.rb +66 -0
- data/lib/cachetastic/adapters/local_memory.rb +37 -0
- data/lib/cachetastic/adapters/memcached.rb +114 -0
- data/lib/cachetastic/cache.rb +165 -0
- data/lib/cachetastic/cacheable.rb +202 -0
- data/lib/cachetastic/extensions/string.rb +8 -0
- data/lib/cachetastic/logger.rb +49 -0
- data/lib/cachetastic/store_object.rb +22 -0
- data/lib/cachetastic.rb +20 -0
- metadata +94 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Mark Bates
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
=What is Cachetastic?
|
2
|
+
Cachetastic is an incredibly easy to use and administer caching framework. Just because it is easy to use, does not mean that
|
3
|
+
it is light with features. Cachetastic allows you to create classes that extend <tt>Cachetastic::Cache</tt>, configure them
|
4
|
+
each individually, and much more.
|
5
|
+
|
6
|
+
Unlike other systems each cache in your system can use different backends via the use of adapters that get assigned to each
|
7
|
+
cache, and globally. You can define different expiration times, loggers, marshal methods, and more! And again, you can choose to
|
8
|
+
define these settings globally, or for each cache!
|
9
|
+
|
10
|
+
Adapters are easy to write, so if the built in adapters don't float your boat, you can easily knock one off in short order.
|
11
|
+
|
12
|
+
==Configuration:
|
13
|
+
Configuration of Cachetastic is done using the Configatron gem. I would recommend reading the documentation on it first, http://configatron.mackframework.com, before continuing.
|
14
|
+
|
15
|
+
All configuration settings hang off of the <tt>cachetastic</tt> namespace on <tt>configatron</tt>. The default settings
|
16
|
+
all hang off the <tt>defaults</tt> namespace on the <tt>cachetastic</tt> namespace, as shown below:
|
17
|
+
|
18
|
+
# This will write detailed information to the logger.
|
19
|
+
configatron.cachetastic.defaults.debug = false
|
20
|
+
|
21
|
+
# This is the type of file store to be used for this cache.
|
22
|
+
# More adapters can be developed and plugged in as desired.
|
23
|
+
# The default is Cachetastic::Adapters::LocalMemory
|
24
|
+
configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::LocalMemory
|
25
|
+
configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::File
|
26
|
+
configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::Memcached
|
27
|
+
|
28
|
+
# This will marshall objects into and out of the store.
|
29
|
+
# The default is :none, except for Cachetastic::Adapters::File, which defaults to :yaml
|
30
|
+
configatron.cachetastic.defaults.marshall_method = :none
|
31
|
+
configatron.cachetastic.defaults.marshall_method = :yaml
|
32
|
+
configatron.cachetastic.defaults.marshall_method = :ruby
|
33
|
+
|
34
|
+
# This sets how long objects will live in the cache before they are auto expired.
|
35
|
+
configatron.cachetastic.defaults.default_expiry = 86400 # time in seconds (default: 24 hours)
|
36
|
+
|
37
|
+
# When setting objects into the cache the expiry_swing is +/- to the expiry time.
|
38
|
+
# Example: if the expiry time is 1 minute, and the swing is 15 seconds,
|
39
|
+
# objects will go into the cache with an expiry time sometime between 45 seconds and 75 seconds.
|
40
|
+
# The default is 0 seconds.
|
41
|
+
configatron.cachetastic.defaults.expiry_swing = 15
|
42
|
+
|
43
|
+
# Configure logging for the system.
|
44
|
+
# n number of logs can be configured for a cache.
|
45
|
+
log_1 = Logger.new(STDOUT)
|
46
|
+
log_1.level = Logger::DEBUG
|
47
|
+
log_2 = Logger.new("log/cachetastic.log")
|
48
|
+
log_2.level = Logger::ERROR
|
49
|
+
configatron.cachetastic.defaults.logger = [log_1, log_2]
|
50
|
+
|
51
|
+
Overriding settings per cache is very simple. Let's take the following two caches:
|
52
|
+
|
53
|
+
class UserCache < Cachetastic::Cache
|
54
|
+
end
|
55
|
+
|
56
|
+
class Admin::UserCache < Cachetastic::Cache
|
57
|
+
end
|
58
|
+
|
59
|
+
If we wanted to set the <tt>UserCache</tt> to use the <tt>Cachetastic::Adapters::File</tt> adapter and we wanted to set the adapter for
|
60
|
+
the <tt>Admin::UserCache</tt> to use <tt>Cachetastic::Adapters::Memcached</tt>, we would configure them like such:
|
61
|
+
|
62
|
+
configatron.cachetastic.user_cache.adapter = Cachetastic::Adapters::File
|
63
|
+
configatron.cachetastic.admin.user_cache.adapter = Cachetastic::Adapters::Memcached
|
64
|
+
|
65
|
+
In this scenario we have changed the adapters for each of the classes. All of the other default settings will remain intact for
|
66
|
+
each of those classes. This makes it incredibly easy to just change the one parameter you need, and not have to reset them all.
|
67
|
+
|
68
|
+
==Examples:
|
69
|
+
|
70
|
+
class MyAwesomeCache < Cachetastic::Cache
|
71
|
+
end
|
72
|
+
|
73
|
+
MyAwesomeCache.set(1, [1,2,3])
|
74
|
+
MyAwesomeCache.get(1) # => [1,2,3]
|
75
|
+
|
76
|
+
class MyAwesomeCache < Cachetastic::Cache
|
77
|
+
class << self
|
78
|
+
def get(key, x, y)
|
79
|
+
super(key) do
|
80
|
+
n = x + y
|
81
|
+
set(key, n)
|
82
|
+
n
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
MyAwesomeCache.get(1, 2, 4) # => 8
|
89
|
+
MyAwesomeCache.get(1, 4, 4) # => 8
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# This method will return the appropriate
|
7
|
+
# <tt>Cachetastic::Adapters::Base</tt> class that is defined
|
8
|
+
# for the Class passed in. If an adapter has not been
|
9
|
+
# defined for the Class than the default adapter is returned.
|
10
|
+
#
|
11
|
+
# Examples:
|
12
|
+
# configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::LocalMemory
|
13
|
+
# configatron.cachetastic.user.adapter = Cachetastic::Adapters::Memcached
|
14
|
+
# Cachetastic::Adapters.build(User).class # => Cachetastic::Adapters::Memcached
|
15
|
+
# Cachetastic::Adapters.build(Comment).class # => Cachetastic::Adapters::LocalMemory
|
16
|
+
def build(klass)
|
17
|
+
adp = klass.to_configatron(:cachetastic).adapter
|
18
|
+
if adp.nil?
|
19
|
+
adp = configatron.cachetastic.defaults.adapter
|
20
|
+
end
|
21
|
+
adp.new(klass)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class << self
|
25
|
+
|
26
|
+
# This class should be extended to create new adapters for various
|
27
|
+
# backends. It is important that all subclasses call the <tt>initialize</tt>
|
28
|
+
# method in this base, otherwise things just will not work right.
|
29
|
+
#
|
30
|
+
# This base class provides common functionality and an API for all
|
31
|
+
# adapters to be used with Cachetastic.
|
32
|
+
#
|
33
|
+
# The default settings for all adapters are:
|
34
|
+
#
|
35
|
+
# configatron.cachetastic.defaults.marshal_method = :none
|
36
|
+
# configatron.cachetastic.defaults.expiry_swing = 0
|
37
|
+
# configatron.cachetastic.defaults.default_expiry = 86400
|
38
|
+
# configatron.cachetastic.defaults.debug = true
|
39
|
+
# configatron.cachetastic.defaults.adapter = Cachetastic::Adapters::LocalMemory
|
40
|
+
# logger = ::Logger.new(File.join(FileUtils.pwd, 'log', 'cachetastic.log'))
|
41
|
+
# logger.level = ::Logger::DEBUG
|
42
|
+
# configatron.cachetastic.defaults.logger = logger
|
43
|
+
#
|
44
|
+
# See the README for more information on what each of those settings mean,
|
45
|
+
# and what are values may be used for each one.
|
46
|
+
class Base
|
47
|
+
|
48
|
+
# The Class that this adapter is associated with. Note that it is a
|
49
|
+
# <i>class</i> reference and not an instance reference.
|
50
|
+
attr_accessor :klass
|
51
|
+
|
52
|
+
# Creates a new adapter. It takes a class reference to tie the
|
53
|
+
# instance of the adapter to a particular class. Note that it is a
|
54
|
+
# <i>class</i> reference and not an instance reference.
|
55
|
+
#
|
56
|
+
# Examples:
|
57
|
+
# Cachetastic::Adapters::Base.new(User)
|
58
|
+
#
|
59
|
+
# Adapters are configured using the Configatron gem.
|
60
|
+
#
|
61
|
+
# Examples:
|
62
|
+
# configatron.cachetastic.user.adapter = Cachetastic::Adapters::File
|
63
|
+
# configatron.cachetastic.user.expiry_time = 5.hours
|
64
|
+
# configatron.cachetastic.defaults.expiry_time = 24.hours
|
65
|
+
#
|
66
|
+
# Refered to each adapter for its specific configuration settings.
|
67
|
+
def initialize(klass)
|
68
|
+
self.klass = klass
|
69
|
+
configatron.cachetastic.defaults.configatron_keys.each do |key|
|
70
|
+
define_accessor(key)
|
71
|
+
self.send("#{key}=", configatron.cachetastic.defaults.send(key))
|
72
|
+
end
|
73
|
+
klass.to_configatron(:cachetastic).configatron_keys.each do |key|
|
74
|
+
define_accessor(key)
|
75
|
+
self.send("#{key}=", klass.to_configatron(:cachetastic).send(key))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# <b>This method MUST be implemented by a subclass!</b>
|
80
|
+
#
|
81
|
+
# The implementation of this method should take a key and return
|
82
|
+
# an associated object, if available, from the underlying persistence
|
83
|
+
# layer.
|
84
|
+
def get(key)
|
85
|
+
raise NoMethodError.new('get')
|
86
|
+
end # get
|
87
|
+
|
88
|
+
# <b>This method MUST be implemented by a subclass!</b>
|
89
|
+
#
|
90
|
+
# The implementation of this method should take a key, a value, and
|
91
|
+
# an expiry time and save it to the persistence store, where it should
|
92
|
+
# live until it is either deleted by the user of the expiry time has passed.
|
93
|
+
def set(key, value, expiry_time = configatron.cachetastic.defaults.default_expiry)
|
94
|
+
raise NoMethodError.new('set')
|
95
|
+
end # set
|
96
|
+
|
97
|
+
# <b>This method MUST be implemented by a subclass!</b>
|
98
|
+
#
|
99
|
+
# The implementation of this method should take a key and remove
|
100
|
+
# an object, if it exists, from an underlying persistence store.
|
101
|
+
def delete(key)
|
102
|
+
raise NoMethodError.new('delete')
|
103
|
+
end # delete
|
104
|
+
|
105
|
+
# <b>This method MUST be implemented by a subclass!</b>
|
106
|
+
#
|
107
|
+
# The implementation of this method is expected to delete all
|
108
|
+
# objects belonging to the associated cache from the underlying
|
109
|
+
# persistence store. It is <b>NOT</b> meant to delete <b>ALL</b>
|
110
|
+
# objects across <b>ALL</b> caches for the underlying persistence
|
111
|
+
# store. That would be very very bad!!
|
112
|
+
def expire_all
|
113
|
+
raise NoMethodError.new('expire_all')
|
114
|
+
end # expire_all
|
115
|
+
|
116
|
+
# Allows an adapter to transform the key
|
117
|
+
# to a safe representation for it's backend.
|
118
|
+
# For example, the key: '$*...123()%~q' is not a
|
119
|
+
# key for the file system, so the
|
120
|
+
# Cachetastic::Adapters::File class should override
|
121
|
+
# this to make it safe for the file system.
|
122
|
+
def transform_key(key)
|
123
|
+
key
|
124
|
+
end
|
125
|
+
|
126
|
+
# <b>This method MUST be implemented by a subclass!</b>
|
127
|
+
#
|
128
|
+
# The implementation of this method should return <tt>true</tt>
|
129
|
+
# if the adapter is in a valid state, and <tt>false</tt> if it is
|
130
|
+
# not.
|
131
|
+
def valid?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
def debug? # :nodoc:
|
136
|
+
return self.debug if self.respond_to?(:debug)
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
140
|
+
def marshal(value) # :nodoc:
|
141
|
+
return nil if value.nil?
|
142
|
+
case self.marshal_method.to_sym
|
143
|
+
when :yaml
|
144
|
+
return YAML.dump(value)
|
145
|
+
when :ruby
|
146
|
+
return Marshal.dump(value)
|
147
|
+
else
|
148
|
+
return value
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def unmarshal(value) # :nodoc:
|
153
|
+
return nil if value.nil?
|
154
|
+
case self.marshal_method.to_sym
|
155
|
+
when :yaml
|
156
|
+
return YAML.load(value)
|
157
|
+
when :ruby
|
158
|
+
return Marshal.load(value)
|
159
|
+
else
|
160
|
+
return value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
def define_accessor(key)
|
166
|
+
instance_eval(%{
|
167
|
+
def #{key}
|
168
|
+
@#{key}
|
169
|
+
end
|
170
|
+
def #{key}=(x)
|
171
|
+
@#{key} = x
|
172
|
+
end
|
173
|
+
})
|
174
|
+
end
|
175
|
+
|
176
|
+
end # Base
|
177
|
+
end # Adapters
|
178
|
+
end # Cachetastic
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
module Adapters
|
3
|
+
# An adapter to cache objects to the file system.
|
4
|
+
#
|
5
|
+
# This adapter supports the following configuration settings,
|
6
|
+
# in addition to the default settings:
|
7
|
+
#
|
8
|
+
# configatron.cachetastic.defaults.storage_path = ::File.join(FileUtils.pwd, 'cachetastic')
|
9
|
+
# configatron.cachetastic.defaults.marshal_method = :yaml
|
10
|
+
#
|
11
|
+
# The <tt>storage_path</tt> setting defines the path to where cached
|
12
|
+
# objects are written to on disk.
|
13
|
+
#
|
14
|
+
# See <tt>Cachetastic::Adapters::Base</tt> for a list of public API
|
15
|
+
# methods.
|
16
|
+
class File < Cachetastic::Adapters::Base
|
17
|
+
|
18
|
+
def initialize(klass) # :nodoc:
|
19
|
+
define_accessor(:storage_path)
|
20
|
+
self.storage_path = ::File.join(FileUtils.pwd, 'cachetastic')
|
21
|
+
super
|
22
|
+
self.marshal_method = :yaml if self.marshal_method == :none
|
23
|
+
@_file_paths = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(key) # :nodoc:
|
27
|
+
path = file_path(key)
|
28
|
+
val = nil
|
29
|
+
val = ::File.read(path) if ::File.exists?(path)
|
30
|
+
return val
|
31
|
+
end # get
|
32
|
+
|
33
|
+
def set(key, value, expiry_time = configatron.cachetastic.defaults.default_expiry) # :nodoc:
|
34
|
+
so = Cachetastic::Cache::StoreObject.new(key, value, expiry_time.from_now)
|
35
|
+
path = file_path(key)
|
36
|
+
::File.open(path, 'w') {|f| f.write marshal(so)}
|
37
|
+
value
|
38
|
+
end # set
|
39
|
+
|
40
|
+
def delete(key) # :nodoc:
|
41
|
+
FileUtils.rm(file_path(key))
|
42
|
+
end # delete
|
43
|
+
|
44
|
+
def expire_all # :nodoc:
|
45
|
+
@_file_paths = {}
|
46
|
+
::FileUtils.rm_rf(::File.join(self.storage_path, klass.name.underscore))
|
47
|
+
return nil
|
48
|
+
end # expire_all
|
49
|
+
|
50
|
+
def transform_key(key) # :nodoc:
|
51
|
+
key.to_s.hexdigest
|
52
|
+
end
|
53
|
+
|
54
|
+
def file_path(key) # :nodoc:
|
55
|
+
path = @_file_paths[key]
|
56
|
+
if path.nil?
|
57
|
+
path = ::File.join(self.storage_path, klass.name.underscore, transform_key(key).scan(/(.{1,4})/).flatten, 'cache.data')
|
58
|
+
@_file_paths[key] = path
|
59
|
+
FileUtils.mkdir_p(::File.dirname(path))
|
60
|
+
end
|
61
|
+
return path
|
62
|
+
end
|
63
|
+
|
64
|
+
end # File
|
65
|
+
end # Adapters
|
66
|
+
end # Cachetastic
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
module Adapters
|
3
|
+
# An adapter to cache objects to memory. It is important to note
|
4
|
+
# that this cache is <b>volatile</b>. If the VM it is running in
|
5
|
+
# shuts down, everything in the cache gets vaporized.
|
6
|
+
#
|
7
|
+
# See <tt>Cachetastic::Adapters::Base</tt> for a list of public API
|
8
|
+
# methods.
|
9
|
+
class LocalMemory < Cachetastic::Adapters::Base
|
10
|
+
|
11
|
+
def initialize(klass) # :nodoc:
|
12
|
+
super
|
13
|
+
@_store = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key) # :nodoc:
|
17
|
+
@_store[key]
|
18
|
+
end # get
|
19
|
+
|
20
|
+
def set(key, value, expiry_time = configatron.cachetastic.defaults.default_expiry) # :nodoc:
|
21
|
+
so = Cachetastic::Cache::StoreObject.new(key, value, expiry_time.from_now)
|
22
|
+
@_store[key] = marshal(so)
|
23
|
+
value
|
24
|
+
end # set
|
25
|
+
|
26
|
+
def delete(key) # :nodoc:
|
27
|
+
@_store.delete(key)
|
28
|
+
end # delete
|
29
|
+
|
30
|
+
def expire_all # :nodoc:
|
31
|
+
@_store = {}
|
32
|
+
return nil
|
33
|
+
end # expire_all
|
34
|
+
|
35
|
+
end # LocalMemory
|
36
|
+
end # Adapters
|
37
|
+
end # Cachetastic
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
module Adapters
|
3
|
+
# An adapter to cache objects to the file system.
|
4
|
+
#
|
5
|
+
# This adapter supports the following configuration settings,
|
6
|
+
# in addition to the default settings:
|
7
|
+
#
|
8
|
+
# configatron.cachetastic.defaults.servers = ['127.0.0.1:11211']
|
9
|
+
# configatron.cachetastic.defaults.mc_options = {:c_threshold => 10_000,
|
10
|
+
# :compression => true,
|
11
|
+
# :debug => false,
|
12
|
+
# :readonly => false,
|
13
|
+
# :urlencode => false}
|
14
|
+
# configatron.cachetastic.delete_delay = 0
|
15
|
+
#
|
16
|
+
# The <tt>servers</tt> setting defines an <tt>Array</tt> of Mecached
|
17
|
+
# servers, represented as "<host>:<port>".
|
18
|
+
#
|
19
|
+
# The <tt>mc_options</tt> setting is a <tt>Hash</tt> of settings required
|
20
|
+
# by Memcached. See the Memcached documentation for more information on
|
21
|
+
# what the settings mean.
|
22
|
+
#
|
23
|
+
# The <tt>delete_delay</tt> setting tells Memcached how long to wait
|
24
|
+
# before it deletes the object. This is not the same as <tt>expiry_time</tt>.
|
25
|
+
# It is only used when the <tt>delete</tt> method is called.
|
26
|
+
#
|
27
|
+
# See <tt>Cachetastic::Adapters::Base</tt> for a list of public API
|
28
|
+
# methods.
|
29
|
+
class Memcached < Cachetastic::Adapters::Base
|
30
|
+
|
31
|
+
def initialize(klass) # :nodoc:
|
32
|
+
define_accessor(:servers)
|
33
|
+
define_accessor(:mc_options)
|
34
|
+
define_accessor(:delete_delay)
|
35
|
+
self.delete_delay = 0
|
36
|
+
self.servers = ['127.0.0.1:11211']
|
37
|
+
self.mc_options = {:c_threshold => 10_000,
|
38
|
+
:compression => true,
|
39
|
+
:debug => false,
|
40
|
+
:readonly => false,
|
41
|
+
:urlencode => false}
|
42
|
+
super
|
43
|
+
connection
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(key) # :nodoc:
|
47
|
+
connection.get(transform_key(key), false)
|
48
|
+
end # get
|
49
|
+
|
50
|
+
def set(key, value, expiry_time = configatron.cachetastic.defaults.default_expiry) # :nodoc:
|
51
|
+
connection.set(transform_key(key), marshal(value), expiry_time, false)
|
52
|
+
end # set
|
53
|
+
|
54
|
+
def delete(key) # :nodoc:
|
55
|
+
connection.delete(transform_key(key), self.delete_delay)
|
56
|
+
end # delete
|
57
|
+
|
58
|
+
def expire_all # :nodoc:
|
59
|
+
increment_version
|
60
|
+
@_mc_connection = nil
|
61
|
+
return nil
|
62
|
+
end # expire_all
|
63
|
+
|
64
|
+
def transform_key(key) # :nodoc:
|
65
|
+
key.to_s.hexdigest
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return <tt>false</tt> if the connection to Memcached is
|
69
|
+
# either <tt>nil</tt> or not active.
|
70
|
+
def valid?
|
71
|
+
return false if @_mc_connection.nil?
|
72
|
+
return false unless @_mc_connection.active?
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def connection
|
78
|
+
unless @_mc_connection && valid? && @_ns_version == get_version
|
79
|
+
@_mc_connection = MemCache.new(self.servers, self.mc_options.merge(:namespace => namespace))
|
80
|
+
end
|
81
|
+
@_mc_connection
|
82
|
+
end
|
83
|
+
|
84
|
+
def ns_connection
|
85
|
+
unless @_ns_connection
|
86
|
+
@_ns_connection = MemCache.new(self.servers, self.mc_options.merge(:namespace => :namespace_versions))
|
87
|
+
end
|
88
|
+
@_ns_connection
|
89
|
+
end
|
90
|
+
|
91
|
+
def increment_version
|
92
|
+
name = self.klass.name
|
93
|
+
v = get_version
|
94
|
+
ns_connection.set(name, v + 1)
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_version
|
98
|
+
name = self.klass.name
|
99
|
+
v = ns_connection.get(name)
|
100
|
+
if v.nil?
|
101
|
+
ns_connection.set(name, 1)
|
102
|
+
v = 1
|
103
|
+
end
|
104
|
+
v
|
105
|
+
end
|
106
|
+
|
107
|
+
def namespace
|
108
|
+
@_ns_version = get_version
|
109
|
+
"#{self.klass.name}.#{@_ns_version}"
|
110
|
+
end
|
111
|
+
|
112
|
+
end # Memcached
|
113
|
+
end # Adapters
|
114
|
+
end # Cachetastic
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
# When creating a new 'Cache' this class should be extended.
|
3
|
+
# Once extended you'll only need to override just the methods
|
4
|
+
# that are different for your cache.
|
5
|
+
# class MyAwesomeCache < Cachetastic::Cache
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# MyAwesomeCache.set(1, "One")
|
9
|
+
# MyAwesomeCache.get(1) # => "One"
|
10
|
+
# MyAwesomeCache.update(1, "One!!")
|
11
|
+
# MyAwesomeCache.get(1) # => "One!!"
|
12
|
+
# MyAwesomeCache.delete(1)
|
13
|
+
# MyAwesomeCache.get(1) # => nil
|
14
|
+
#
|
15
|
+
# class MyAwesomeCache < Cachetastic::Cache
|
16
|
+
# class << self
|
17
|
+
# def get(key)
|
18
|
+
# super(key) do
|
19
|
+
# set(key, key * 10)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# MyAwesomeCache.set(1, "One")
|
26
|
+
# MyAwesomeCache.get(1) # => "One"
|
27
|
+
# MyAwesomeCache.delete(1)
|
28
|
+
# MyAwesomeCache.get(1) # => 10
|
29
|
+
class Cache
|
30
|
+
|
31
|
+
# everything is done at the class level. there won't be any 'instances of it'
|
32
|
+
# using class << self means we don't have to prefix each method with 'self.'
|
33
|
+
class << self
|
34
|
+
|
35
|
+
# Returns an object from the cache for a given key.
|
36
|
+
# If the object comes back as nil and a block is given
|
37
|
+
# that block will be run and the results of the block
|
38
|
+
# will be returned. This can be used to JIT caches, just make
|
39
|
+
# sure in the block to call the set method because the
|
40
|
+
# results of the block are not automatically cached.
|
41
|
+
def get(key, &block)
|
42
|
+
do_with_logging(:get, key) do
|
43
|
+
val = self.adapter.get(key)
|
44
|
+
handle_store_object(key, adapter.unmarshal(val), &block)
|
45
|
+
end
|
46
|
+
end # get
|
47
|
+
|
48
|
+
# Set a particular object info the cache for the given key.
|
49
|
+
#
|
50
|
+
# An optional third parameter sets the expiry time for the object in the cache.
|
51
|
+
# If no expiry_time is passed in then the default expiry_time that has been configured
|
52
|
+
# will be used.
|
53
|
+
#
|
54
|
+
# If there is an the expiry_swing setting is configured it will be +/- to the
|
55
|
+
# expiry time.
|
56
|
+
def set(key, value, expiry_time = nil)
|
57
|
+
do_with_logging(:set, key) do
|
58
|
+
self.adapter.set(key, value, calculate_expiry_time(expiry_time))
|
59
|
+
end
|
60
|
+
end # set
|
61
|
+
|
62
|
+
# Deletes an object from the cache.
|
63
|
+
def delete(key)
|
64
|
+
do_with_logging(:delete, key) do
|
65
|
+
self.adapter.delete(key)
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end # delete
|
69
|
+
|
70
|
+
# Expires all objects for this cache.
|
71
|
+
def expire_all
|
72
|
+
do_with_logging(:expire_all, nil) do
|
73
|
+
self.adapter.expire_all
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end # expire_all
|
77
|
+
|
78
|
+
# Returns the underlying Cachetastic::Adapters::Base for this cache.
|
79
|
+
def adapter
|
80
|
+
unless @_adapter && @_adapter.valid?
|
81
|
+
@_adapter = Cachetastic::Adapters.build(cache_klass)
|
82
|
+
end
|
83
|
+
@_adapter
|
84
|
+
end # adapter
|
85
|
+
|
86
|
+
# Clears the adapter so it can be redefined. This is useful if you have
|
87
|
+
# reconfigured the cache to use a different adapater, or different settings.
|
88
|
+
def clear_adapter!
|
89
|
+
@_adapter = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def cache_klass # :nodoc:
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the Cachetastic::Logger for this cache.
|
97
|
+
def logger
|
98
|
+
unless @_logger
|
99
|
+
@_logger = Cachetastic::Logger.new(adapter.logger)
|
100
|
+
end
|
101
|
+
@_logger
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
# If the expiry time is set to 60 minutes and the expiry_swing time is set to
|
106
|
+
# 15 minutes, this method will return a number between 45 minutes and 75 minutes.
|
107
|
+
def calculate_expiry_time(expiry_time) # :doc:
|
108
|
+
expiry_time = self.adapter.default_expiry if expiry_time.nil?
|
109
|
+
exp_swing = self.adapter.expiry_swing
|
110
|
+
if exp_swing && exp_swing != 0
|
111
|
+
swing = rand(exp_swing.to_i)
|
112
|
+
case rand(2)
|
113
|
+
when 0
|
114
|
+
expiry_time = (expiry_time.to_i + swing)
|
115
|
+
when 1
|
116
|
+
expiry_time = (expiry_time.to_i - swing)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
expiry_time
|
120
|
+
end
|
121
|
+
|
122
|
+
def handle_store_object(key, val, &block)
|
123
|
+
if val.is_a?(Cachetastic::Cache::StoreObject)
|
124
|
+
if val.expired?
|
125
|
+
self.delete(key)
|
126
|
+
val = nil
|
127
|
+
else
|
128
|
+
val = val.value
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if val.respond_to?(:empty?)
|
133
|
+
val = nil if val.empty?
|
134
|
+
elsif val.respond_to?(:blank?)
|
135
|
+
val = nil if val.blank?
|
136
|
+
end
|
137
|
+
return val unless val.nil?
|
138
|
+
|
139
|
+
val = yield if block_given?
|
140
|
+
return val
|
141
|
+
end
|
142
|
+
|
143
|
+
def do_with_logging(action, key)
|
144
|
+
if adapter.debug?
|
145
|
+
start_time = Time.now
|
146
|
+
logger.debug(:starting, action, cache_klass.name, key)
|
147
|
+
res = yield if block_given?
|
148
|
+
end_time = Time.now
|
149
|
+
str = ''
|
150
|
+
unless res.nil?
|
151
|
+
str = "[#{res.class.name}]"
|
152
|
+
str << "\t[Size = #{res.size}]" if res.respond_to? :size
|
153
|
+
str << "\t" << res.inspect
|
154
|
+
end
|
155
|
+
logger.debug(:finished, action, cache_klass.name, key, (end_time - start_time), str)
|
156
|
+
return res
|
157
|
+
else
|
158
|
+
return yield if block_given?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end # class << self
|
163
|
+
|
164
|
+
end # Cache
|
165
|
+
end # Cachetastic
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
# Include this module into an Object to achieve simplistic Object level caching.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# class Person
|
6
|
+
# include Cachetastic::Cacheable
|
7
|
+
#
|
8
|
+
# attr_accessor :name
|
9
|
+
#
|
10
|
+
# def cachetastic_key
|
11
|
+
# self.name
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def always_the_same(x, y)
|
15
|
+
# cacher("always_the_same") do
|
16
|
+
# x + y
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# end
|
21
|
+
module Cacheable
|
22
|
+
|
23
|
+
module ClassAndInstanceMethods
|
24
|
+
# Returns the Cachetastic::Cache object associated with the object.
|
25
|
+
# If a cache hasn't been defined the one will be created on the fly.
|
26
|
+
# The cache for the object is expected to be defined as:
|
27
|
+
# Cachetastic::Cacheable::{CLASS_NAME_HERE}Cache
|
28
|
+
#
|
29
|
+
# Examples:
|
30
|
+
# class Person
|
31
|
+
# include Cachetastic::Cacheable
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Person.cache_class # => Cachetastic::Cacheable::PersonCache
|
35
|
+
#
|
36
|
+
# class Admin::Person
|
37
|
+
# include Cachetastic::Cacheable
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Admin::Person.cache_class # => Cachetastic::Cacheable::Admin_PersonCache
|
41
|
+
def cache_class
|
42
|
+
n = self.class.name
|
43
|
+
n = self.name if n == "Class"
|
44
|
+
c_name = "Cachetastic::Cacheable::#{n.gsub('::', '_')}Cache"
|
45
|
+
begin
|
46
|
+
return c_name.constantize
|
47
|
+
rescue NameError => e
|
48
|
+
eval %{
|
49
|
+
class #{c_name} < Cachetastic::Cache
|
50
|
+
|
51
|
+
def self.cache_klass
|
52
|
+
#{n}
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
}
|
57
|
+
return c_name.constantize
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# How much did I want to call this method cache?? It originally was that, but
|
63
|
+
# in Rails 2.0 they decided to use that name, so I had to rename this method.
|
64
|
+
# This method will attempt to get an object from the cache for a given key.
|
65
|
+
# If the object is nil and a block is given the block will be run, and the results
|
66
|
+
# of the block will be automatically cached.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# class Person
|
70
|
+
# include Cachetastic::Cacheable
|
71
|
+
#
|
72
|
+
# def always_the_same(x,y)
|
73
|
+
# cacher("always_the_same") do
|
74
|
+
# x + y
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# Person.new.always_the_same(1,2) # => 3
|
80
|
+
# Person.new.always_the_same(2,2) # => 3
|
81
|
+
# Person.new.always_the_same(3,3) # => 3
|
82
|
+
# Person.cacher("always_the_same") # => 3
|
83
|
+
# Person.get_from_cache("always_the_same") # => 3
|
84
|
+
# Cachetastic::Cacheable::PersonCache.get("always_the_same") # => 3
|
85
|
+
#
|
86
|
+
# Person.cacher("say_hi") {"Hi There"} # => "Hi There"
|
87
|
+
# Person.get_from_cache("say_hi") # => "Hi There"
|
88
|
+
# Cachetastic::Cacheable::PersonCache.get("say_hi") # => "Hi There"
|
89
|
+
def cacher(key, expiry = nil)
|
90
|
+
cache_class.get(key) do
|
91
|
+
if block_given?
|
92
|
+
res = yield
|
93
|
+
cache_class.set(key, res, expiry)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Expires the entire cache associated with this objects's cache.
|
99
|
+
#
|
100
|
+
# Example:
|
101
|
+
# class Person
|
102
|
+
# include Cachetastic::Cacheable
|
103
|
+
# attr_accessor :name
|
104
|
+
# def cachetastic_key
|
105
|
+
# self.name
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# Person.set_into_cache(1, "one")
|
110
|
+
# Person.get_from_cache(1) # => "one"
|
111
|
+
# Person.expire_all
|
112
|
+
# Person.get_from_cache(1) # => nil
|
113
|
+
# Person.set_into_cache(1, "one")
|
114
|
+
# Person.get_from_cache(1) # => "one"
|
115
|
+
# Cachetastic::Cacheable::PersonCache.expire_all
|
116
|
+
# Person.get_from_cache(1) # => nil
|
117
|
+
def expire_all
|
118
|
+
cache_class.expire_all
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# --------------------------
|
124
|
+
# Instance only methods:
|
125
|
+
|
126
|
+
# Unless the object's cachetastic_key method returns nil this method will store
|
127
|
+
# the object in the cache using the object's cachetastic_key as the key.
|
128
|
+
# You *MUST* create an instance level method called cachetastic_key and
|
129
|
+
# have it return a valid key! If you return nil from the cachetastic_key method or you will not be
|
130
|
+
# able to use the cache_self and uncache_self methods.
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
# class Person
|
134
|
+
# include Cachetastic::Cacheable
|
135
|
+
# attr_accessor :name
|
136
|
+
# def cachetastic_key
|
137
|
+
# self.name
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# Person.get_from_cache("Mark Bates") # => nil
|
142
|
+
# p = Person.new
|
143
|
+
# p.name = "Mark Bates"
|
144
|
+
# p.cache_self
|
145
|
+
# Person.get_from_cache("Mark Bates") # => "Mark Bates"
|
146
|
+
def cache_self
|
147
|
+
cache_class.set(self.cachetastic_key, self) unless self.cachetastic_key.nil?
|
148
|
+
end
|
149
|
+
|
150
|
+
# Unless the object's cachetastic_key method returns nil this method will delete
|
151
|
+
# the object in the cache using the object's cachetastic_key as the key.
|
152
|
+
# You *MUST* create an instance level method called cachetastic_key and
|
153
|
+
# have it return a valid key! If you return nil from the cachetastic_key method or you will not be
|
154
|
+
# able to use the cache_self and uncache_self methods.
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
# class Person
|
158
|
+
# include Cachetastic::Cacheable
|
159
|
+
# attr_accessor :name
|
160
|
+
# def cachetastic_key
|
161
|
+
# self.name
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# Person.get_from_cache("Mark Bates") # => nil
|
166
|
+
# p = Person.new
|
167
|
+
# p.name = "Mark Bates"
|
168
|
+
# p.cache_self
|
169
|
+
# Person.get_from_cache("Mark Bates") # => "Mark Bates"
|
170
|
+
# p.uncache_self
|
171
|
+
# Person.get_from_cache("Mark Bates") # => nil
|
172
|
+
def uncache_self
|
173
|
+
cache_class.delete(self.cachetastic_key) unless self.cachetastic_key.nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
# --------------------------
|
177
|
+
|
178
|
+
def self.included(klass) # :nodoc:
|
179
|
+
klass.send(:include, ClassAndInstanceMethods)
|
180
|
+
klass.extend(ClassOnlyMethods)
|
181
|
+
klass.extend(ClassAndInstanceMethods)
|
182
|
+
end
|
183
|
+
|
184
|
+
module ClassOnlyMethods
|
185
|
+
# Returns an object from the cache for a given key.
|
186
|
+
def get_from_cache(key, &block)
|
187
|
+
cache_class.get(key, &block)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Deletes an object from the cache for a given key.
|
191
|
+
def delete_from_cache(key)
|
192
|
+
cache_class.delete(key)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Sets an object into the cache for a given key.
|
196
|
+
def set_into_cache(key, value, expiry = 0)
|
197
|
+
cache_class.set(key, value, expiry)
|
198
|
+
end
|
199
|
+
end # ClassMethods
|
200
|
+
|
201
|
+
end # Cacheable
|
202
|
+
end # Cachetastic
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
# This class handles logging for the caches and their adapters.
|
3
|
+
# This class exists simply to supply the ability to write to
|
4
|
+
# multiple loggers simultaneously from a single call. It also
|
5
|
+
# creates a standardized message to write to those loggers.
|
6
|
+
#
|
7
|
+
# It is important that any logger type of class you decide to use
|
8
|
+
# reponds to the following methods:
|
9
|
+
# fatal(message)
|
10
|
+
# error(message)
|
11
|
+
# warn(message)
|
12
|
+
# info(message)
|
13
|
+
# debug(message)
|
14
|
+
class Logger
|
15
|
+
|
16
|
+
# An <tt>Array</tt> of 'real' loggers to write to.
|
17
|
+
attr_accessor :loggers
|
18
|
+
|
19
|
+
# The <tt>initialize</tt> method takes an <tt>Array</tt>
|
20
|
+
# of your favorite logger style classes to write to.
|
21
|
+
def initialize(*loggers)
|
22
|
+
@loggers = [loggers].flatten
|
23
|
+
end
|
24
|
+
|
25
|
+
LOG_LEVELS = [:fatal, :error, :warn, :info, :debug] # :nodoc:
|
26
|
+
|
27
|
+
LOG_LEVELS.each do |level|
|
28
|
+
define_method(level) do |*args|
|
29
|
+
lm = "[CACHE] [#{level.to_s.upcase}]\t#{Time.now.strftime("%m/%d/%y %H:%M:%S")}"
|
30
|
+
exs = []
|
31
|
+
args.each do |arg|
|
32
|
+
if arg.is_a?(Exception)
|
33
|
+
exs << arg
|
34
|
+
continue
|
35
|
+
end
|
36
|
+
lm << "\t" << arg.to_s
|
37
|
+
end
|
38
|
+
exs.each do |ex|
|
39
|
+
lm << "\n#{ex.message}\n" << ex.backtrace.join("\n")
|
40
|
+
end
|
41
|
+
# puts "lm: #{lm}"
|
42
|
+
self.loggers.each do |log|
|
43
|
+
log.send(level, lm)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end # Logger
|
49
|
+
end # Cachetastic
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Cachetastic # :nodoc:
|
2
|
+
class Cache
|
3
|
+
|
4
|
+
class StoreObject # :nodoc:
|
5
|
+
attr_accessor :expires_at
|
6
|
+
attr_accessor :key
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def initialize(key, value, expires_at)
|
10
|
+
self.key = key
|
11
|
+
self.value = value
|
12
|
+
self.expires_at = expires_at
|
13
|
+
end
|
14
|
+
|
15
|
+
def expired?
|
16
|
+
return Time.now > self.expires_at
|
17
|
+
end
|
18
|
+
|
19
|
+
end # StoreObject
|
20
|
+
|
21
|
+
end # Cache
|
22
|
+
end # Cachetastic
|
data/lib/cachetastic.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'configatron'
|
2
|
+
require 'logger'
|
3
|
+
require 'activesupport'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'memcache'
|
6
|
+
|
7
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'cachetastic', '**/*.rb')).each do |f|
|
8
|
+
require File.expand_path(f)
|
9
|
+
end
|
10
|
+
|
11
|
+
configatron.cachetastic.defaults.set_default(:marshal_method, :none)
|
12
|
+
configatron.cachetastic.defaults.set_default(:expiry_swing, 0)
|
13
|
+
configatron.cachetastic.defaults.set_default(:default_expiry, 86400)
|
14
|
+
configatron.cachetastic.defaults.set_default(:debug, true)
|
15
|
+
configatron.cachetastic.defaults.set_default(:adapter, Cachetastic::Adapters::LocalMemory)
|
16
|
+
log_path = File.join(FileUtils.pwd, 'log', 'cachetastic.log')
|
17
|
+
FileUtils.mkdir_p(File.dirname(log_path))
|
18
|
+
logger = ::Logger.new(log_path)
|
19
|
+
logger.level = ::Logger::DEBUG
|
20
|
+
configatron.cachetastic.defaults.set_default(:logger, logger)
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: markbates-cachetastic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0.20090611142033
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Bates
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-11 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: configatron
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.2
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: memcache-client
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.4
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: activesupport
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.3.2
|
44
|
+
version:
|
45
|
+
description: A very simple, yet very powerful caching framework for Ruby.
|
46
|
+
email: mark@mackframework.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README
|
53
|
+
- LICENSE
|
54
|
+
files:
|
55
|
+
- lib/cachetastic/adapters/base.rb
|
56
|
+
- lib/cachetastic/adapters/file.rb
|
57
|
+
- lib/cachetastic/adapters/local_memory.rb
|
58
|
+
- lib/cachetastic/adapters/memcached.rb
|
59
|
+
- lib/cachetastic/cache.rb
|
60
|
+
- lib/cachetastic/cacheable.rb
|
61
|
+
- lib/cachetastic/extensions/string.rb
|
62
|
+
- lib/cachetastic/logger.rb
|
63
|
+
- lib/cachetastic/store_object.rb
|
64
|
+
- lib/cachetastic.rb
|
65
|
+
- README
|
66
|
+
- LICENSE
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://www.metabates.com
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project: magrathea
|
89
|
+
rubygems_version: 1.2.0
|
90
|
+
signing_key:
|
91
|
+
specification_version: 2
|
92
|
+
summary: A very simple, yet very powerful caching framework for Ruby.
|
93
|
+
test_files: []
|
94
|
+
|