cache_crispies 0.3.1 → 1.0.1
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 +4 -4
- data/lib/cache_crispies.rb +21 -8
- data/lib/cache_crispies/attribute.rb +31 -7
- data/lib/cache_crispies/base.rb +100 -31
- data/lib/cache_crispies/collection.rb +2 -4
- data/lib/cache_crispies/configuration.rb +28 -0
- data/lib/cache_crispies/controller.rb +7 -5
- data/lib/cache_crispies/plan.rb +2 -1
- data/lib/cache_crispies/version.rb +1 -1
- data/spec/{attribute_spec.rb → cache_crispies/attribute_spec.rb} +29 -0
- data/spec/cache_crispies/base_spec.rb +289 -0
- data/spec/{collection_spec.rb → cache_crispies/collection_spec.rb} +31 -21
- data/spec/{condition_spec.rb → cache_crispies/condition_spec.rb} +0 -0
- data/spec/cache_crispies/configuration_spec.rb +42 -0
- data/spec/cache_crispies/controller_spec.rb +90 -0
- data/spec/{hash_builder_spec.rb → cache_crispies/hash_builder_spec.rb} +0 -0
- data/spec/{memoizer_spec.rb → cache_crispies/memoizer_spec.rb} +0 -0
- data/spec/{plan_spec.rb → cache_crispies/plan_spec.rb} +12 -7
- data/spec/cache_crispies_spec.rb +27 -0
- metadata +49 -24
- data/spec/base_spec.rb +0 -186
- data/spec/controller_spec.rb +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7be596f4546279c27b355a83a6c87b5d986cbcc65779def4310df106c3017557
|
4
|
+
data.tar.gz: e7b5ceb4ac6c68d7149bb45246bdba99864c5fbd289277d054b22c5c622f748d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac4fe247753da8c9afa3e2d5a27d8f673c780b6bbb0d71a8777e736e9b9761be4791c33e5370a3f6a1ade8df2ff65befd648c9004908f1d4ebaafb3ce3678aa1
|
7
|
+
data.tar.gz: 823e1a06a9a007e74cb148751f765cf506e96849b5947e5132b2fffadfbaaa447a3c379cbd805d72fb03ceffd2bb8daf12a8649e282020d1d91b98656af97e82
|
data/lib/cache_crispies.rb
CHANGED
@@ -15,12 +15,25 @@ module CacheCrispies
|
|
15
15
|
require 'cache_crispies/version'
|
16
16
|
|
17
17
|
# Use autoload for better Rails development
|
18
|
-
autoload :Attribute,
|
19
|
-
autoload :Base,
|
20
|
-
autoload :Collection,
|
21
|
-
autoload :Condition,
|
22
|
-
autoload :
|
23
|
-
autoload :
|
24
|
-
autoload :
|
25
|
-
autoload :
|
18
|
+
autoload :Attribute, 'cache_crispies/attribute'
|
19
|
+
autoload :Base, 'cache_crispies/base'
|
20
|
+
autoload :Collection, 'cache_crispies/collection'
|
21
|
+
autoload :Condition, 'cache_crispies/condition'
|
22
|
+
autoload :Configuration, 'cache_crispies/configuration'
|
23
|
+
autoload :HashBuilder, 'cache_crispies/hash_builder'
|
24
|
+
autoload :Memoizer, 'cache_crispies/memoizer'
|
25
|
+
autoload :Controller, 'cache_crispies/controller'
|
26
|
+
autoload :Plan, 'cache_crispies/plan'
|
27
|
+
|
28
|
+
def self.configure
|
29
|
+
yield config
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.config
|
33
|
+
@config ||= Configuration.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.cache
|
37
|
+
config.cache_store
|
38
|
+
end
|
26
39
|
end
|
@@ -22,44 +22,68 @@ module CacheCrispies
|
|
22
22
|
# argument's value
|
23
23
|
def initialize(
|
24
24
|
key,
|
25
|
-
from: nil, with: nil, to: nil, nesting: [], conditions: []
|
25
|
+
from: nil, with: nil, through: nil, to: nil, nesting: [], conditions: [],
|
26
|
+
&block
|
26
27
|
)
|
27
28
|
@key = key
|
28
29
|
@method_name = from || key || :itself
|
29
30
|
@serializer = with
|
31
|
+
@through = through
|
30
32
|
@coerce_to = to
|
31
33
|
@nesting = Array(nesting)
|
32
34
|
@conditions = Array(conditions)
|
35
|
+
@block = block
|
33
36
|
end
|
34
37
|
|
35
38
|
attr_reader(
|
36
39
|
:method_name,
|
37
40
|
:key,
|
38
41
|
:serializer,
|
42
|
+
:through,
|
39
43
|
:coerce_to,
|
40
44
|
:nesting,
|
41
|
-
:conditions
|
45
|
+
:conditions,
|
46
|
+
:block
|
42
47
|
)
|
43
48
|
|
44
|
-
# Gets the value of the attribute for the given
|
49
|
+
# Gets the value of the attribute for the given target object and options
|
45
50
|
#
|
46
|
-
# @param
|
51
|
+
# @param target [Object] typically ActiveRecord::Base, but could be anything
|
47
52
|
# @param options [Hash] any optional values from the serializer instance
|
48
53
|
# @return the value for the attribute for the given model and options
|
49
54
|
# @raise [InvalidCoercionType] when an invalid argument is passed in the
|
50
55
|
# to: argument
|
51
|
-
def value_for(
|
52
|
-
value =
|
56
|
+
def value_for(target, options)
|
57
|
+
value =
|
58
|
+
if block?
|
59
|
+
block.call(target, options)
|
60
|
+
elsif through?
|
61
|
+
target.public_send(through)&.public_send(method_name)
|
62
|
+
else
|
63
|
+
target.public_send(method_name)
|
64
|
+
end
|
53
65
|
|
54
66
|
serializer ? serialize(value, options) : coerce(value)
|
55
67
|
end
|
56
68
|
|
69
|
+
|
70
|
+
|
57
71
|
private
|
58
72
|
|
73
|
+
def through?
|
74
|
+
!through.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def block?
|
78
|
+
!block.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Here we'll render the attribute with a given serializer and attempt to
|
82
|
+
# cache the results for better cache reusability
|
59
83
|
def serialize(value, options)
|
60
84
|
plan = CacheCrispies::Plan.new(serializer, value, options)
|
61
85
|
|
62
|
-
if
|
86
|
+
if plan.collection?
|
63
87
|
plan.cache { Collection.new(value, serializer, options).as_json }
|
64
88
|
else
|
65
89
|
plan.cache { serializer.new(value, options).as_json }
|
data/lib/cache_crispies/base.rb
CHANGED
@@ -43,55 +43,118 @@ module CacheCrispies
|
|
43
43
|
HashBuilder.new(self).call
|
44
44
|
end
|
45
45
|
|
46
|
-
#
|
47
|
-
# It
|
46
|
+
# Get or set whether or not this serializer class should allow caching of
|
47
|
+
# results. It returns false by default, but can be overridden in child
|
48
|
+
# classes. Calling the method with an argument will set the value, calling
|
49
|
+
# it without any arguments will get the value.
|
48
50
|
#
|
51
|
+
# @param value [Boolean] true to enable caching, false to disable
|
49
52
|
# @return [Boolean]
|
50
|
-
def self.do_caching
|
51
|
-
false
|
53
|
+
def self.do_caching(value = nil)
|
54
|
+
@do_caching ||= false
|
55
|
+
|
56
|
+
# method called with no args so act as a getter
|
57
|
+
return @do_caching if value.nil?
|
58
|
+
|
59
|
+
# method called with args so act as a setter
|
60
|
+
@do_caching = !!value
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
alias do_caching? do_caching
|
52
65
|
end
|
53
66
|
|
54
|
-
#
|
55
|
-
# default it's the name of the class without the
|
56
|
-
# can be overridden in a subclass to be anything.
|
67
|
+
# Get or set a JSON key to use as a root key on a non-collection
|
68
|
+
# serializable. By default it's the name of the class without the
|
69
|
+
# "Serializer" part. But it can be overridden in a subclass to be anything.
|
70
|
+
# Calling the method with a key will set the key, calling it without any
|
71
|
+
# arguments will get the key.
|
57
72
|
#
|
58
|
-
# @
|
59
|
-
|
60
|
-
|
73
|
+
# @param key [Symbol, nil] a symbol to be used as a key for a JSON-ready
|
74
|
+
# Hash, or nil for no key
|
75
|
+
# @return [Symbol, nil] a symbol to be used as a key for a JSON-ready Hash,
|
76
|
+
# or nil for no key
|
77
|
+
def self.key(*key)
|
78
|
+
@default_key ||= to_s.demodulize.chomp('Serializer').underscore.to_sym
|
79
|
+
|
80
|
+
# method called with no args so act as a getter
|
81
|
+
return defined?(@key) ? @key : @default_key if key.empty?
|
82
|
+
|
83
|
+
# method called with args so act as a setter
|
84
|
+
@key = key.first&.to_sym
|
61
85
|
end
|
62
86
|
|
63
|
-
#
|
64
|
-
# deafult it's the plural version of .key, but it can be
|
65
|
-
# subclass to be anything.
|
87
|
+
# Get or set a JSON key to use as a root key on a collection-type
|
88
|
+
# serializable. By deafult it's the plural version of .key, but it can be
|
89
|
+
# overridden in a subclass to be anything. Calling the method with a key
|
90
|
+
# will set the key, calling it without any arguments will get the key.
|
66
91
|
#
|
67
92
|
# @return [Symbol] a symbol to be used as a key for a JSON-ready Hash
|
68
|
-
|
69
|
-
|
93
|
+
# @param key [Symbol, nil] a symbol to be used as a key for a JSON-ready
|
94
|
+
# Hash, or nil for no key
|
95
|
+
# @return [Symbol, nil] a symbol to be used as a key for a JSON-ready Hash,
|
96
|
+
# or nil for no key
|
97
|
+
def self.collection_key(*key)
|
98
|
+
@default_collection_key ||= self.key.to_s.pluralize.to_sym
|
99
|
+
|
100
|
+
# method called with no args so act as a getter
|
101
|
+
if key.empty?
|
102
|
+
if defined? @collection_key
|
103
|
+
return @collection_key
|
104
|
+
else
|
105
|
+
return @default_collection_key
|
106
|
+
end
|
107
|
+
end
|
70
108
|
|
71
|
-
|
109
|
+
# method called with args so act as a setter
|
110
|
+
@collection_key = key.first&.to_sym
|
72
111
|
end
|
73
112
|
|
74
|
-
#
|
75
|
-
# of this serializer. Typically you'd add
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
113
|
+
# Call with a block returning an array of strings that should be added to
|
114
|
+
# the cache key for an instance of this serializer. Typically you'd add
|
115
|
+
# in string values to uniquely represent the values you're passing to
|
116
|
+
# the serializer so that they are cached separately. But it could also
|
117
|
+
# contain any custom logic about how to construct a cache key.
|
118
|
+
# Call without a block to act as a getter and return the value.
|
79
119
|
#
|
80
120
|
# @example cache based off models provided in options
|
81
|
-
#
|
82
|
-
# [options[:current_user].cache_key]
|
83
|
-
# end
|
121
|
+
# cache_key_addons { |options| [options[:current_user].id] }
|
84
122
|
#
|
85
123
|
# @example time-based caching
|
86
|
-
#
|
87
|
-
# [Date.today.to_s]
|
88
|
-
# end
|
124
|
+
# cache_key_addons { |_options| [Date.today.to_s] }
|
89
125
|
#
|
90
126
|
# @param options [Hash] the options hash passed to the serializer, will be
|
91
127
|
# passed in here as well so you can refernce it if needed.
|
128
|
+
# @yield [options] a block that takes options passed to the serializer and
|
129
|
+
# should return an array of strings to use in the cache key
|
92
130
|
# @return [Array<String>]
|
93
|
-
def self.cache_key_addons(
|
94
|
-
|
131
|
+
def self.cache_key_addons(options = {}, &block)
|
132
|
+
@cache_key_addons ||= nil
|
133
|
+
|
134
|
+
if block_given?
|
135
|
+
@cache_key_addons = block
|
136
|
+
nil
|
137
|
+
else
|
138
|
+
Array(@cache_key_addons&.call(options))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get or set a cache key that can be changed whenever an outside dependency
|
143
|
+
# of any kind changes in any way that could change the output of your
|
144
|
+
# serializer. For instance, if a mixin is changed. Or maybe an object
|
145
|
+
# you're serializing has changed it's #to_json method. This key should be
|
146
|
+
# changed accordingly, to bust the cache so that you're not serving stale
|
147
|
+
# data.
|
148
|
+
#
|
149
|
+
# @return [String] a version string in any form
|
150
|
+
def self.dependency_key(key = nil)
|
151
|
+
@dependency_key ||= nil
|
152
|
+
|
153
|
+
# method called with no args so act as a getter
|
154
|
+
return @dependency_key unless key
|
155
|
+
|
156
|
+
# method called with args so act as a setter
|
157
|
+
@dependency_key = key.to_s
|
95
158
|
end
|
96
159
|
|
97
160
|
# Return a cache key string for the serializer class to be included in the
|
@@ -155,7 +218,11 @@ module CacheCrispies
|
|
155
218
|
end
|
156
219
|
private_class_method :nested_serializers
|
157
220
|
|
158
|
-
def self.serialize(
|
221
|
+
def self.serialize(
|
222
|
+
*attribute_names,
|
223
|
+
from: nil, with: nil, through: nil, to: nil,
|
224
|
+
&block
|
225
|
+
)
|
159
226
|
attribute_names.flatten.map { |att| att&.to_sym }.map do |attrib|
|
160
227
|
current_nesting = Array(@nesting).dup
|
161
228
|
current_conditions = Array(@conditions).dup
|
@@ -165,9 +232,11 @@ module CacheCrispies
|
|
165
232
|
attrib,
|
166
233
|
from: from,
|
167
234
|
with: with,
|
235
|
+
through: through,
|
168
236
|
to: to,
|
169
237
|
nesting: current_nesting,
|
170
|
-
conditions: current_conditions
|
238
|
+
conditions: current_conditions,
|
239
|
+
&block
|
171
240
|
)
|
172
241
|
end
|
173
242
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rails'
|
4
|
-
|
5
3
|
module CacheCrispies
|
6
4
|
# Handles rendering and possibly caching a collection of models using a
|
7
5
|
# Serializer
|
@@ -48,11 +46,11 @@ module CacheCrispies
|
|
48
46
|
hash[plan.cache_key] = model
|
49
47
|
end
|
50
48
|
|
51
|
-
|
49
|
+
CacheCrispies.cache.fetch_multi(*models_by_cache_key.keys) do |cache_key|
|
52
50
|
model = models_by_cache_key[cache_key]
|
53
51
|
|
54
52
|
serializer.new(model, options).as_json
|
55
|
-
end
|
53
|
+
end.values
|
56
54
|
end
|
57
55
|
end
|
58
56
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module CacheCrispies
|
6
|
+
class Configuration
|
7
|
+
SETTINGS = [
|
8
|
+
:cache_store,
|
9
|
+
:etags
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
SETTINGS.each do |setting|
|
13
|
+
attr_accessor setting
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
reset!
|
18
|
+
end
|
19
|
+
|
20
|
+
alias etags? etags
|
21
|
+
|
22
|
+
# Resets all values to their defaults. Useful for testing.
|
23
|
+
def reset!
|
24
|
+
@cache_store = Rails.cache || ActiveSupport::Cache::NullStore.new
|
25
|
+
@etags = false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -28,14 +28,16 @@ module CacheCrispies
|
|
28
28
|
def cache_render(serializer, cacheable, options = {})
|
29
29
|
plan = CacheCrispies::Plan.new(serializer, cacheable, options)
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
if CacheCrispies.config.etags?
|
32
|
+
response.weak_etag = plan.etag
|
33
|
+
end
|
34
34
|
|
35
35
|
serializer_json =
|
36
36
|
if plan.collection?
|
37
|
-
|
38
|
-
|
37
|
+
plan.cache do
|
38
|
+
CacheCrispies::Collection.new(
|
39
|
+
cacheable, serializer, options
|
40
|
+
).as_json
|
39
41
|
end
|
40
42
|
else
|
41
43
|
plan.cache { serializer.new(cacheable, options).as_json }
|
data/lib/cache_crispies/plan.rb
CHANGED
@@ -57,6 +57,7 @@ module CacheCrispies
|
|
57
57
|
[
|
58
58
|
CACHE_KEY_PREFIX,
|
59
59
|
serializer.cache_key_base,
|
60
|
+
serializer.dependency_key,
|
60
61
|
addons_key,
|
61
62
|
cacheable.cache_key
|
62
63
|
].flatten.compact.join(CACHE_KEY_SEPARATOR)
|
@@ -69,7 +70,7 @@ module CacheCrispies
|
|
69
70
|
# @return whatever the provided block returns
|
70
71
|
def cache
|
71
72
|
if cache?
|
72
|
-
|
73
|
+
CacheCrispies.cache.fetch(cache_key) { yield }
|
73
74
|
else
|
74
75
|
yield
|
75
76
|
end
|
@@ -14,6 +14,7 @@ describe CacheCrispies::Attribute do
|
|
14
14
|
let(:key) { :name }
|
15
15
|
let(:from) { nil }
|
16
16
|
let(:with) { nil }
|
17
|
+
let(:through) { nil }
|
17
18
|
let(:to) { nil }
|
18
19
|
let(:nesting) { [] }
|
19
20
|
let(:conditions) { [] }
|
@@ -22,6 +23,7 @@ describe CacheCrispies::Attribute do
|
|
22
23
|
key,
|
23
24
|
from: from,
|
24
25
|
with: with,
|
26
|
+
through: through,
|
25
27
|
to: to,
|
26
28
|
nesting: nesting,
|
27
29
|
conditions: conditions
|
@@ -60,6 +62,23 @@ describe CacheCrispies::Attribute do
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
context 'with a through: argument' do
|
66
|
+
let(:through) { :branding }
|
67
|
+
let(:model) { OpenStruct.new(branding: OpenStruct.new(name: name)) }
|
68
|
+
|
69
|
+
it 'returns the value from the "through" object' do
|
70
|
+
expect(subject).to eq name
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the through method returns nil' do
|
74
|
+
let(:model) { OpenStruct.new(branding: nil) }
|
75
|
+
|
76
|
+
it 'returns nil' do
|
77
|
+
expect(subject).to be nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
63
82
|
context 'with a to: argument' do
|
64
83
|
context 'when corecing to a String' do
|
65
84
|
let(:name) { 1138 }
|
@@ -155,5 +174,15 @@ describe CacheCrispies::Attribute do
|
|
155
174
|
end
|
156
175
|
end
|
157
176
|
end
|
177
|
+
|
178
|
+
context 'with a block' do
|
179
|
+
let(:instance) {
|
180
|
+
described_class.new(key) { |model, _opt| model.name.upcase }
|
181
|
+
}
|
182
|
+
|
183
|
+
it 'uses the return value of the block' do
|
184
|
+
expect(subject).to eq "CAP'N CRUNCH"
|
185
|
+
end
|
186
|
+
end
|
158
187
|
end
|
159
188
|
end
|