granola-cache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []