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 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 [![Build Status](https://img.shields.io/travis/foca/granola-cache.svg)](https://travis-ci.org/foca/granola-cache) [![RubyGem](https://img.shields.io/gem/v/granola-cache.svg)](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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Granola
4
+ module Cache
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
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: []