iknow_cache 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14a5fbfe4eb06e835259e354c8a8fdfb9bc82c2d34bfe6ea62fc8269144ca8a5
4
+ data.tar.gz: 22da19d2f809d5d092298d47d1b703a146ff6d143d7ac83c0191a8fae98eca36
5
+ SHA512:
6
+ metadata.gz: 80d4ff3de7d25e388217e20922971d4378970591bc6a8c57672e1c4745e1451e57a2105f16a468c7845ce4103dae8b62a5f8abe5cf6ab66713a4fa9ec23aac8f
7
+ data.tar.gz: c23861a1464d8e2ea0c6d711eac3b8517253f745cda66bb9c612b4c67be11f669fff637c0e5eeb49c967600d38b050692895104c7d84500d5d8aa78a4f3c9cc0
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Chris Andreae
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.
@@ -0,0 +1,25 @@
1
+ [![Build Status](https://travis-ci.org/iknow/iknow_cache.svg?branch=master)](https://travis-ci.org/iknow/iknow_cache)
2
+
3
+ # IknowCache
4
+
5
+ iKnow's versioned nested cache backed by Rails cache
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'iknow_cache'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install iknow_cache
22
+ ```
23
+
24
+ ## License
25
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -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 = 'IknowCache'
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
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IknowCache
4
+ def self.register_group(name, key_name, default_options: nil, static_version: 1)
5
+ group = CacheGroup.new(nil, name, key_name, default_options, static_version)
6
+ yield group if block_given?
7
+ group
8
+ end
9
+
10
+ class CacheGroup
11
+ attr_reader :parent, :default_options, :name, :key_name, :key
12
+
13
+ def initialize(parent, name, key_name, default_options, static_version)
14
+ @parent = parent
15
+ @name = name
16
+ @key_name = key_name
17
+ @key = Struct.new(*parent.try { |p| p.key.members }, key_name)
18
+ @default_options = IknowCache.merge_options(parent&.default_options, default_options).try { |x| x.dup.freeze }
19
+ @static_version = static_version
20
+
21
+ @caches = []
22
+ @children = []
23
+ end
24
+
25
+ def register_child_group(name, key_name, default_options: nil, static_version: 1)
26
+ group = CacheGroup.new(self, name, key_name, default_options, static_version)
27
+ @children << group
28
+ yield group if block_given?
29
+ group
30
+ end
31
+
32
+ def register_cache(name, cache_options: nil)
33
+ c = Cache.new(self, name, cache_options)
34
+ @caches << c
35
+ c
36
+ end
37
+
38
+ # Clear this key in all Caches in this CacheGroup.
39
+ # It is an error to do this if this CacheGroup has an statically versioned child, as that child cannot be invalidated.
40
+ def delete_all(key, parent_path: nil)
41
+ @caches.each do |cache|
42
+ cache.delete(key, parent_path: parent_path)
43
+ end
44
+
45
+ @children.each do |child_group|
46
+ child_group.invalidate_cache_group(key)
47
+ end
48
+ end
49
+
50
+ # Clear all keys in this cache group (for the given parent),
51
+ # invalidating all caches in it and its children
52
+ def invalidate_cache_group(parent_key = nil)
53
+ parent_path = self.parent_path(parent_key)
54
+ Rails.cache.increment(version_path_string(parent_path))
55
+ end
56
+
57
+ # Fetch the path for this cache. We allow the parent_path to be precomputed
58
+ # to save hitting the cache multiple times for its version.
59
+ def path(key, parent_path = nil)
60
+ if key.nil? || key[self.key_name].nil?
61
+ raise ArgumentError.new("Missing required key '#{self.key_name}' for cache '#{self.name}'")
62
+ end
63
+
64
+ key_value = key[self.key_name]
65
+ parent_path ||= self.parent_path(key)
66
+ version = self.version(parent_path)
67
+ path_string(parent_path, version, key_value)
68
+ end
69
+
70
+ ROOT_PATH = 'IknowCache'
71
+
72
+ def parent_path(parent_key = nil)
73
+ if parent.nil?
74
+ ROOT_PATH
75
+ else
76
+ parent.path(parent_key)
77
+ end
78
+ end
79
+
80
+ def version(parent_path)
81
+ Rails.cache.fetch(version_path_string(parent_path), raw: true) { 1 }
82
+ end
83
+
84
+ # compute multiple paths at once: returns { key => path }
85
+ def path_multi(keys)
86
+ # compute parent path for each key
87
+ parent_paths = self.parent_path_multi(keys)
88
+
89
+ # and versions for each parent path
90
+ versions = self.version_multi(parent_paths.values.uniq)
91
+
92
+ # update parent_paths with our paths
93
+ keys.each do |key|
94
+ parent_path = parent_paths[key]
95
+ version = versions[parent_path]
96
+ key_value = key[self.key_name]
97
+
98
+ unless key_value
99
+ raise ArgumentError.new("Required cache key missing: #{self.key_name}")
100
+ end
101
+
102
+ parent_paths[key] = path_string(parent_path, version, key_value)
103
+ end
104
+
105
+ parent_paths
106
+ end
107
+
108
+ # look up multiple parent paths at once, returns { key => parent_path }
109
+ def parent_path_multi(parent_keys = nil)
110
+ if parent.nil?
111
+ parent_keys.each_with_object({}) { |k, h| h[k] = ROOT_PATH }
112
+ else
113
+ parent.path_multi(parent_keys)
114
+ end
115
+ end
116
+
117
+ # Look up multiple versions at once, returns { parent_path => version }
118
+ def version_multi(parent_paths)
119
+ # compute version paths
120
+ version_by_pp = parent_paths.each_with_object({}) { |pp, h| h[pp] = version_path_string(pp) }
121
+ version_paths = version_by_pp.values
122
+
123
+ # look up versions in cache
124
+ versions = Rails.cache.read_multi(*version_paths, raw: true)
125
+
126
+ version_paths.each do |vp|
127
+ next if versions.has_key?(vp)
128
+
129
+ versions[vp] = Rails.cache.fetch(vp, raw: true) { 1 }
130
+ end
131
+
132
+ # swap in the versions
133
+ parent_paths.each do |pp|
134
+ vp = version_by_pp[pp]
135
+ version = versions[vp]
136
+ version_by_pp[pp] = version
137
+ end
138
+
139
+ version_by_pp
140
+ end
141
+
142
+ private
143
+
144
+ def path_string(parent_path, version, value)
145
+ "#{parent_path}/#{name}/#{@static_version}/#{version}/#{value}"
146
+ end
147
+
148
+ def version_path_string(parent_path)
149
+ "#{parent_path}/#{name}/_version"
150
+ end
151
+ end
152
+
153
+ class Cache
154
+ DEBUG = false
155
+
156
+ attr_reader :name, :cache_options, :cache_group
157
+
158
+ def initialize(cache_group, name, cache_options)
159
+ @cache_group = cache_group
160
+ @name = name
161
+ @cache_options = IknowCache.merge_options(cache_group.default_options, cache_options).try { |x| x.dup.freeze }
162
+ end
163
+
164
+ def fetch(key, parent_path: nil, **options, &block)
165
+ p = path(key, parent_path)
166
+ Rails.logger.debug("Cache Fetch: #{p}") if DEBUG
167
+ v = Rails.cache.fetch(p, IknowCache.merge_options(cache_options, options), &block)
168
+ Rails.logger.debug("=> #{v.inspect}") if DEBUG
169
+ v
170
+ end
171
+
172
+ def read(key, parent_path: nil, **options)
173
+ p = path(key, parent_path)
174
+ Rails.logger.debug("Cache Read: #{p}") if DEBUG
175
+ v = Rails.cache.read(p, IknowCache.merge_options(cache_options, options))
176
+ Rails.logger.debug("=> #{v.inspect}") if DEBUG
177
+ v
178
+ end
179
+
180
+ def write(key, value, parent_path: nil, **options)
181
+ p = path(key, parent_path)
182
+ if DEBUG
183
+ Rails.logger.debug("Cache Store: #{p} (#{IknowCache.merge_options(cache_options, options).inspect})")
184
+ Rails.logger.debug("<= #{value.inspect}")
185
+ end
186
+ Rails.cache.write(p, value, IknowCache.merge_options(cache_options, options))
187
+ end
188
+
189
+ def delete(key, parent_path: nil, **options)
190
+ p = path(key, parent_path)
191
+ Rails.logger.debug("Cache Delete: #{p}") if DEBUG
192
+ Rails.cache.delete(p, IknowCache.merge_options(cache_options, options))
193
+ end
194
+
195
+ def read_multi(keys)
196
+ return {} if keys.blank?
197
+
198
+ key_paths = path_multi(keys)
199
+ path_keys = key_paths.invert
200
+
201
+ Rails.logger.debug("Cache Multi-Read: #{key_paths.values.inspect}") if DEBUG
202
+ raw = Rails.cache.read_multi(*key_paths.values)
203
+ vs = raw.each_with_object({}) do |(path, value), h|
204
+ h[path_keys[path]] = value
205
+ end
206
+ Rails.logger.debug("=> #{vs.inspect}") if DEBUG
207
+ vs
208
+ end
209
+
210
+ def write_multi(entries, options = nil)
211
+ return {} if entries.blank?
212
+
213
+ key_paths = path_multi(entries.keys)
214
+ options = IknowCache.merge_options(cache_options, options)
215
+
216
+ entries.each do |key, value|
217
+ Rails.logger.debug("Cache Multi-Write: #{key_paths[key]}") if DEBUG
218
+ Rails.cache.write(key_paths[key], value, options)
219
+ end
220
+ end
221
+
222
+ delegate :key, to: :cache_group
223
+
224
+ private
225
+
226
+ def path(key, parent_path = nil)
227
+ group_path = @cache_group.path(key, parent_path)
228
+ path_string(group_path)
229
+ end
230
+
231
+ def path_multi(keys)
232
+ @cache_group.path_multi(keys).each_with_object({}) do |(key, group_path), h|
233
+ h[key] = path_string(group_path)
234
+ end
235
+ end
236
+
237
+ def path_string(group_path)
238
+ "#{group_path}/#{self.name}"
239
+ end
240
+ end
241
+
242
+ def self.merge_options(parent_options, options)
243
+ if parent_options.blank?
244
+ options
245
+ elsif options.blank?
246
+ parent_options
247
+ else
248
+ parent_options.merge(options)
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,3 @@
1
+ class IknowCache
2
+ VERSION = '1.1.1'
3
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iknow_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Andreae
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-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: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: appraisal
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:
56
+ email:
57
+ - chris@bibo.com.ph
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - lib/iknow_cache.rb
66
+ - lib/iknow_cache/version.rb
67
+ homepage: https://github.com/iknow/iknow_cache
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 3.0.3
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: iKnow's versioned nested cache
90
+ test_files: []