granola-cache 0.1.0
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/LICENSE +22 -0
- data/README.md +109 -0
- data/examples/redis_store.rb +43 -0
- data/lib/granola/cache.rb +167 -0
- data/lib/granola/cache/version.rb +7 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 02a1ad1117691d8f9e4839c15debccf1239fcee9
|
4
|
+
data.tar.gz: 4ff5f68499ed602dc741d59cc178e95c5d117aab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 74bac219d9f6d8350c95065e14eb0fb3b84003b9367d9d98003bf6ea290b75ff71130b2548e20430c8a952e95245a60925ce19a76873f530001122465b57261e
|
7
|
+
data.tar.gz: a1ae267d04594de510fb1c950815f8c7cebb65e5df6cb8976e3ddc6e93d76c11a794df31506c5b56944946d9ef000dcafc0945f7cab34117d000ed614d6789ff
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Nicolas Sanguinetti <hi@nicolassanguinetti.info>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
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
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Granola::Cache [](https://travis-ci.org/foca/granola-cache) [](https://rubygems.org/gems/granola-cache)
|
2
|
+
|
3
|
+
Provide caching options for the result of your [Granola][] serializers.
|
4
|
+
|
5
|
+
[Granola]: https://github.com/foca/granola
|
6
|
+
|
7
|
+
## Example
|
8
|
+
|
9
|
+
``` ruby
|
10
|
+
class PersonSerializer < Granola::Serializer
|
11
|
+
cache key: "person", expire_in: 3600
|
12
|
+
|
13
|
+
def data
|
14
|
+
{
|
15
|
+
id: object.id,
|
16
|
+
name: object.name,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_key
|
21
|
+
"%s:%s" % [object.id, object.updated_at.to_i]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
serializer = PersonSerializer.new(person)
|
26
|
+
serializer.to_json # Generates JSON and stores it in the cache.
|
27
|
+
serializer.to_json # Retreives from cache without rendering.
|
28
|
+
|
29
|
+
person.update(...) # Do something that would change the `cache_key` (e.g. update
|
30
|
+
# the object's `updated_at`.)
|
31
|
+
|
32
|
+
serializer.to_json # Generates JSON again, storing the new version in the cache.
|
33
|
+
```
|
34
|
+
|
35
|
+
**NOTE**: Changing the cache key will invalidate previous versions of the cached
|
36
|
+
object, but will _not_ delete them from the cache store.
|
37
|
+
|
38
|
+
## Install
|
39
|
+
|
40
|
+
gem install granola-cache
|
41
|
+
|
42
|
+
## Cache Stores
|
43
|
+
|
44
|
+
By default, Granola::Cache stores cached output from serializers in an in-memory
|
45
|
+
Hash. This is not meant for production use. You should provide an alternative
|
46
|
+
store for your application.
|
47
|
+
|
48
|
+
Alternative stores should implement a single method:
|
49
|
+
|
50
|
+
``` ruby
|
51
|
+
fetch(key, options = {}, &block)
|
52
|
+
```
|
53
|
+
|
54
|
+
Where:
|
55
|
+
|
56
|
+
* `key`: The cache key to fetch from cache.
|
57
|
+
* `options`: Any options the cache store can take (for example, `:expire_in`
|
58
|
+
if your store supports Time-based expiration.)
|
59
|
+
|
60
|
+
If the key isn't found in the store, the block is invoked, and the result from
|
61
|
+
this block is both returned _and_ stored in the cache for further use.
|
62
|
+
|
63
|
+
There's an example [Redis Store](./examples/redis_store.rb) included in this
|
64
|
+
repository, should you wish to inspect it.
|
65
|
+
|
66
|
+
### Rails
|
67
|
+
|
68
|
+
This is compatible with `ActiveSupport::Cache::Store`, so if you're in a Rails
|
69
|
+
application, you can just do this in `config/initializers/granola.rb`:
|
70
|
+
|
71
|
+
``` ruby
|
72
|
+
Granola::Cache.store = Rails.cache
|
73
|
+
```
|
74
|
+
|
75
|
+
## Cache Options
|
76
|
+
|
77
|
+
Pass caching options to a serializer by calling the `cache` singleton method.
|
78
|
+
|
79
|
+
Granola::Cache only recognizes these options: `:key` and `:store`. Any other
|
80
|
+
option passed will be ignored by Granola, but forwarded to the [cache
|
81
|
+
store](#cache-stores).
|
82
|
+
|
83
|
+
### `:key`
|
84
|
+
|
85
|
+
This is meant to be a prefix applied to cache keys. In the example above, any
|
86
|
+
particular serializer will be stored in the cache with the following key:
|
87
|
+
|
88
|
+
``` ruby
|
89
|
+
"#{key}/#{object.id}:#{object.updated_at.to_i}"
|
90
|
+
```
|
91
|
+
|
92
|
+
### `:store`
|
93
|
+
|
94
|
+
This allows overriding the caching store for a specific serializer.
|
95
|
+
|
96
|
+
``` ruby
|
97
|
+
class WeeklyReportSerializer < Granola::Serializer
|
98
|
+
cache store: DifferentStore.new
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
See [Cache Stores](#cache-stores) for more on configuring the global store.
|
103
|
+
|
104
|
+
## License
|
105
|
+
|
106
|
+
This project is shared under the MIT license. See the attached [LICENSE][] file
|
107
|
+
for details.
|
108
|
+
|
109
|
+
[LICENSE]: ./LICENSE
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redic"
|
4
|
+
|
5
|
+
module Granola
|
6
|
+
module Cache
|
7
|
+
# Redis-based cache store, relying on Redic for transport.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Granola::Cache.store = Granola::Cache::RedisStore.new($redic)
|
12
|
+
class RedisStore
|
13
|
+
# Public: Initialize the store.
|
14
|
+
#
|
15
|
+
# redis - A Redic instance (defaults to `Redic.new`).
|
16
|
+
def initialize(redis = Redic.new)
|
17
|
+
@redis = redis
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Fetch/Store a value from the cache.
|
21
|
+
#
|
22
|
+
# key - String key under which to store the value.
|
23
|
+
# options - Options Hash (defaults to: `{}`):
|
24
|
+
# :expire_in - Integer TTL in seconds for keys to expire.
|
25
|
+
#
|
26
|
+
# Yields if the key isn't found, and the result of the block is stored in
|
27
|
+
# the cache.
|
28
|
+
#
|
29
|
+
# Returns a String.
|
30
|
+
def fetch(key, options = {})
|
31
|
+
value = @redis.call("GET", key)
|
32
|
+
return value unless value.nil?
|
33
|
+
|
34
|
+
value = yield
|
35
|
+
|
36
|
+
expiration = ["EX", options[:expire_in]] if options[:expire_in]
|
37
|
+
@redis.call("SET", key, value, *expiration)
|
38
|
+
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "granola"
|
4
|
+
|
5
|
+
# Adds caching capabilities to Granola serializers. Use the `cache` directive on
|
6
|
+
# the serializers for this:
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class PersonSerializer < Granola::Serializer
|
11
|
+
# cache key: "person", expires_in: 3600
|
12
|
+
#
|
13
|
+
# def data
|
14
|
+
# {}
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def cache_key
|
18
|
+
# [object.id, object.updated_at.to_i].join(":")
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# serializer = PersonSerializer.new(person)
|
23
|
+
# serializer.to_json #=> generates JSON and stores in Granola::Cache.store
|
24
|
+
# serializer.to_json #=> retreives the JSON from the cache.
|
25
|
+
#
|
26
|
+
# # do something with person so that `person.updated_at` changes
|
27
|
+
#
|
28
|
+
# serializer.to_json #=> generates JSON and puts the new version in the store
|
29
|
+
#
|
30
|
+
# Cache Options
|
31
|
+
# =============
|
32
|
+
#
|
33
|
+
# Granola::Cache only recognizes two options: `:key` and `:store`. Any other
|
34
|
+
# option passed will be ignored by Granola, but forwarded to the cache store
|
35
|
+
# (see below.)
|
36
|
+
#
|
37
|
+
# `:key`
|
38
|
+
# ------
|
39
|
+
#
|
40
|
+
# This is meant to be a prefix applied to cache keys. In the example above, any
|
41
|
+
# particular serializer will be stored in the cache with the following key:
|
42
|
+
#
|
43
|
+
# "#{key}/#{object.id}:#{object.updated_at.to_i}"
|
44
|
+
#
|
45
|
+
# `:store`
|
46
|
+
# --------
|
47
|
+
#
|
48
|
+
# This allows overriding the caching store for a specific serializer.
|
49
|
+
#
|
50
|
+
# class WeeklyReportSerializer < Granola::Serializer
|
51
|
+
# cache store: DifferentStore.new
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# See Cache Stores below for more on configuring the global store.
|
55
|
+
#
|
56
|
+
# Cache Stores
|
57
|
+
# ============
|
58
|
+
#
|
59
|
+
# By default, Granola::Cache stores cached output from serializers in an
|
60
|
+
# in-memory Hash. This is not meant for production use. You should provide an
|
61
|
+
# alternative store for your application.
|
62
|
+
#
|
63
|
+
# Alternative stores should implement a single method:
|
64
|
+
#
|
65
|
+
# fetch(key, options = {}, &block)
|
66
|
+
#
|
67
|
+
# Where:
|
68
|
+
#
|
69
|
+
# key - The cache key to fetch from cache.
|
70
|
+
# options - Any options the cache store can take (for example, `:expires_in`
|
71
|
+
# if your store supports Time-based expiration.)
|
72
|
+
#
|
73
|
+
# If the key isn't found in the store, the block is invoked, and the result from
|
74
|
+
# this block is both returned _and_ stored in the cache for further use.
|
75
|
+
#
|
76
|
+
# This is compatible with `ActiveSupport::Cache::Store`, so if you're in a Rails
|
77
|
+
# application, you can just do this in `config/initializers/granola.rb`:
|
78
|
+
#
|
79
|
+
# Granola::Cache.store = Rails.cache
|
80
|
+
module Granola::Cache
|
81
|
+
class << self
|
82
|
+
# Public: Get/Set the Cache store. By default this is an instance of
|
83
|
+
# Granola::Cache::MemoryStore. See that class for the expected interface.
|
84
|
+
attr_accessor :store
|
85
|
+
end
|
86
|
+
|
87
|
+
# MemoryStore just stores things in a Hash in memory. WARNING: MemoryStore is
|
88
|
+
# *not* thread safe. It's not meant for production use. You should provide a
|
89
|
+
# proper implementation that stores the rendered output somewhere (like redis,
|
90
|
+
# memcached, etc)
|
91
|
+
class MemoryStore
|
92
|
+
def initialize # :nodoc:
|
93
|
+
@store = {}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Fetch an object from the cache, or, if none is found, yield and
|
97
|
+
# store the result of executing the block.
|
98
|
+
#
|
99
|
+
# key - A String with the cache key.
|
100
|
+
# options - A Hash of options. MemoryStore does not support any options, and
|
101
|
+
# just ignores this argument.
|
102
|
+
#
|
103
|
+
# Yields and stores the result of evaluating the block on a cache miss.
|
104
|
+
# Returns the value stored in the cache.
|
105
|
+
def fetch(key, options = {})
|
106
|
+
@store.fetch(key) { @store[key] = yield }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Extensions to Granola::Serializer to support configuring caching on a
|
111
|
+
# serializer-by-serializer basis.
|
112
|
+
module CacheableSerializer
|
113
|
+
# Public: Make this serializer cacheable. Any unrecognized options will be
|
114
|
+
# forwarded to the cache store.
|
115
|
+
#
|
116
|
+
# options - Hash of options (default: {}):
|
117
|
+
# :key - The prefix to use for keys when storing keys in the
|
118
|
+
# cache.
|
119
|
+
#
|
120
|
+
# Returns nothing.
|
121
|
+
def cache(options = {})
|
122
|
+
cache_options[:should_cache] = true
|
123
|
+
cache_options.update(options)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Access the current cache options for this serializer.
|
127
|
+
def cache_options
|
128
|
+
@cache_options ||= {}
|
129
|
+
end
|
130
|
+
|
131
|
+
# Public: Disable caching for the duration of the block.
|
132
|
+
def without_caching
|
133
|
+
should_cache = cache_options[:should_cache]
|
134
|
+
cache_options[:should_cache] = false
|
135
|
+
yield
|
136
|
+
ensure
|
137
|
+
cache_options[:should_cache] = should_cache
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Extensions to Granola::Renderer to perform the actual storing in or
|
142
|
+
# retreiving from the cache.
|
143
|
+
module CacheableRenderer
|
144
|
+
def render(serializer, *) # :nodoc:
|
145
|
+
options = case serializer
|
146
|
+
when Granola::List
|
147
|
+
serializer.item_serializer.cache_options
|
148
|
+
else
|
149
|
+
serializer.class.cache_options
|
150
|
+
end
|
151
|
+
|
152
|
+
options = options.dup
|
153
|
+
|
154
|
+
store = options.delete(:store) { Granola::Cache.store }
|
155
|
+
if options.delete(:should_cache)
|
156
|
+
key = [options.delete(:key), serializer.cache_key].compact.join("/")
|
157
|
+
store.fetch(key, options) { super }
|
158
|
+
else
|
159
|
+
super
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
Granola::Serializer.extend(Granola::Cache::CacheableSerializer)
|
166
|
+
Granola::Renderer.prepend(Granola::Cache::CacheableRenderer)
|
167
|
+
Granola::Cache.store = Granola::Cache::MemoryStore.new
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: granola-cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicolas Sanguinetti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: granola
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.13'
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.13'
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: cutest
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.2'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.2'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: redic
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.5'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.5'
|
61
|
+
description: |
|
62
|
+
Granola::Cache provides an interface to wrap your serialization in a cache
|
63
|
+
so that you don't needlessly generate the same bit of JSON over and over
|
64
|
+
again.
|
65
|
+
email:
|
66
|
+
- contacto@nicolassanguinetti.info
|
67
|
+
executables: []
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
- examples/redis_store.rb
|
74
|
+
- lib/granola/cache.rb
|
75
|
+
- lib/granola/cache/version.rb
|
76
|
+
homepage: http://github.com/foca/granola
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.5.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Cache the output of your Granola serializers.
|
100
|
+
test_files: []
|