cachable 0.5.3
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +190 -0
- data/Rakefile +34 -0
- data/lib/cachable.rb +165 -0
- data/lib/cachable/configuration.rb +31 -0
- data/lib/cachable/version.rb +3 -0
- data/lib/tasks/cachable_tasks.rake +4 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 70e62e1e63223309b99070e0b8d17707a93e2a1e
|
4
|
+
data.tar.gz: a43bad698ed24dfe8a86b26d5ba4120cdd8f93ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 299c3e2bd420d884196ed55883a9e2d6b4c745df9fdf90d4bfca1c24ad14b728920ae2c427f49f861087fc9bee421d3579eda032d0ca082b1840a0c7d34c07b7
|
7
|
+
data.tar.gz: 789bf412bf8b7d4a4a0cddd74fc235febebd29045b05f033a846c854aa5e465f5a30760ea53eada73d6ec116e3193fbb612cf2f25e66e534a8d52b1b3d4652eb
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Kai Marshland
|
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.md
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
# Cachable
|
2
|
+
Caching is often a bother. This gem allows you to simply wrap your code in a block and have it be cached with redis.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'cachable'
|
9
|
+
```
|
10
|
+
|
11
|
+
You must also add redis to your app. On heroku, you can add [Heroku Redis](https://elements.heroku.com/addons/heroku-redis).
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
At its hearty, this gem is the `unless_cached` method.
|
15
|
+
Although no options are required, it accepts the following:
|
16
|
+
- key. If present, will be added to the base key to generate the full key. Defaults to the name of the caller.
|
17
|
+
- json. If true, will serialize and deserialize the result as json
|
18
|
+
- expiration Time for which to cache the result. Defaults to 1 day
|
19
|
+
- json_options. Options that get passed into json serialization and deserialization
|
20
|
+
- skip_cache. If true, will not populate the cache -- this can be used if you populate the cache elsewhere, but still want to check if there's something there.
|
21
|
+
|
22
|
+
### Examples
|
23
|
+
In your model:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
include Cachable
|
27
|
+
|
28
|
+
def your_method
|
29
|
+
unless_cached do
|
30
|
+
# ... output of this will be cached under the key model_name_id_updated_at_your_method
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Of course, `unless_cached` accepts a variety of options:
|
36
|
+
```ruby
|
37
|
+
# opts[:key]. If present, will be added to the base key to generate the full key. Defaults to the name of the caller.
|
38
|
+
unless_cached(key: 'some_key') do
|
39
|
+
# will be stored under the key model_name_id_some_key
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# opts[:json]. If true, will serialize and deserialize the result as json
|
44
|
+
unless_cached(json: true) do
|
45
|
+
{
|
46
|
+
json: 'object'
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# opts[:expiration]. Time for which to cache the result. Defaults to 1 day
|
51
|
+
unless_cached(expiration: 30.minutes) do
|
52
|
+
# result will expire in half an hour
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# opts[:json_options]. Options that get passed into json serialization and deserialization
|
57
|
+
unless_cached(json: true, json_options: {allow_nan: true}) do
|
58
|
+
{
|
59
|
+
value: Float::NAN
|
60
|
+
}
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Other features
|
65
|
+
|
66
|
+
### Configuring the redis connection
|
67
|
+
It defaults to checking the `REDIS_URL` and the `HEROKU_REDIS_URL` environment variables to make a connection to redis.
|
68
|
+
However, you may wish to change the way you connect to redis. Create an initializer in `config/initializers/cachable.rb`.
|
69
|
+
```ruby
|
70
|
+
Cachable::configure do |config|
|
71
|
+
|
72
|
+
# set a lambda to get a redis connection
|
73
|
+
config.redis_connection = -> {
|
74
|
+
Redis.new
|
75
|
+
}
|
76
|
+
|
77
|
+
# set the redis instance directly
|
78
|
+
config.redis_instance = Redis.new
|
79
|
+
|
80
|
+
# or set the redis url
|
81
|
+
config.redis_url = 'YOUR REDIS URL'
|
82
|
+
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### Deleting from the cache
|
87
|
+
Sometimes you want to delete something from the cache.
|
88
|
+
This can be done using the `delete_from_cache` method, which takes in one or more keys.
|
89
|
+
These keys are either the
|
90
|
+
```ruby
|
91
|
+
delete_from_cache(:key1, :key2)
|
92
|
+
```
|
93
|
+
|
94
|
+
A special use case is after the changes have been committed.
|
95
|
+
For this, you can use the special `purge_cache` callback.
|
96
|
+
You must define the `tracked_cache_keys` method for this to work properly.
|
97
|
+
The following example will clear the cache for `cached_a` and `cached_b`, but not `cached_c`.
|
98
|
+
```ruby
|
99
|
+
# in your model
|
100
|
+
after_commit :purge_cache
|
101
|
+
|
102
|
+
def tracked_cache_keys
|
103
|
+
[:cached_a, :cached_b]
|
104
|
+
end
|
105
|
+
|
106
|
+
def cached_a
|
107
|
+
unless_cached do
|
108
|
+
# ...
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def cached_b
|
113
|
+
unless_cached do
|
114
|
+
# ...
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def cached_c
|
119
|
+
unless_cached do
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
If you want to use the `delete_from_cache` method directly in an `after_commit` callback, you must specify that in the options.
|
126
|
+
```ruby
|
127
|
+
delete_from_cache(:key1, :key2, after_commit: true)
|
128
|
+
```
|
129
|
+
|
130
|
+
### Batch requests to the cache
|
131
|
+
Oftentimes, you want the output of a given method on an ActiveRecordCollection.
|
132
|
+
You could loop through each record in the collection, but if you expect the method to be cached,
|
133
|
+
it's much more efficient this gem's method, and avoid the memory overhead and extra database calls.
|
134
|
+
|
135
|
+
Imagine you have a model that looks as follows:
|
136
|
+
```ruby
|
137
|
+
class Book < ActiveRecord::Base
|
138
|
+
include Cachable
|
139
|
+
|
140
|
+
def summary
|
141
|
+
unless_cached do
|
142
|
+
'Some summary'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
To get the summaries of all fantasy books, you could run `Book.where(genre: 'fantasy').unless_cached(:summary)`.
|
148
|
+
|
149
|
+
Note that this will remove any ordering on the collection unless you set the slurp option to true or include `EachInOrder` in your model (this gem has not yet been released -- create an issue if you need me to release it). A full list of options is as follows:
|
150
|
+
- slurp. If true will not pull the records in in batches, which increases memory overhead but preserves order.
|
151
|
+
- force_cache. If true will add its own cache, regardless of what the underlying function does.
|
152
|
+
- cache_batches. If true will add another layer of caching outside each individual model. This caches a the result of up to 50 records at a time, which can drastically speed up the cache but will also increase the cache size.
|
153
|
+
- skip_result. If true will not return the result, but it will still prepopulate the cache, which can avoid memory overhead.
|
154
|
+
- clear_previous_batch. If true, and was also true previous times, it will remove the cached batches for the previous batch. This can be very useful when you are frequently adding more records and want to keep the redis memory usage down. Note that this was designed to have minimal overhead, and so was based on the total number of records, which may cause a small number of stale keys to stay in memory.
|
155
|
+
- You can also pass in all normal unless_cached options, such as expiration, json, and json_options.
|
156
|
+
|
157
|
+
|
158
|
+
### Adding an additional redis key
|
159
|
+
Sometimes your cache depends on more than just the model.
|
160
|
+
For example, imagine you have a model book that `belongs_to` an author.
|
161
|
+
When the author is updated, you want the cache to become invalidated.
|
162
|
+
In your book model, you might have something like this:
|
163
|
+
```ruby
|
164
|
+
def added_redis_key
|
165
|
+
self.author.updated_at.to_i
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.added_redis_key
|
169
|
+
first = all.first
|
170
|
+
return '' if first.blank?
|
171
|
+
|
172
|
+
first.author.updated_at.to_i
|
173
|
+
end
|
174
|
+
|
175
|
+
```
|
176
|
+
|
177
|
+
### Using the cache outside of a specific instance
|
178
|
+
Sometimes you need to cache something in a static (class) method. It accepts the following options:
|
179
|
+
- json. If true, will serialize and deserialize the result as json
|
180
|
+
- expiration Time for which to cache the result. Defaults to 1 day
|
181
|
+
- json_options. Options that get passed into json serialization and deserialization
|
182
|
+
- skip_cache. If true, will not populate the cache -- this can be used if you populate the cache elsewhere, but still want to check if there's something there.
|
183
|
+
```ruby
|
184
|
+
self.class.unless_cached_base("#{self.class.to_s.downcase}_key", json: true, expiration: 1.day) do
|
185
|
+
# this output will be cached under the key your_model_name_key
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
## License
|
190
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Cachable'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
data/lib/cachable.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'cachable/configuration'
|
2
|
+
|
3
|
+
module Cachable
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
|
9
|
+
after_commit :purge_cache #try to purge the cache after anything changes
|
10
|
+
|
11
|
+
#Generates the basic redis key from id, updated_at, and whatever else we want
|
12
|
+
def base_redis_key(opts={})
|
13
|
+
added = ''
|
14
|
+
added = "_#{self.added_redis_key}" if self.respond_to? :added_redis_key
|
15
|
+
|
16
|
+
on_previous_changes = opts[:after_commit] && self.previous_changes['updated_at']
|
17
|
+
updated_at = on_previous_changes ? self.previous_changes['updated_at'].first : self.updated_at
|
18
|
+
|
19
|
+
"#{self.class.to_s.downcase}_#{self.id}_#{updated_at.to_i}#{added}"
|
20
|
+
end
|
21
|
+
|
22
|
+
#Clears the given keys from redis
|
23
|
+
def delete_from_cache(*keys, **opts)
|
24
|
+
base_key = self.base_redis_key opts
|
25
|
+
keys.each do |key|
|
26
|
+
Cachable::redis.del "#{base_key}_#{key}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Purges the cache for this record, if the client has defined the purge_cache method
|
31
|
+
def purge_cache
|
32
|
+
return unless self.respond_to? :tracked_cache_keys
|
33
|
+
|
34
|
+
self.delete_from_cache(*self.tracked_cache_keys, after_commit: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
# If the is present in the cache, returns that; otherwise generates and caches the result
|
38
|
+
# opts[:key]. If present, will be added to the base key to generate the full key. Defaults to the name of the caller.
|
39
|
+
# opts[:json]. If true, will serialize and deserialize the result as json
|
40
|
+
# opts[:expiration]. Time for which to cache the result. Defaults to 1 day
|
41
|
+
# opts[:json_options]. Options that get passed into json serialization and deserialization
|
42
|
+
def unless_cached(opts={})
|
43
|
+
partial_key = opts[:key].present? ? opts[:key] : caller.first.match(/`[^']*/).to_s[1..-1]
|
44
|
+
key = "#{self.base_redis_key}_#{partial_key}"
|
45
|
+
|
46
|
+
self.class.unless_cached_base(key, opts) do
|
47
|
+
yield
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Calls unless cached on all records passed
|
52
|
+
# action
|
53
|
+
# opts[:slurp]. If true will not pull the records in in batches, which increases memory overhead but preserves order.
|
54
|
+
# opts[:force_cache]. If true will add its own cache, regardless of what the underlying function does
|
55
|
+
# opts[:cache_batches]. If true will add another layer of caching outside
|
56
|
+
# opts[:skip_result]. If true will not generate (useful for prepopulating the cache with lower overhead)
|
57
|
+
# opts[clear_previous_batch] If true, and was also true previous times, it will remove the cached batches for the previous batch. This can be very useful when you are frequently adding more records and want to keep the redis memory usage down.
|
58
|
+
# Can also pass in all normal unless_cached options
|
59
|
+
def self.unless_cached(action, opts={})
|
60
|
+
result = []
|
61
|
+
|
62
|
+
iterator = all.find_in_batches
|
63
|
+
iterator = all.each_in_order_in_batches if self.respond_to? :each_in_order_in_batches
|
64
|
+
iterator = all.each if opts[:slurp]
|
65
|
+
|
66
|
+
partial_key = opts[:key].present? ? opts[:key] : action
|
67
|
+
added_key = ''
|
68
|
+
added_key = "_#{self.added_redis_key}" if self.respond_to? :added_redis_key
|
69
|
+
|
70
|
+
batch_key_list = []
|
71
|
+
record_count = 0
|
72
|
+
|
73
|
+
opts[:skip_cache] = !opts[:force_cache]
|
74
|
+
|
75
|
+
iterator.each do |batch|
|
76
|
+
factors = batch.pluck(:id, :updated_at)
|
77
|
+
record_count += factors.length if opts[:clear_previous_batch]
|
78
|
+
|
79
|
+
if opts[:cache_batches]
|
80
|
+
batch_key = "#{self.to_s.downcase}_#{factors.flatten.join(',')}#{added_key}_#{partial_key}"
|
81
|
+
batch_key_list << batch_key if opts[:clear_previous_batch]
|
82
|
+
|
83
|
+
existing_result = Cachable::redis.get batch_key
|
84
|
+
if existing_result.present?
|
85
|
+
result.concat JSON(existing_result)
|
86
|
+
next
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
batch_result = factors.map do |id, updated_at|
|
91
|
+
key = "#{self.to_s.downcase}_#{id}_#{updated_at.to_i}#{added_key}_#{partial_key}"
|
92
|
+
|
93
|
+
self.unless_cached_base(key, opts) do
|
94
|
+
record = self.unscope(:order, :where, :offset).find id
|
95
|
+
|
96
|
+
block_given? ? (yield record) : record.send(action) if record.present?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
result.concat batch_result unless opts[:skip_result]
|
101
|
+
|
102
|
+
if opts[:cache_batches]
|
103
|
+
Cachable::redis.set batch_key, JSON(batch_result)
|
104
|
+
expiration = opts[:expiration]
|
105
|
+
expiration = 15.minutes unless expiration.present?
|
106
|
+
Cachable::redis.expire batch_key, expiration unless expiration === false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if opts[:clear_previous_batch]
|
111
|
+
|
112
|
+
# store the keys for the current batch list
|
113
|
+
expiration = opts[:expiration]
|
114
|
+
expiration = 15.minutes unless expiration.present?
|
115
|
+
|
116
|
+
gen_key -> n {
|
117
|
+
"#{self.to_s.downcase}_keys_#{n}_#{added_key}_#{partial_key}"
|
118
|
+
}
|
119
|
+
batch_key = gen_key[record_count]
|
120
|
+
|
121
|
+
Cachable::redis.set(batch_key, JSON(batch_key_list))
|
122
|
+
Cachable::redis.expire(batch_key, expiration)
|
123
|
+
|
124
|
+
# delete the keys from the previous batch list
|
125
|
+
Cachable::redis.del(gen_key[record_count - 1])
|
126
|
+
Cachable::redis.del(gen_key[record_count + 1])
|
127
|
+
end
|
128
|
+
|
129
|
+
result
|
130
|
+
end
|
131
|
+
|
132
|
+
# Core implementation of unless cached.
|
133
|
+
# As well as options from above:
|
134
|
+
# opts[:skip_cache]. If true, will not populate the cache
|
135
|
+
def self.unless_cached_base(key, opts={})
|
136
|
+
opts[:json] = true if opts[:json].nil?
|
137
|
+
|
138
|
+
cached = Cachable::redis.get(key)
|
139
|
+
if cached.present?
|
140
|
+
cached = JSON.parse(cached, opts[:json_options]) if opts[:json]
|
141
|
+
|
142
|
+
return cached
|
143
|
+
end
|
144
|
+
|
145
|
+
result = yield
|
146
|
+
|
147
|
+
unless opts[:skip_cache]
|
148
|
+
if opts[:json]
|
149
|
+
Cachable::redis.set(key, JSON.generate(result, opts[:json_options]))
|
150
|
+
else
|
151
|
+
Cachable::redis.set(key, result)
|
152
|
+
end
|
153
|
+
|
154
|
+
expiration = opts[:expiration]
|
155
|
+
expiration = 1.day unless expiration.present?
|
156
|
+
Cachable::redis.expire(key, expiration) unless expiration === false
|
157
|
+
end
|
158
|
+
|
159
|
+
result
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Cachable
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :redis_connection, :redis_instance, :redis_url
|
5
|
+
|
6
|
+
def redis
|
7
|
+
return @redis_connection.call if @redis_connection.present?
|
8
|
+
return @redis_instance if @redis_instance.present?
|
9
|
+
|
10
|
+
|
11
|
+
@redis_url = ENV['REDIS_URL'] || ENV['HEROKU_REDIS_URL']
|
12
|
+
raise 'No redis url provided' if @redis_url.blank?
|
13
|
+
|
14
|
+
uri = URI.parse(@redis_url)
|
15
|
+
@redis_instance = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configuration
|
20
|
+
@config ||= Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure
|
24
|
+
yield self.configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.redis
|
28
|
+
self.configuration.redis
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cachable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kai Marshland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Easily add caching to models
|
56
|
+
email:
|
57
|
+
- kaimarshland@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- lib/cachable.rb
|
66
|
+
- lib/cachable/configuration.rb
|
67
|
+
- lib/cachable/version.rb
|
68
|
+
- lib/tasks/cachable_tasks.rake
|
69
|
+
homepage: https://github.com/KMarshland/cachable
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.5.1
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Easily add caching to models
|
93
|
+
test_files: []
|