sequel-query-cache 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/History.md +0 -0
- data/LICENSE +21 -0
- data/README.md +112 -0
- data/Rakefile +2 -0
- data/lib/sequel-query-cache.rb +28 -0
- data/lib/sequel-query-cache/class_methods.rb +20 -0
- data/lib/sequel-query-cache/dataset_methods.rb +254 -0
- data/lib/sequel-query-cache/driver.rb +58 -0
- data/lib/sequel-query-cache/driver/dalli.rb +18 -0
- data/lib/sequel-query-cache/driver/memcache.rb +14 -0
- data/lib/sequel-query-cache/instance_methods.rb +72 -0
- data/lib/sequel-query-cache/serializer/json.rb +24 -0
- data/lib/sequel-query-cache/serializer/message_pack.rb +25 -0
- data/lib/sequel-query-cache/version.rb +13 -0
- data/sequel-query-cache.gemspec +28 -0
- data/spec/lib/sequel-query-cache/driver/dalli_driver_spec.rb +8 -0
- data/spec/lib/sequel-query-cache/driver/memcache_driver_spec.rb +7 -0
- data/spec/lib/sequel-query-cache/driver/redis_driver_spec.rb +8 -0
- data/spec/lib/sequel-query-cache/driver_spec.rb +35 -0
- data/spec/lib/sequel-query-cache_spec.rb +5 -0
- data/spec/models/dalli_spec.rb +5 -0
- data/spec/models/memcache_spec.rb +5 -0
- data/spec/models/redis_spec.rb +5 -0
- data/spec/shared/driver.rb +102 -0
- data/spec/shared/query-cache.rb +146 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/models/dalli.rb +5 -0
- data/spec/support/models/memcache.rb +5 -0
- data/spec/support/models/redis.rb +5 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a295f7dea37913880f5a662798aa375f17f80314
|
4
|
+
data.tar.gz: b953a768fd1a58750cf68b5b99b3e1403fc82ed0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 569ecbb3f9433d70e5c39dece54a2f68efb615a6a323fe145c5d46ccc87664fc7d17cced2d764e934dc02b77a032894eac87dc441c824cf44bfc1275fa97f65d
|
7
|
+
data.tar.gz: 19e2ff8e34d236c8e5a724fca56b1cf8392416ff1e6251dd332b18d8b665e4dee18679416ae04491454aa07f1076c588203a9cdf90f8b3fc694774f4a11a4507
|
data/Gemfile
ADDED
data/History.md
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2012-2013 Sho Kusano <rosylilly>
|
2
|
+
Copyright (c) 2013 Joshua Hansen <binarypaladin>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
Sequel Query Cache
|
2
|
+
==================
|
3
|
+
|
4
|
+
Sequel Query Cache is a Sequel model plugin that allows the results of Sequel datasets to be cached in a key-value store like Memcached or Redis. This plugin is flexible and can easily be adapted to other key-value stores.
|
5
|
+
|
6
|
+
Results are serialized for storage by default using JSON or [MessagePack](http://msgpack.org/) if the [MessagePack gem](https://github.com/msgpack/msgpack-ruby) is available. This is also flexible. Any object with a very simple interface can be used to serialize and unserialize data.
|
7
|
+
|
8
|
+
Sequel Query Cache was forked from Sho Kusano's [sequel-cacheable gem](https://github.com/rosylilly/sequel-cacheable) and while it has seen substantial internal architectural changes, it maintains the original spirit of being easy to adapt to multiple cache stores and serialization options.
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
Sequel Query Cache requires [Sequel](https://github.com/jeremyevans/sequel) and one of the following gems for accessing cache store:
|
14
|
+
|
15
|
+
* [Redis](https://rubygems.org/gems/redis)
|
16
|
+
* [Hiredis](https://rubygems.org/gems/hiredis)
|
17
|
+
* [Memcached](https://rubygems.org/gems/memcache)
|
18
|
+
* [Dalli](https://rubygems.org/gems/dalli)
|
19
|
+
|
20
|
+
Additionally, using [MessagePack](https://github.com/msgpack/msgpack-ruby) is strongly encouraged over the JSON default and is necessary when caching binary data.
|
21
|
+
|
22
|
+
Configuration
|
23
|
+
-------------
|
24
|
+
|
25
|
+
First, initialize a cache client.
|
26
|
+
|
27
|
+
Using Dalli:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
CACHE_CLIENT = Dalli::Client.new('localhost:11211')
|
31
|
+
```
|
32
|
+
|
33
|
+
Using Redis:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
CACHE_CLIENT = Redis.new(host: 'localhost', port: 6379)
|
37
|
+
```
|
38
|
+
|
39
|
+
Then, apply the plugin to all models:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
Sequel::Model.plugin :query_cache, CACHE_CLIENT
|
43
|
+
```
|
44
|
+
|
45
|
+
Or just a select few:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
MyModel.plugin :query_cache, CACHE_CLIENT
|
49
|
+
```
|
50
|
+
|
51
|
+
Configuration Options
|
52
|
+
---------------------
|
53
|
+
|
54
|
+
The plugin method also accepts a hash of options as a final argument, like so:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
Sequel::Model.plugin :query_cache, CACHE_CLIENT, ttl: 3600
|
58
|
+
```
|
59
|
+
|
60
|
+
The current options are:
|
61
|
+
|
62
|
+
* **ttl**: Time to live. The amount of time before a given cache expires automatically.
|
63
|
+
* **serializer**: An object that responds to serialize and unserialize methods for converting Sequel result hashs and models to serialized strings.
|
64
|
+
* **cache_by_default**: See below.
|
65
|
+
|
66
|
+
Cached Datasets
|
67
|
+
---------------
|
68
|
+
|
69
|
+
When a dataset is set to have its results cached, if a method that would return results is called (e.g. #first or #all) a check will be made to see if there are any cached results. If there are, the cached results will be used. If there are not, the results will be pulled from the database, cached in the store and then passed to the user.
|
70
|
+
|
71
|
+
Whether or not dataset results are cached is determined in one of two ways. The first is to explicitly set a dataset to be cached. For example:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
MyModel.cached.all
|
75
|
+
```
|
76
|
+
|
77
|
+
This will check the cache for results and set them if they do not exist. Caching be be explicitly ignored as well:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
MyModel.uncached.all
|
81
|
+
```
|
82
|
+
|
83
|
+
The second way a determination to cache a dataset is made is by the options set by cache_by_default. By default, these are set to cache the results of any query which has a LIMIT clause set to 1.
|
84
|
+
|
85
|
+
For more details on how cache_by_default, see the documentation for Sequel::Plugins::QueryCache and Sequel::Plugins::QueryCache::DatasetMethods.
|
86
|
+
|
87
|
+
Caches are automatically deleted when a dataset is updated or deleted.
|
88
|
+
|
89
|
+
Cached Models
|
90
|
+
---------------
|
91
|
+
|
92
|
+
Models have a few additional features on top of their respective dataset. Models can have their caches explicitly set or deleted by calling #cache! or #uncache! on a model instance.
|
93
|
+
|
94
|
+
Additionally, models have an #after_save hook that updates caches associated with model instances.
|
95
|
+
|
96
|
+
TODO
|
97
|
+
----
|
98
|
+
|
99
|
+
* The testing suite is completely broken and needs to be updated.
|
100
|
+
* Additional documentation and commenting needs to be added to the source, particularly for the functionality of cache_by_default.
|
101
|
+
* There is basically no need for this plugin to require the use of models and could be used purely as a dataset extension with a tiny model plugin built on top. This should be implemented at some point.
|
102
|
+
|
103
|
+
Thanks
|
104
|
+
------
|
105
|
+
|
106
|
+
* [Sho Kusano](https://github.com/rosylilly)
|
107
|
+
* [Jeremy Watkins](https://github.com/vegasje)
|
108
|
+
|
109
|
+
Copyright
|
110
|
+
---------
|
111
|
+
|
112
|
+
Copyright (c) 2013 Joshua Hansen. See LICENSE for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'sequel'
|
3
|
+
require 'sequel-query-cache/version'
|
4
|
+
require 'sequel-query-cache/driver'
|
5
|
+
require 'sequel-query-cache/class_methods'
|
6
|
+
require 'sequel-query-cache/instance_methods'
|
7
|
+
require 'sequel-query-cache/dataset_methods'
|
8
|
+
|
9
|
+
module Sequel::Plugins
|
10
|
+
module QueryCache
|
11
|
+
def self.configure(model, store, opts={})
|
12
|
+
model.instance_eval do
|
13
|
+
@cache_options = {
|
14
|
+
:ttl => 3600,
|
15
|
+
:cache_by_default => {
|
16
|
+
:always => false,
|
17
|
+
:if_limit => 1
|
18
|
+
}
|
19
|
+
}.merge(opts)
|
20
|
+
|
21
|
+
@cache_driver = Driver.from_store(
|
22
|
+
store,
|
23
|
+
:serializer => @cache_options.delete(:serializer)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Sequel::Plugins
|
3
|
+
module QueryCache
|
4
|
+
module ClassMethods
|
5
|
+
attr_reader :cache_driver, :cache_options
|
6
|
+
|
7
|
+
def cached(opts={})
|
8
|
+
dataset.cached(opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def not_cached
|
12
|
+
dataset.not_cached
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_cached
|
16
|
+
dataset.default_cached
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module Sequel::Plugins
|
5
|
+
module QueryCache
|
6
|
+
module DatasetMethods
|
7
|
+
CACHE_BY_DEFAULT_PROC = lambda do |ds, opts|
|
8
|
+
if ds.opts[:limit] && opts[:if_limit]
|
9
|
+
return true if
|
10
|
+
(opts[:if_limit] == true) ||
|
11
|
+
(opts[:if_limit] >= ds.opts[:limit])
|
12
|
+
end
|
13
|
+
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the model's cache driver.
|
18
|
+
#--
|
19
|
+
# TODO: Caching should be modified to be a database/dataset extension with
|
20
|
+
# a tiny plugin for the models. However, this works just fine for now.
|
21
|
+
#++
|
22
|
+
def cache_driver
|
23
|
+
model.cache_driver
|
24
|
+
end
|
25
|
+
|
26
|
+
# Copies the model's +cache_options+ and merges them with options provided
|
27
|
+
# by +opts+ if any are provided. Returns the current cache_options.
|
28
|
+
#
|
29
|
+
# <tt>@cache_options</tt> is cloned when the dataset is cloned.
|
30
|
+
def cache_options(opts=nil)
|
31
|
+
@cache_options ||= model.cache_options
|
32
|
+
@cache_options = @cache_options.merge(opts) if opts
|
33
|
+
@cache_options
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determines whether or not to cache a dataset based on the configuration
|
37
|
+
# settings of the plugin.
|
38
|
+
#--
|
39
|
+
# TODO: Specify a place to find those settings. However, where those are
|
40
|
+
# applied is currently in flux. Also, further document how this process
|
41
|
+
# actually works.
|
42
|
+
#++
|
43
|
+
def is_cacheable_by_default?
|
44
|
+
cache_by_default = cache_options[:cache_by_default]
|
45
|
+
return false unless cache_by_default
|
46
|
+
return true if cache_by_default[:always]
|
47
|
+
proc = cache_by_default[:proc] || CACHE_BY_DEFAULT_PROC
|
48
|
+
proc.call(self, cache_by_default)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Determines whether or not a dataset should be cached. If
|
52
|
+
# <tt>@is_cacheable</tt> is set that value will be returned, otherwise the
|
53
|
+
# default value will be returned by #is_cacheable_by_default?
|
54
|
+
def is_cacheable?
|
55
|
+
defined?(@is_cacheable) ? @is_cacheable : is_cacheable_by_default?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the value for <tt>@is_cacheable</tt> which is used as the return
|
59
|
+
# value from #is_cacheable?. <tt>@is_cacheable</tt> is cloned when the
|
60
|
+
# dataset is cloned.
|
61
|
+
#
|
62
|
+
# *Note:* In general, #cached and #not_cached should be used to set this
|
63
|
+
# value. This method exists primarily for their use.
|
64
|
+
def is_cacheable=(is_cacheable)
|
65
|
+
@is_cacheable = !!is_cacheable
|
66
|
+
end
|
67
|
+
|
68
|
+
# Clones the current dataset, sets it to be cached and returns the new
|
69
|
+
# dataset. This is useful for chaining purposes:
|
70
|
+
#
|
71
|
+
# dataset.where(column1: true).order(:column2).cached.all
|
72
|
+
#
|
73
|
+
# In the above example, the data would always be pulled from the cache or
|
74
|
+
# cached if it wasn't already. The value of <tt>@is_cacheable</tt> is
|
75
|
+
# cloned when a dataset is cloned, so the following example would also
|
76
|
+
# have the same result:
|
77
|
+
#
|
78
|
+
# dataset.cached.where(column1: true).order(:column2).all
|
79
|
+
#
|
80
|
+
# +opts+ is passed to #cache_options on the new dataset.
|
81
|
+
def cached(opts=nil)
|
82
|
+
c = clone
|
83
|
+
c.cache_options(opts)
|
84
|
+
c.is_cacheable = true
|
85
|
+
c
|
86
|
+
end
|
87
|
+
|
88
|
+
# Clones the current dataset, sets it to not be cached and returns the new
|
89
|
+
# dataset. See #cached for further details and examples.
|
90
|
+
def not_cached
|
91
|
+
c = clone
|
92
|
+
c.is_cacheable = false
|
93
|
+
c
|
94
|
+
end
|
95
|
+
|
96
|
+
# Clones the current dataset, returns the caching state to whatever
|
97
|
+
# would be default for that dataset and returns the new dataset. See
|
98
|
+
# #cached for further details and examples.
|
99
|
+
#
|
100
|
+
# *Note:* This is the "proper" way to clear <tt>@is_cacheable</tt> once
|
101
|
+
# it's been set.
|
102
|
+
def default_cached
|
103
|
+
if defined? @is_cacheable
|
104
|
+
c = clone
|
105
|
+
c.remove_instance_variable(:@is_cacheable)
|
106
|
+
c
|
107
|
+
else
|
108
|
+
self
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Creates a default cache key, which is an MD5 base64 digest of the
|
113
|
+
# the literal select SQL with +Sequel:+ added as a prefix. This value is
|
114
|
+
# memoized because assembling the SQL string and hashing it every time
|
115
|
+
# this method gets called is obnoxious.
|
116
|
+
def default_cache_key
|
117
|
+
@default_cache_key ||= "Sequel:#{Digest::MD5.base64digest(sql)}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns the default cache key if a manual cache key has not been set.
|
121
|
+
# The cache key is used by the storage engines to retrieve cached data.
|
122
|
+
# The default will suffice in almost all instances.
|
123
|
+
def cache_key
|
124
|
+
@cache_key || default_cache_key
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sets a manual cache key for a dataset that overrides the default MD5
|
128
|
+
# hash. This key has no +Sequel:+ prefix, so if that's important, remember
|
129
|
+
# to add it manually.
|
130
|
+
#
|
131
|
+
# *Note:* Setting the cache key manually is *NOT* inherited by cloned
|
132
|
+
# datasets since keys are presumed to be for the current dataset and any
|
133
|
+
# changes, such as where clauses or limits, should result in a new key. In
|
134
|
+
# general, you shouldn't change the cache key unless you have a really
|
135
|
+
# good reason for doing so.
|
136
|
+
def cache_key=(cache_key)
|
137
|
+
@cache_key = cache_key ? cache_key.to_s : nil
|
138
|
+
end
|
139
|
+
|
140
|
+
def clear_cache_keys!
|
141
|
+
remove_instance_variable(:@default_cache_key) if defined? @default_cache_key
|
142
|
+
remove_instance_variable(:@cache_key) if defined? @cache_key
|
143
|
+
end
|
144
|
+
|
145
|
+
# Gets the cache value using the current dataset's key and logs the
|
146
|
+
# action. The underlying driver should return +nil+ in the event that
|
147
|
+
# there is no cached data. Also logs whether there was a hit or miss on
|
148
|
+
# the cache.
|
149
|
+
def cache_get
|
150
|
+
db.log_info("CACHE GET: #{cache_key}")
|
151
|
+
cached_rows = cache_driver.get(cache_key)
|
152
|
+
db.log_info("CACHE #{cached_rows ? 'HIT' : 'MISS'}: #{cache_key}")
|
153
|
+
cached_rows
|
154
|
+
end
|
155
|
+
|
156
|
+
# Sets the cache value using the current dataset's key and logs the
|
157
|
+
# action. In general, this method should not be called directly. It's
|
158
|
+
# exposed because model instances need access to it.
|
159
|
+
#
|
160
|
+
# An +opts+ hash can be passed to override any default options being sent
|
161
|
+
# to the driver. The most common use for this would be to modify the +ttl+
|
162
|
+
# for a cache. However, this should probably be done using #cached rather
|
163
|
+
# than doing anything directly via this method.
|
164
|
+
def cache_set(value, opts={})
|
165
|
+
db.log_info("CACHE SET: #{cache_key}")
|
166
|
+
cache_driver.set(cache_key, value, opts.merge(cache_options))
|
167
|
+
end
|
168
|
+
|
169
|
+
# Deletes the cache value using the current dataset's key and logs the
|
170
|
+
# action.
|
171
|
+
def cache_del
|
172
|
+
db.log_info("CACHE DEL: #{cache_key}")
|
173
|
+
cache_driver.del(cache_key)
|
174
|
+
end
|
175
|
+
|
176
|
+
def cache_clear_on_update
|
177
|
+
@cache_clear_on_update.nil? ? true : @cache_clear_on_update
|
178
|
+
end
|
179
|
+
|
180
|
+
def cache_clear_on_update=(v)
|
181
|
+
@cache_clear_on_update = !!v
|
182
|
+
end
|
183
|
+
|
184
|
+
# Overrides the dataset's existing +update+ method. Deletes an existing
|
185
|
+
# cache after a successful update.
|
186
|
+
def update(values={}, &block)
|
187
|
+
result = super
|
188
|
+
cache_del if is_cacheable? && cache_clear_on_update
|
189
|
+
result
|
190
|
+
end
|
191
|
+
|
192
|
+
# Overrides the dataset's existing +delete+ method. Deletes an existing
|
193
|
+
# cache after a successful delete.
|
194
|
+
def delete(&block)
|
195
|
+
result = super
|
196
|
+
cache_del if is_cacheable?
|
197
|
+
result
|
198
|
+
end
|
199
|
+
|
200
|
+
# Overrides the dataset's existing +fetch_rows+ method. If the dataset is
|
201
|
+
# cacheable it will do one of two things:
|
202
|
+
#
|
203
|
+
# If a cache exists it will yield the cached rows rather query the
|
204
|
+
# database.
|
205
|
+
#
|
206
|
+
# If a cache does not exist it will query the database, store the results
|
207
|
+
# in an array, cache those and then yield the results like the original
|
208
|
+
# method would have.
|
209
|
+
#
|
210
|
+
# *Note:* If you're using PostgreSQL, or another database where +each+
|
211
|
+
# iterates with the cursor rather over the dataset results, you'll lose
|
212
|
+
# that functionality when caching is enabled for a query since the entire
|
213
|
+
# result is iterated first before it is yielded. If that behavior is
|
214
|
+
# important, remember to disable caching for that particular query.
|
215
|
+
def fetch_rows(sql)
|
216
|
+
if is_cacheable?
|
217
|
+
if cached_rows = cache_get
|
218
|
+
# Symbolize the row keys before yielding as they're often strings
|
219
|
+
# when the data is deserialized. Sequel doesn't play nice with
|
220
|
+
# string keys.
|
221
|
+
cached_rows.each{|r| yield r.reduce({}){|h,v| h[v[0].to_sym]=v[1]; h}}
|
222
|
+
else
|
223
|
+
cached_rows = []
|
224
|
+
super(sql){|r| cached_rows << r}
|
225
|
+
cache_set(cached_rows)
|
226
|
+
cached_rows.each{|r| yield r}
|
227
|
+
end
|
228
|
+
else
|
229
|
+
super
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Sets self as the source_dataset on a result if that result supports
|
234
|
+
# source datasets. While it can almost certainly be presumed the result
|
235
|
+
# will, if the dataset's row_proc has been modified for some reason the
|
236
|
+
# result might be different than expected.
|
237
|
+
def each
|
238
|
+
super do |r|
|
239
|
+
r.source_dataset = self if r.respond_to? :source_dataset=
|
240
|
+
yield r
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Overrides the dataset's existing clone method. Clones the existing
|
245
|
+
# dataset but clears any manually set cache key and the memoized default
|
246
|
+
# cache key to ensure it's regenerated by the new dataset.
|
247
|
+
def clone(opts=nil)
|
248
|
+
c = super(opts)
|
249
|
+
c.clear_cache_keys!
|
250
|
+
c
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Sequel::Plugins
|
3
|
+
module QueryCache
|
4
|
+
class Driver
|
5
|
+
def self.from_store(store, opts={})
|
6
|
+
case store.class.name
|
7
|
+
when 'Memcache'
|
8
|
+
require 'sequel-query-cache/driver/memcache'
|
9
|
+
MemcacheDriver.new(store, opts)
|
10
|
+
when 'Dalli::Client'
|
11
|
+
require 'sequel-query-cache/driver/dalli'
|
12
|
+
DalliDriver.new(store, opts)
|
13
|
+
else
|
14
|
+
Driver.new(store, opts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :store, :serializer
|
19
|
+
|
20
|
+
def initialize(store, opts={})
|
21
|
+
@store = store
|
22
|
+
@serializer = opts[:serializer] || _default_serializer
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(key)
|
26
|
+
val = store.get(key)
|
27
|
+
val ? serializer.deserialize(val) : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(key, val, opts={})
|
31
|
+
store.set(key, serializer.serialize(val))
|
32
|
+
expire(key, opts[:ttl]) unless opts[:ttl].nil?
|
33
|
+
val
|
34
|
+
end
|
35
|
+
|
36
|
+
def del(key)
|
37
|
+
store.del(key)
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def expire(key, time)
|
42
|
+
store.expire(key, time)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def _default_serializer
|
48
|
+
if defined? MessagePack
|
49
|
+
require 'sequel-query-cache/serializer/message_pack'
|
50
|
+
Serializer::MessagePack
|
51
|
+
else
|
52
|
+
require 'sequel-query-cache/serializer/json'
|
53
|
+
Serializer::JSON
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Sequel::Plugins
|
3
|
+
module QueryCache
|
4
|
+
class DalliDriver < Driver
|
5
|
+
def del(key)
|
6
|
+
store.delete(key)
|
7
|
+
end
|
8
|
+
|
9
|
+
def expire(key, time)
|
10
|
+
if time > 0
|
11
|
+
store.touch(key, time)
|
12
|
+
else
|
13
|
+
store.delete(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Sequel::Plugins
|
3
|
+
module QueryCache
|
4
|
+
module InstanceMethods
|
5
|
+
# For the purpose of caching, it's helpful to have the dataset that
|
6
|
+
# is actually responsible for creating the model instance since it's
|
7
|
+
# likely that if the instance is updated you'll want the dataset related
|
8
|
+
# to it to be cleaned up. See #recache_source_dataset! for further
|
9
|
+
# information.
|
10
|
+
attr_accessor :source_dataset
|
11
|
+
|
12
|
+
def before_save
|
13
|
+
# Since the cache will be updated after the save is complete there's no
|
14
|
+
# reason to have it deleted by the update process.
|
15
|
+
this.cache_clear_on_update = false
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_save
|
20
|
+
super
|
21
|
+
recache_source_dataset!
|
22
|
+
cache!
|
23
|
+
end
|
24
|
+
|
25
|
+
def cache_key
|
26
|
+
this.cache_key
|
27
|
+
end
|
28
|
+
|
29
|
+
def cache!(opts={})
|
30
|
+
this.cache_set([self], opts) if this.is_cacheable?
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def uncache!
|
35
|
+
this.cache_del if this.is_cacheable?
|
36
|
+
source_dataset.cache_del if source_dataset_cache?
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_msgpack(io=nil)
|
41
|
+
values.to_msgpack(io)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
# There are many instances where the dataset that creates a model instance
|
46
|
+
# is not equal to #this. The two most common instances are when a model
|
47
|
+
# has a unique column that is used on a regular basis to fetch records
|
48
|
+
# (e.g. an email address in a User model) or a foreign key.
|
49
|
+
#
|
50
|
+
# In the event that the source dataset is guaranteed to return only one
|
51
|
+
# result (has a limit statement of 1) it will be cached. If it is not, the
|
52
|
+
# related cached will be cleared in an attempt to clean up potentially
|
53
|
+
# stale queries.
|
54
|
+
def recache_source_dataset!
|
55
|
+
if source_dataset_cache?
|
56
|
+
if source_dataset.opts[:limit] == 1
|
57
|
+
source_dataset.cache_set([self])
|
58
|
+
else
|
59
|
+
source_dataset.cache_del
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def source_dataset_cache?
|
65
|
+
source_dataset &&
|
66
|
+
(source_dataset != this) &&
|
67
|
+
source_dataset.respond_to?(:is_cacheable?) &&
|
68
|
+
source_dataset.is_cacheable?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Sequel::Plugins
|
5
|
+
module QueryCache
|
6
|
+
module Serializer
|
7
|
+
# While this works, if you're using binary data at all, it's not a great
|
8
|
+
# idea and MessagePack is faster.
|
9
|
+
module JSON
|
10
|
+
def self.serialize(obj)
|
11
|
+
binding.pry
|
12
|
+
obj.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
# Keys are specifically *NOT* symbolized here. This is done by
|
16
|
+
# Sequel::Plugins::Cacheable::DatasetMethods#fetch_rows since only the
|
17
|
+
# top level needs to have symbolized keys for Sequel's purposes.
|
18
|
+
def self.deserialize(string)
|
19
|
+
::JSON.parse(string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'msgpack'
|
3
|
+
|
4
|
+
module Sequel::Plugins
|
5
|
+
module QueryCache
|
6
|
+
module Serializer
|
7
|
+
module MessagePack
|
8
|
+
def self.serialize(obj)
|
9
|
+
obj.to_msgpack
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.deserialize(string)
|
13
|
+
::MessagePack.unpack(string)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Adds #to_msgpack to some common classes for packing purposes.
|
21
|
+
[BigDecimal, Bignum, Date, Time, Sequel::SQLTime].each do |klass|
|
22
|
+
unless klass.instance_methods.include? :to_msgpack
|
23
|
+
klass.send(:define_method, :to_msgpack) {|io=nil| to_s.to_msgpack(io)}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require './lib/sequel-query-cache/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'sequel-query-cache'
|
5
|
+
s.version = Sequel::Plugins::QueryCache::VERSION
|
6
|
+
s.license = 'MIT'
|
7
|
+
|
8
|
+
s.authors = ['Joshua Hansen']
|
9
|
+
s.email = ['joshua@amicus-tech.com']
|
10
|
+
s.homepage = 'https://github.com/binarypaladin/sequel-query-cache'
|
11
|
+
|
12
|
+
s.summary = 'A plugin for Sequel that allows dataset results to be cached in Memcached or Redis.'
|
13
|
+
s.description = s.summary
|
14
|
+
|
15
|
+
s.files = Dir.glob('lib/**/*') + [
|
16
|
+
'Gemfile',
|
17
|
+
'History.md',
|
18
|
+
'LICENSE',
|
19
|
+
'Rakefile',
|
20
|
+
'README.md',
|
21
|
+
'sequel-query-cache.gemspec',
|
22
|
+
]
|
23
|
+
|
24
|
+
s.test_files = Dir.glob('spec/**/*')
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
|
27
|
+
s.add_dependency 'sequel', ['>= 3.42', '< 5.0']
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sequel::Plugins::QueryCache::Driver do
|
4
|
+
let(:store) { RedisCli }
|
5
|
+
|
6
|
+
include_examples :driver
|
7
|
+
|
8
|
+
describe '.factory' do
|
9
|
+
subject { described_class.factory(store) }
|
10
|
+
|
11
|
+
context 'when Memcache' do
|
12
|
+
let(:store) { MemcacheCli }
|
13
|
+
|
14
|
+
it { should be_a(Sequel::Plugins::QueryCache::MemcacheDriver) }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when Dalli' do
|
18
|
+
let(:store) { DalliCli }
|
19
|
+
|
20
|
+
it { should be_a(Sequel::Plugins::QueryCache::DalliDriver) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when Redis' do
|
24
|
+
let(:store) { RedisCli }
|
25
|
+
|
26
|
+
it { should be_a(Sequel::Plugins::QueryCache::RedisDriver) }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when Unkown Store' do
|
30
|
+
let(:store) { mock }
|
31
|
+
|
32
|
+
it { should be_a(Sequel::Plugins::QueryCache::Driver) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
shared_examples :driver do
|
2
|
+
let(:pack_lib) { MessagePack }
|
3
|
+
subject(:driver) { described_class.new(store, pack_lib) }
|
4
|
+
|
5
|
+
let(:key) { 'cache_key' }
|
6
|
+
let(:val) { 100 }
|
7
|
+
|
8
|
+
describe '#set' do
|
9
|
+
subject { driver.set(key, val) }
|
10
|
+
|
11
|
+
it { should == val }
|
12
|
+
|
13
|
+
it 'should be stored in cache' do
|
14
|
+
driver.set(key, val)
|
15
|
+
store.get(key).should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with expire' do
|
19
|
+
subject { driver.set(key, val, -1) }
|
20
|
+
|
21
|
+
it 'should be expired cache' do
|
22
|
+
store.get(key).should be_nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#get' do
|
28
|
+
before do
|
29
|
+
driver.set(key, val)
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:get_key) { key }
|
33
|
+
|
34
|
+
subject { driver.get(get_key) }
|
35
|
+
|
36
|
+
context 'be found key' do
|
37
|
+
it { should == val }
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'be not found key' do
|
41
|
+
let(:get_key) { 'not_found' }
|
42
|
+
|
43
|
+
it { should be_nil }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#del' do
|
48
|
+
before do
|
49
|
+
driver.set(key, val)
|
50
|
+
end
|
51
|
+
|
52
|
+
subject(:del_method) { driver.del(key) }
|
53
|
+
|
54
|
+
it { should be_nil }
|
55
|
+
|
56
|
+
it 'should be deleted cache' do
|
57
|
+
store.get(key).should_not be_nil
|
58
|
+
del_method
|
59
|
+
store.get(key).should be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#expire' do
|
64
|
+
before do
|
65
|
+
driver.set(key, val)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should be expired cache' do
|
69
|
+
store.get(key).should_not be_nil
|
70
|
+
driver.expire(key, -1)
|
71
|
+
store.get(key).should be_nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#fetch' do
|
76
|
+
subject(:fetch) do
|
77
|
+
driver.fetch(key) { val }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'be found key' do
|
81
|
+
before do
|
82
|
+
driver.set(key, val)
|
83
|
+
end
|
84
|
+
|
85
|
+
it { should == val }
|
86
|
+
|
87
|
+
it 'should not call #set' do
|
88
|
+
store.should_not_receive(:set)
|
89
|
+
fetch
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'be not found key' do
|
94
|
+
it { should == val }
|
95
|
+
|
96
|
+
it 'should call #set' do
|
97
|
+
store.should_receive(:set).and_call_original
|
98
|
+
fetch
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples :cacheable do
|
4
|
+
let(:model) { described_class }
|
5
|
+
|
6
|
+
before do
|
7
|
+
3.times do
|
8
|
+
model.create(
|
9
|
+
:string => Forgery::Basic.text,
|
10
|
+
:integer => rand(255),
|
11
|
+
:float => rand(3000).to_f / 10.0,
|
12
|
+
:bignum => (2 + rand(10)) ** 100,
|
13
|
+
:numeric => BigDecimal(rand(100).to_s),
|
14
|
+
:date => Date.today,
|
15
|
+
:datetime => DateTime.now,
|
16
|
+
:time => Time.now,
|
17
|
+
:bool => rand(2).odd?
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'ClassMethods' do
|
23
|
+
let(:key) { 'cache_key' }
|
24
|
+
let(:value) { model.first }
|
25
|
+
|
26
|
+
describe '#cache_set' do
|
27
|
+
subject(:cache_set) { model.cache_set(key, value) }
|
28
|
+
|
29
|
+
it { should == value }
|
30
|
+
|
31
|
+
it 'should be stored cache' do
|
32
|
+
cache_set
|
33
|
+
model.cache_driver.get("#{model.name}:#{key}").should == value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#cache_get' do
|
38
|
+
before do
|
39
|
+
model.cache_set(key, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
subject { model.cache_get(key) }
|
43
|
+
|
44
|
+
it { should == value }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#cache_del' do
|
48
|
+
before do
|
49
|
+
model.cache_set(key, value)
|
50
|
+
end
|
51
|
+
|
52
|
+
subject(:cache_del) { model.cache_del(key) }
|
53
|
+
|
54
|
+
it { should be_nil }
|
55
|
+
|
56
|
+
it 'should be deleted cache' do
|
57
|
+
cache_del
|
58
|
+
model.cache_get("#{model.name}:#{key}").should be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#cache_fetch' do
|
63
|
+
it 'should call driver#fetch' do
|
64
|
+
model.cache_driver.should_receive(:fetch).at_least(1).times.and_call_original
|
65
|
+
|
66
|
+
model.cache_fetch('test') do
|
67
|
+
value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'DatasetMethods' do
|
74
|
+
let(:dataset) { model.dataset }
|
75
|
+
|
76
|
+
describe '#all' do
|
77
|
+
subject(:fetch_all) { dataset.all }
|
78
|
+
|
79
|
+
it { should have(3).records }
|
80
|
+
|
81
|
+
it { should == model.all }
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#first' do
|
85
|
+
subject(:fetch_first) { dataset.first }
|
86
|
+
|
87
|
+
it { should be_kind_of(model) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'InstanceMethods' do
|
92
|
+
let(:instance) { model.first }
|
93
|
+
|
94
|
+
describe '#after_initialize' do
|
95
|
+
it 'should call #cache!' do
|
96
|
+
model.any_instance.should_receive(:cache!)
|
97
|
+
model.first
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#after_update' do
|
102
|
+
it 'should call #recache!' do
|
103
|
+
instance.string = 'hoge'
|
104
|
+
instance.should_receive(:recache!)
|
105
|
+
instance.save
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#destroy' do
|
110
|
+
it 'should call #uncache!' do
|
111
|
+
instance.should_receive(:uncache!)
|
112
|
+
instance.destroy
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#delete' do
|
117
|
+
it 'should call #uncache!' do
|
118
|
+
instance.should_receive(:uncache!)
|
119
|
+
instance.delete
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#cache!' do
|
124
|
+
it 'should call .cache_set' do
|
125
|
+
instance = model.first
|
126
|
+
model.should_receive(:cache_set).with(instance.pk.to_s, instance)
|
127
|
+
instance.cache!
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#uncache!' do
|
132
|
+
it 'should call .cache_del' do
|
133
|
+
model.should_receive(:cache_del).at_least(1).times
|
134
|
+
instance.uncache!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#recache!' do
|
139
|
+
it 'should call #uncache! and #cache!' do
|
140
|
+
instance.should_receive(:uncache!)
|
141
|
+
instance.should_receive(:cache!)
|
142
|
+
instance.recache!
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
|
5
|
+
DB = Sequel.sqlite
|
6
|
+
DB.create_table(:spec) do
|
7
|
+
primary_key :id, :auto_increment => true
|
8
|
+
String :string
|
9
|
+
Integer :integer
|
10
|
+
Float :float
|
11
|
+
Bignum :bignum
|
12
|
+
BigDecimal :numeric
|
13
|
+
Date :date
|
14
|
+
DateTime :datetime
|
15
|
+
Time :time, :only_time=>true
|
16
|
+
TrueClass :bool
|
17
|
+
end
|
18
|
+
|
19
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
20
|
+
Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each {|f| require f}
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.after(:each) do
|
24
|
+
RedisCli.flushall
|
25
|
+
MemcacheCli.flush_all
|
26
|
+
DalliCli.flush_all
|
27
|
+
end
|
28
|
+
|
29
|
+
config.around(:each) do |e|
|
30
|
+
DB.transaction do
|
31
|
+
e.run
|
32
|
+
raise Sequel::Rollback
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-query-cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua Hansen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.42'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.42'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.0'
|
33
|
+
description: A plugin for Sequel that allows dataset results to be cached in Memcached
|
34
|
+
or Redis.
|
35
|
+
email:
|
36
|
+
- joshua@amicus-tech.com
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- Gemfile
|
42
|
+
- History.md
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- lib/sequel-query-cache.rb
|
47
|
+
- lib/sequel-query-cache/class_methods.rb
|
48
|
+
- lib/sequel-query-cache/dataset_methods.rb
|
49
|
+
- lib/sequel-query-cache/driver.rb
|
50
|
+
- lib/sequel-query-cache/driver/dalli.rb
|
51
|
+
- lib/sequel-query-cache/driver/memcache.rb
|
52
|
+
- lib/sequel-query-cache/instance_methods.rb
|
53
|
+
- lib/sequel-query-cache/serializer/json.rb
|
54
|
+
- lib/sequel-query-cache/serializer/message_pack.rb
|
55
|
+
- lib/sequel-query-cache/version.rb
|
56
|
+
- sequel-query-cache.gemspec
|
57
|
+
- spec/lib/sequel-query-cache/driver/dalli_driver_spec.rb
|
58
|
+
- spec/lib/sequel-query-cache/driver/memcache_driver_spec.rb
|
59
|
+
- spec/lib/sequel-query-cache/driver/redis_driver_spec.rb
|
60
|
+
- spec/lib/sequel-query-cache/driver_spec.rb
|
61
|
+
- spec/lib/sequel-query-cache_spec.rb
|
62
|
+
- spec/models/dalli_spec.rb
|
63
|
+
- spec/models/memcache_spec.rb
|
64
|
+
- spec/models/redis_spec.rb
|
65
|
+
- spec/shared/driver.rb
|
66
|
+
- spec/shared/query-cache.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/support/models/dalli.rb
|
69
|
+
- spec/support/models/memcache.rb
|
70
|
+
- spec/support/models/redis.rb
|
71
|
+
homepage: https://github.com/binarypaladin/sequel-query-cache
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.2.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: A plugin for Sequel that allows dataset results to be cached in Memcached
|
95
|
+
or Redis.
|
96
|
+
test_files:
|
97
|
+
- spec/lib/sequel-query-cache/driver/dalli_driver_spec.rb
|
98
|
+
- spec/lib/sequel-query-cache/driver/memcache_driver_spec.rb
|
99
|
+
- spec/lib/sequel-query-cache/driver/redis_driver_spec.rb
|
100
|
+
- spec/lib/sequel-query-cache/driver_spec.rb
|
101
|
+
- spec/lib/sequel-query-cache_spec.rb
|
102
|
+
- spec/models/dalli_spec.rb
|
103
|
+
- spec/models/memcache_spec.rb
|
104
|
+
- spec/models/redis_spec.rb
|
105
|
+
- spec/shared/driver.rb
|
106
|
+
- spec/shared/query-cache.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- spec/support/models/dalli.rb
|
109
|
+
- spec/support/models/memcache.rb
|
110
|
+
- spec/support/models/redis.rb
|
111
|
+
has_rdoc:
|