cache_crispies 1.3.2 → 1.5.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 +4 -4
- data/lib/cache_crispies/attribute.rb +43 -8
- data/lib/cache_crispies/base.rb +35 -1
- data/lib/cache_crispies/collection.rb +16 -0
- data/lib/cache_crispies/controller.rb +1 -1
- data/lib/cache_crispies/hash_builder.rb +11 -10
- data/lib/cache_crispies/json_builder.rb +69 -0
- data/lib/cache_crispies/plan.rb +2 -2
- data/lib/cache_crispies/version.rb +1 -1
- data/lib/cache_crispies.rb +4 -0
- data/spec/cache_crispies/base_spec.rb +87 -0
- data/spec/cache_crispies/collection_spec.rb +8 -0
- data/spec/cache_crispies/controller_spec.rb +40 -10
- data/spec/cache_crispies/json_builder_spec.rb +218 -0
- data/spec/fixtures/engine_test_serializer.rb +3 -0
- data/spec/spec_helper.rb +17 -2
- metadata +64 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bce114ae1c4a30b76f0f72c18996ade2d18bdf4efec52b3e3cf7d889a4c7b444
|
4
|
+
data.tar.gz: d42c3daa8142d5f0dea1ae43890f67d232064ca2c505bca4a3edac9c4f282980
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7deb2b2f954c8cb56fdf6bd23f7b99d441ec45252d5ea7a5012b6bac04fe67ff6ba38ef640d803e285aafa77c6b2f626cce9726a8542c7e1d9c895a4543c1928
|
7
|
+
data.tar.gz: 6d44ca0bb510c8a10ac14fea8c1b30333e73e9f060446aaa6c31c00c6a6e0e66f8a41c9fae05d872c233dbb4850f81e22f624fa258fb80a9c4059b5c900f2789
|
@@ -63,18 +63,29 @@ module CacheCrispies
|
|
63
63
|
# @raise [InvalidCoercionType] when an invalid argument is passed in the
|
64
64
|
# to: argument
|
65
65
|
def value_for(target, options)
|
66
|
-
value =
|
67
|
-
if block?
|
68
|
-
block.call(target, options)
|
69
|
-
elsif through?
|
70
|
-
target.public_send(through)&.public_send(method_name)
|
71
|
-
else
|
72
|
-
target.public_send(method_name)
|
73
|
-
end
|
66
|
+
value = retrieve_value(target, options)
|
74
67
|
|
75
68
|
serializer ? serialize(value, options) : coerce(value)
|
76
69
|
end
|
77
70
|
|
71
|
+
# Writes a key-value pair of the attribute for the given target object
|
72
|
+
# and options into the given Oj::StringWriter
|
73
|
+
#
|
74
|
+
# @param json_writer [Oj::StringWriter] any Oj::StringWriter instance
|
75
|
+
# @param target [Object] typically ActiveRecord::Base, but could be anything
|
76
|
+
# @param options [Hash] any optional values from the serializer instance
|
77
|
+
# @raise [InvalidCoercionType] when an invalid argument is passed in the
|
78
|
+
# to: argument
|
79
|
+
def write_to_json(json_writer, target, options)
|
80
|
+
value = retrieve_value(target, options)
|
81
|
+
|
82
|
+
if serializer
|
83
|
+
write_serialized_value(json_writer, value, options)
|
84
|
+
else
|
85
|
+
json_writer.push_value(coerce(value), key.to_s)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
78
89
|
private
|
79
90
|
|
80
91
|
def through?
|
@@ -85,6 +96,16 @@ module CacheCrispies
|
|
85
96
|
!block.nil?
|
86
97
|
end
|
87
98
|
|
99
|
+
def retrieve_value(target, options)
|
100
|
+
if block?
|
101
|
+
block.call(target, options)
|
102
|
+
elsif through?
|
103
|
+
target.public_send(through)&.public_send(method_name)
|
104
|
+
else
|
105
|
+
target.public_send(method_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
88
109
|
# Here we'll render the attribute with a given serializer and attempt to
|
89
110
|
# cache the results for better cache reusability
|
90
111
|
def serialize(value, options)
|
@@ -99,6 +120,20 @@ module CacheCrispies
|
|
99
120
|
end
|
100
121
|
end
|
101
122
|
|
123
|
+
def write_serialized_value(json_writer, value, options)
|
124
|
+
plan = CacheCrispies::Plan.new(
|
125
|
+
serializer, value, collection: collection, **options
|
126
|
+
)
|
127
|
+
|
128
|
+
json_writer.push_key(key.to_s) if key
|
129
|
+
|
130
|
+
if key && plan.collection?
|
131
|
+
Collection.new(value, serializer, options).write_to_json(json_writer)
|
132
|
+
else
|
133
|
+
JsonBuilder.new(serializer.new(value, options)).call(json_writer, flat: !key)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
102
137
|
def coerce(value)
|
103
138
|
return value if coerce_to.nil?
|
104
139
|
|
data/lib/cache_crispies/base.rb
CHANGED
@@ -43,6 +43,18 @@ module CacheCrispies
|
|
43
43
|
HashBuilder.new(self).call
|
44
44
|
end
|
45
45
|
|
46
|
+
# Renders the serializer instance to an Oj::StringWriter instance
|
47
|
+
#
|
48
|
+
# @return [Oj::StringWriter] an Oj::StringWriter instance with the
|
49
|
+
# serialized content
|
50
|
+
def write_to_json(json_writer = nil)
|
51
|
+
json_writer ||= Oj::StringWriter.new(mode: :rails)
|
52
|
+
|
53
|
+
JsonBuilder.new(self).call(json_writer)
|
54
|
+
|
55
|
+
json_writer
|
56
|
+
end
|
57
|
+
|
46
58
|
# Get or set whether or not this serializer class should allow caching of
|
47
59
|
# results. It returns false by default, but can be overridden in child
|
48
60
|
# classes. Calling the method with an argument will set the value, calling
|
@@ -64,6 +76,21 @@ module CacheCrispies
|
|
64
76
|
alias do_caching? do_caching
|
65
77
|
end
|
66
78
|
|
79
|
+
# Get or set root path of Rails application or engine.
|
80
|
+
# It uses Rails by default, but can be overriden with your Rails Engine class.
|
81
|
+
# Calling the method with an argument will set the value, calling it without
|
82
|
+
# any arguments will get the value.
|
83
|
+
def self.engine(value = nil)
|
84
|
+
@engine ||= superclass.try(:engine)
|
85
|
+
@engine ||= Rails
|
86
|
+
|
87
|
+
# method called with no args so act as a getter
|
88
|
+
return @engine if value.nil?
|
89
|
+
|
90
|
+
# method called with args so act as a setter
|
91
|
+
@engine = value
|
92
|
+
end
|
93
|
+
|
67
94
|
# Get or set a JSON key to use as a root key on a non-collection
|
68
95
|
# serializable. By default it's the name of the class without the
|
69
96
|
# "Serializer" part. But it can be overridden in a subclass to be anything.
|
@@ -178,6 +205,13 @@ module CacheCrispies
|
|
178
205
|
).uniq.sort
|
179
206
|
end
|
180
207
|
|
208
|
+
def self.attributes_by_nesting
|
209
|
+
@attributes_by_nesting ||= (
|
210
|
+
attributes.sort_by(&:nesting).group_by(&:nesting)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
delegate :attributes_by_nesting, to: :class
|
214
|
+
|
181
215
|
private
|
182
216
|
|
183
217
|
def self.file_hash
|
@@ -190,7 +224,7 @@ module CacheCrispies
|
|
190
224
|
parts = %w[app serializers]
|
191
225
|
parts += to_s.deconstantize.split('::').map(&:underscore)
|
192
226
|
parts << "#{to_s.demodulize.underscore}.rb"
|
193
|
-
|
227
|
+
engine.root.join(*parts)
|
194
228
|
end
|
195
229
|
end
|
196
230
|
private_class_method :path
|
@@ -29,6 +29,22 @@ module CacheCrispies
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
# Renders the collection to an Oj::StringWriter instance without caching
|
33
|
+
#
|
34
|
+
# @return [Oj::StringWriter] an Oj::StringWriter instance with the
|
35
|
+
# serialized content
|
36
|
+
def write_to_json(json_writer = nil)
|
37
|
+
json_writer ||= Oj::StringWriter.new(mode: :rails)
|
38
|
+
json_writer.push_array
|
39
|
+
|
40
|
+
collection.each do |model|
|
41
|
+
serializer.new(model, options).write_to_json(json_writer)
|
42
|
+
end
|
43
|
+
|
44
|
+
json_writer.pop
|
45
|
+
json_writer
|
46
|
+
end
|
47
|
+
|
32
48
|
private
|
33
49
|
|
34
50
|
attr_reader :collection, :serializer, :options
|
@@ -16,7 +16,7 @@ module CacheCrispies
|
|
16
16
|
#
|
17
17
|
# @return [Hash]
|
18
18
|
def call
|
19
|
-
return
|
19
|
+
return if @serializer.model.nil?
|
20
20
|
|
21
21
|
hash = {}
|
22
22
|
|
@@ -42,7 +42,7 @@ module CacheCrispies
|
|
42
42
|
hash
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
protected
|
46
46
|
|
47
47
|
attr_reader :serializer, :condition_results
|
48
48
|
|
@@ -56,18 +56,19 @@ module CacheCrispies
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
59
|
+
def target_for(attribute)
|
60
60
|
meth = attribute.method_name
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
62
|
+
if serializer.respond_to?(meth) && meth != :itself
|
63
|
+
serializer
|
64
|
+
else
|
65
|
+
serializer.model
|
66
|
+
end
|
67
|
+
end
|
68
68
|
|
69
|
+
def value_for(attribute)
|
69
70
|
# TODO: rescue NoMethodErrors here with something more telling
|
70
|
-
attribute.value_for(
|
71
|
+
attribute.value_for(target_for(attribute), serializer.options)
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CacheCrispies
|
4
|
+
# Builds out a JSON string directly with Oj::StringWriter
|
5
|
+
class JsonBuilder < HashBuilder
|
6
|
+
# Builds the JSON string with Oj::StringWriter
|
7
|
+
#
|
8
|
+
# @return [Oj::StringWriter]
|
9
|
+
def call(json_writer, flat: false)
|
10
|
+
if @serializer.model.nil?
|
11
|
+
json_writer.push_value(nil)
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
json_writer.push_object unless flat
|
16
|
+
|
17
|
+
last_nesting = []
|
18
|
+
|
19
|
+
serializer.attributes_by_nesting.each do |nesting, attributes|
|
20
|
+
prefix_length = common_prefix_length(last_nesting, nesting)
|
21
|
+
|
22
|
+
(last_nesting.length - prefix_length).times do
|
23
|
+
json_writer.pop
|
24
|
+
end
|
25
|
+
|
26
|
+
nesting[prefix_length..-1].each do |key|
|
27
|
+
json_writer.push_object(key.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
attributes.each do |attrib|
|
31
|
+
write_attribute(json_writer, attrib) if show?(attrib)
|
32
|
+
end
|
33
|
+
|
34
|
+
last_nesting = nesting
|
35
|
+
end
|
36
|
+
|
37
|
+
last_nesting.each do
|
38
|
+
json_writer.pop
|
39
|
+
end
|
40
|
+
|
41
|
+
json_writer.pop unless flat
|
42
|
+
|
43
|
+
json_writer
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def write_attribute(json_writer, attribute)
|
49
|
+
# TODO: rescue NoMethodErrors here with something more telling
|
50
|
+
attribute.write_to_json(
|
51
|
+
json_writer,
|
52
|
+
target_for(attribute),
|
53
|
+
serializer.options
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def common_prefix_length(array1, array2)
|
60
|
+
shorter_length = [array1.length, array2.length].min
|
61
|
+
|
62
|
+
(0...shorter_length).each do |i|
|
63
|
+
return i if array1[i] != array2[i]
|
64
|
+
end
|
65
|
+
|
66
|
+
shorter_length
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/cache_crispies/plan.rb
CHANGED
@@ -16,7 +16,7 @@ module CacheCrispies
|
|
16
16
|
# data under
|
17
17
|
# @option options [Boolean] :collection whether to render the data as a
|
18
18
|
# collection/array or a single object
|
19
|
-
def initialize(serializer, cacheable, key:
|
19
|
+
def initialize(serializer, cacheable, key: UNDEFINED, collection: nil, **options)
|
20
20
|
@serializer = serializer
|
21
21
|
@cacheable = cacheable
|
22
22
|
|
@@ -91,7 +91,7 @@ module CacheCrispies
|
|
91
91
|
private
|
92
92
|
|
93
93
|
def key
|
94
|
-
return @key unless @key
|
94
|
+
return @key unless @key == UNDEFINED
|
95
95
|
|
96
96
|
(collection? ? serializer.collection_key : serializer.key)
|
97
97
|
end
|
data/lib/cache_crispies.rb
CHANGED
@@ -12,6 +12,9 @@ module CacheCrispies
|
|
12
12
|
# The string to use to join parts of the cache keys together
|
13
13
|
CACHE_KEY_SEPARATOR = '+'
|
14
14
|
|
15
|
+
# Magic value for undefined arguments
|
16
|
+
UNDEFINED = Object.new.freeze
|
17
|
+
|
15
18
|
require 'cache_crispies/version'
|
16
19
|
|
17
20
|
# Use autoload for better Rails development
|
@@ -22,6 +25,7 @@ module CacheCrispies
|
|
22
25
|
autoload :Optional, 'cache_crispies/optional'
|
23
26
|
autoload :Configuration, 'cache_crispies/configuration'
|
24
27
|
autoload :HashBuilder, 'cache_crispies/hash_builder'
|
28
|
+
autoload :JsonBuilder, 'cache_crispies/json_builder'
|
25
29
|
autoload :Memoizer, 'cache_crispies/memoizer'
|
26
30
|
autoload :Controller, 'cache_crispies/controller'
|
27
31
|
autoload :Plan, 'cache_crispies/plan'
|
@@ -39,6 +39,15 @@ describe CacheCrispies::Base do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
class MyEngine
|
43
|
+
def self.root
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class CacheCrispiesEngineTestSerializer < CacheCrispiesTestSerializer
|
48
|
+
engine MyEngine
|
49
|
+
end
|
50
|
+
|
42
51
|
let(:model) do
|
43
52
|
OpenStruct.new(
|
44
53
|
id: 42,
|
@@ -52,6 +61,7 @@ describe CacheCrispies::Base do
|
|
52
61
|
end
|
53
62
|
|
54
63
|
let(:serializer) { CacheCrispiesTestSerializer }
|
64
|
+
let(:engine_serializer) { CacheCrispiesEngineTestSerializer }
|
55
65
|
let(:instance) { serializer.new(model) }
|
56
66
|
subject { instance }
|
57
67
|
|
@@ -95,6 +105,50 @@ describe CacheCrispies::Base do
|
|
95
105
|
end
|
96
106
|
end
|
97
107
|
|
108
|
+
describe '#write_to_json' do
|
109
|
+
let(:recovered_hash) { Oj.compat_load(subject.write_to_json.to_s, symbol_keys: true) }
|
110
|
+
|
111
|
+
it 'serializes to an Oj::StringWriter' do
|
112
|
+
expect(subject.write_to_json).to be_kind_of(Oj::StringWriter)
|
113
|
+
|
114
|
+
expect(recovered_hash).to eq(
|
115
|
+
id: '42',
|
116
|
+
name: 'Cookie Crisp',
|
117
|
+
company: 'General Mills',
|
118
|
+
nested: {
|
119
|
+
nested_again: {
|
120
|
+
deeply_nested: 'TRUE'
|
121
|
+
}
|
122
|
+
},
|
123
|
+
nutrition_info: {
|
124
|
+
calories: 1000
|
125
|
+
},
|
126
|
+
organic: true,
|
127
|
+
parent_company: 'Disney probably'
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when nutrition_info is nil' do
|
132
|
+
before { model.nutrition_info = nil }
|
133
|
+
|
134
|
+
it 'serializes to an Oj::StringWriter' do
|
135
|
+
expect(recovered_hash).to eq(
|
136
|
+
id: '42',
|
137
|
+
name: 'Cookie Crisp',
|
138
|
+
company: 'General Mills',
|
139
|
+
nested: {
|
140
|
+
nested_again: {
|
141
|
+
deeply_nested: 'TRUE'
|
142
|
+
}
|
143
|
+
},
|
144
|
+
nutrition_info: nil,
|
145
|
+
organic: true,
|
146
|
+
parent_company: 'Disney probably'
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
98
152
|
describe '.key' do
|
99
153
|
it 'underscores the demodulized class name by default' do
|
100
154
|
expect(serializer.key).to eq :cache_crispies_test
|
@@ -161,6 +215,24 @@ describe CacheCrispies::Base do
|
|
161
215
|
end
|
162
216
|
end
|
163
217
|
|
218
|
+
describe '.engine' do
|
219
|
+
it 'defaults to Rails' do
|
220
|
+
expect(serializer.engine).to eq Rails
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when called with an argument' do
|
224
|
+
after { serializer.remove_instance_variable :@engine }
|
225
|
+
|
226
|
+
it 'sets and returns a value' do
|
227
|
+
expect {
|
228
|
+
serializer.engine MyEngine
|
229
|
+
}.to change {
|
230
|
+
serializer.engine
|
231
|
+
}.from(Rails).to MyEngine
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
164
236
|
describe '.cache_key_addons' do
|
165
237
|
it 'returns an empty array by default' do
|
166
238
|
expect(serializer.cache_key_addons).to eq []
|
@@ -203,6 +275,12 @@ describe CacheCrispies::Base do
|
|
203
275
|
let(:serializer_file_digest) {
|
204
276
|
Digest::MD5.file(serializer_file_path).to_s
|
205
277
|
}
|
278
|
+
let(:engine_serializer_file_path) {
|
279
|
+
File.expand_path('../fixtures/engine_test_serializer.rb', __dir__)
|
280
|
+
}
|
281
|
+
let(:engine_serializer_file_digest) {
|
282
|
+
Digest::MD5.file(engine_serializer_file_path).to_s
|
283
|
+
}
|
206
284
|
|
207
285
|
before do
|
208
286
|
allow(NutritionSerializer).to receive(:file_hash).and_return(
|
@@ -211,6 +289,9 @@ describe CacheCrispies::Base do
|
|
211
289
|
allow(Rails).to receive_message_chain(:root, :join).and_return(
|
212
290
|
serializer_file_path
|
213
291
|
)
|
292
|
+
allow(MyEngine).to receive_message_chain(:root, :join).and_return(
|
293
|
+
engine_serializer_file_path
|
294
|
+
)
|
214
295
|
end
|
215
296
|
|
216
297
|
it 'includes the file name' do
|
@@ -230,6 +311,12 @@ describe CacheCrispies::Base do
|
|
230
311
|
"#{serializer}-#{serializer_file_digest}+#{nested_serializer_digest}"
|
231
312
|
)
|
232
313
|
end
|
314
|
+
|
315
|
+
context 'when an engine is set' do
|
316
|
+
it "includes a digest of the serializer class file's contents" do
|
317
|
+
expect(engine_serializer.cache_key_base).to include engine_serializer_file_digest
|
318
|
+
end
|
319
|
+
end
|
233
320
|
end
|
234
321
|
|
235
322
|
describe '.attributes' do
|
@@ -80,4 +80,12 @@ describe CacheCrispies::Collection do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
83
|
+
|
84
|
+
describe '#write_to_json' do
|
85
|
+
let(:recovered_hash) { Oj.compat_load(subject.write_to_json.to_s, symbol_keys: true) }
|
86
|
+
|
87
|
+
it 'serializes to an Oj::StringWriter' do
|
88
|
+
expect(recovered_hash).to eq [{ name: name1 }, { name: name2 }]
|
89
|
+
end
|
90
|
+
end
|
83
91
|
end
|
@@ -38,7 +38,7 @@ describe CacheCrispies::Controller do
|
|
38
38
|
|
39
39
|
context 'with etags disabled' do
|
40
40
|
it 'does not set etags' do
|
41
|
-
expect(subject).to receive(:render).with json: collection_json
|
41
|
+
expect(subject).to receive(:render).with({ json: collection_json })
|
42
42
|
response_double = double
|
43
43
|
allow(subject).to receive(:response).and_return response_double
|
44
44
|
expect(response_double).to_not receive(:weak_etag=)
|
@@ -51,7 +51,7 @@ describe CacheCrispies::Controller do
|
|
51
51
|
let(:etags) { true }
|
52
52
|
|
53
53
|
it 'sets etags' do
|
54
|
-
expect(subject).to receive(:render).with json: collection_json
|
54
|
+
expect(subject).to receive(:render).with({ json: collection_json })
|
55
55
|
expect_any_instance_of(
|
56
56
|
CacheCrispies::Plan
|
57
57
|
).to receive(:etag).and_return 'test-etag'
|
@@ -62,23 +62,53 @@ describe CacheCrispies::Controller do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
it 'renders a json collection' do
|
65
|
-
expect(subject).to receive(:render).with json: collection_json
|
65
|
+
expect(subject).to receive(:render).with({ json: collection_json })
|
66
66
|
|
67
67
|
subject.cache_render CerealSerializerForController, collection
|
68
68
|
end
|
69
69
|
|
70
70
|
it 'renders a single json object' do
|
71
|
-
expect(subject).to receive(:render).with json: single_json
|
71
|
+
expect(subject).to receive(:render).with({ json: single_json })
|
72
72
|
|
73
73
|
subject.cache_render CerealSerializerForController, collection.first
|
74
74
|
end
|
75
75
|
|
76
|
+
context 'with a key: option'do
|
77
|
+
context 'overriding the key in the serializer' do
|
78
|
+
it 'renders a json collection' do
|
79
|
+
expect(subject).to receive(:render).with({ json: { serials: cereal_names.map { |name| { name: name } } }.to_json })
|
80
|
+
|
81
|
+
subject.cache_render CerealSerializerForController, collection, key: "serials"
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'renders a single json object' do
|
85
|
+
expect(subject).to receive(:render).with({ json: { serial: { name: cereal_names.first} }.to_json })
|
86
|
+
|
87
|
+
subject.cache_render CerealSerializerForController, collection.first, key: "serial"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'set to nil' do
|
92
|
+
it 'renders a json collection' do
|
93
|
+
expect(subject).to receive(:render).with({ json: cereal_names.map { |name| { name: name } }.to_json })
|
94
|
+
|
95
|
+
subject.cache_render CerealSerializerForController, collection, key: nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'renders a single json object' do
|
99
|
+
expect(subject).to receive(:render).with({ json: { name: cereal_names.first }.to_json })
|
100
|
+
|
101
|
+
subject.cache_render CerealSerializerForController, collection.first, key: nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
76
106
|
context 'with a status: option' do
|
77
107
|
it 'passes the status option to the Rails render call' do
|
78
|
-
expect(subject).to receive(:render).with(
|
108
|
+
expect(subject).to receive(:render).with({
|
79
109
|
json: single_json,
|
80
110
|
status: 418
|
81
|
-
)
|
111
|
+
})
|
82
112
|
|
83
113
|
subject.cache_render(
|
84
114
|
CerealSerializerForController,
|
@@ -90,9 +120,9 @@ describe CacheCrispies::Controller do
|
|
90
120
|
|
91
121
|
context 'with a meta: option' do
|
92
122
|
it 'adds a meta data hash to the JSON' do
|
93
|
-
expect(subject).to receive(:render).with(
|
123
|
+
expect(subject).to receive(:render).with({
|
94
124
|
json: single_hash.merge(meta: { page: 42 }).to_json
|
95
|
-
)
|
125
|
+
})
|
96
126
|
|
97
127
|
subject.cache_render(
|
98
128
|
CerealSerializerForController,
|
@@ -104,9 +134,9 @@ describe CacheCrispies::Controller do
|
|
104
134
|
|
105
135
|
context 'with a meta_key: option' do
|
106
136
|
it 'adds a meta data hash to the JSON with the provided key' do
|
107
|
-
expect(subject).to receive(:render).with(
|
137
|
+
expect(subject).to receive(:render).with({
|
108
138
|
json: single_hash.merge(test_meta_data: { page: 42 }).to_json
|
109
|
-
)
|
139
|
+
})
|
110
140
|
|
111
141
|
subject.cache_render(
|
112
142
|
CerealSerializerForController,
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CacheCrispies::JsonBuilder do
|
4
|
+
# These example serializers are meant to show a variety of options,
|
5
|
+
# configurations, and data types in order to really put the HashBuilder class
|
6
|
+
# through the ringer.
|
7
|
+
class ConstituentSerializer < CacheCrispies::Base
|
8
|
+
serialize :name
|
9
|
+
end
|
10
|
+
|
11
|
+
class AllergenSerializer < CacheCrispies::Base
|
12
|
+
serialize :name
|
13
|
+
end
|
14
|
+
|
15
|
+
class BuzzwordSerializer < CacheCrispies::Base
|
16
|
+
serialize :tagline, :small_print
|
17
|
+
|
18
|
+
def tagline
|
19
|
+
"#{model.tagline}#{options[:footnote_marker]}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def small_print
|
23
|
+
"#{options[:footnote_marker]}this doesn't mean jack-squat"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class CerealSerializerForJsonBuilder < CacheCrispies::Base
|
28
|
+
serialize :uid, from: :id, to: String
|
29
|
+
serialize :name, :company
|
30
|
+
merge :itself, with: BuzzwordSerializer
|
31
|
+
|
32
|
+
nest_in :about do
|
33
|
+
nest_in :nutritional_information do
|
34
|
+
serialize :calories
|
35
|
+
serialize :ingredients, with: ConstituentSerializer
|
36
|
+
|
37
|
+
serialize :allergies, with: AllergenSerializer, optional: true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
show_if ->(_model, options) { options[:be_trendy] } do
|
42
|
+
nest_in :health do
|
43
|
+
serialize :organic
|
44
|
+
|
45
|
+
show_if ->(model) { model.organic } do
|
46
|
+
serialize :certification
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def certification
|
52
|
+
'Totally Not A Scam Certifiers Inc'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
let(:organic) { false }
|
57
|
+
let(:ingredients) {
|
58
|
+
[
|
59
|
+
OpenStruct.new(name: 'Sugar'),
|
60
|
+
OpenStruct.new(name: 'Other Kind of Sugar')
|
61
|
+
]
|
62
|
+
}
|
63
|
+
let(:allergies) {
|
64
|
+
[
|
65
|
+
OpenStruct.new(name: 'Peanuts'),
|
66
|
+
OpenStruct.new(name: 'Lactose')
|
67
|
+
]
|
68
|
+
}
|
69
|
+
let(:model) {
|
70
|
+
OpenStruct.new(
|
71
|
+
id: 42,
|
72
|
+
name: 'Lucky Charms',
|
73
|
+
company: 'General Mills',
|
74
|
+
calories: 1_000,
|
75
|
+
organic: organic,
|
76
|
+
tagline: "Part of a balanced breakfast",
|
77
|
+
ingredients: ingredients,
|
78
|
+
allergies: allergies
|
79
|
+
)
|
80
|
+
}
|
81
|
+
let(:options) { { footnote_marker: '*' } }
|
82
|
+
let(:serializer) { CerealSerializerForJsonBuilder.new(model, options) }
|
83
|
+
let(:json_writer) { Oj::StringWriter.new(mode: :rails) }
|
84
|
+
subject { described_class.new(serializer) }
|
85
|
+
|
86
|
+
describe '#call' do
|
87
|
+
let(:recovered_hash) { Oj.compat_load(subject.call(json_writer).to_s, symbol_keys: true) }
|
88
|
+
|
89
|
+
it 'correctly renders the hash' do
|
90
|
+
expect(recovered_hash).to eq ({
|
91
|
+
uid: '42',
|
92
|
+
name: 'Lucky Charms',
|
93
|
+
company: 'General Mills',
|
94
|
+
tagline: 'Part of a balanced breakfast*',
|
95
|
+
small_print: "*this doesn't mean jack-squat",
|
96
|
+
about: {
|
97
|
+
nutritional_information: {
|
98
|
+
calories: 1000,
|
99
|
+
ingredients: [
|
100
|
+
{ name: 'Sugar' },
|
101
|
+
{ name: 'Other Kind of Sugar' },
|
102
|
+
]
|
103
|
+
}
|
104
|
+
},
|
105
|
+
health: {}
|
106
|
+
})
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when the outer show_if is true' do
|
110
|
+
let(:options) { { footnote_marker: '†', be_trendy: true } }
|
111
|
+
|
112
|
+
it 'builds values wrapped in the outer if' do
|
113
|
+
expect(recovered_hash).to eq ({
|
114
|
+
uid: '42',
|
115
|
+
name: 'Lucky Charms',
|
116
|
+
company: 'General Mills',
|
117
|
+
tagline: 'Part of a balanced breakfast†',
|
118
|
+
small_print: "†this doesn't mean jack-squat",
|
119
|
+
about: {
|
120
|
+
nutritional_information: {
|
121
|
+
calories: 1000,
|
122
|
+
ingredients: [
|
123
|
+
{ name: 'Sugar' },
|
124
|
+
{ name: 'Other Kind of Sugar' },
|
125
|
+
]
|
126
|
+
}
|
127
|
+
},
|
128
|
+
health: {
|
129
|
+
organic: false
|
130
|
+
}
|
131
|
+
})
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when the inner show_if is true' do
|
135
|
+
let(:organic) { true }
|
136
|
+
|
137
|
+
it 'builds values wrapped in the outer and inner if' do
|
138
|
+
expect(recovered_hash).to eq ({
|
139
|
+
uid: '42',
|
140
|
+
name: 'Lucky Charms',
|
141
|
+
company: 'General Mills',
|
142
|
+
tagline: 'Part of a balanced breakfast†',
|
143
|
+
small_print: "†this doesn't mean jack-squat",
|
144
|
+
about: {
|
145
|
+
nutritional_information: {
|
146
|
+
calories: 1000,
|
147
|
+
ingredients: [
|
148
|
+
{ name: 'Sugar' },
|
149
|
+
{ name: 'Other Kind of Sugar' },
|
150
|
+
]
|
151
|
+
}
|
152
|
+
},
|
153
|
+
health: {
|
154
|
+
organic: true,
|
155
|
+
certification: 'Totally Not A Scam Certifiers Inc'
|
156
|
+
}
|
157
|
+
})
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'when allergies are included' do
|
163
|
+
let(:options) { { footnote_marker: '*', include: :allergies } }
|
164
|
+
|
165
|
+
it 'includes the allergies' do
|
166
|
+
expect(recovered_hash).to eq ({
|
167
|
+
uid: '42',
|
168
|
+
name: 'Lucky Charms',
|
169
|
+
company: 'General Mills',
|
170
|
+
tagline: 'Part of a balanced breakfast*',
|
171
|
+
small_print: "*this doesn't mean jack-squat",
|
172
|
+
about: {
|
173
|
+
nutritional_information: {
|
174
|
+
calories: 1000,
|
175
|
+
ingredients: [
|
176
|
+
{ name: 'Sugar' },
|
177
|
+
{ name: 'Other Kind of Sugar' },
|
178
|
+
],
|
179
|
+
allergies: [
|
180
|
+
{ name: 'Peanuts' },
|
181
|
+
{ name: 'Lactose' },
|
182
|
+
]
|
183
|
+
}
|
184
|
+
},
|
185
|
+
health: {}
|
186
|
+
})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when everything is included' do
|
191
|
+
let(:options) { { footnote_marker: '*', include: '*' } }
|
192
|
+
|
193
|
+
it 'includes the allergies' do
|
194
|
+
expect(recovered_hash).to eq ({
|
195
|
+
uid: '42',
|
196
|
+
name: 'Lucky Charms',
|
197
|
+
company: 'General Mills',
|
198
|
+
tagline: 'Part of a balanced breakfast*',
|
199
|
+
small_print: "*this doesn't mean jack-squat",
|
200
|
+
about: {
|
201
|
+
nutritional_information: {
|
202
|
+
calories: 1000,
|
203
|
+
ingredients: [
|
204
|
+
{ name: 'Sugar' },
|
205
|
+
{ name: 'Other Kind of Sugar' },
|
206
|
+
],
|
207
|
+
allergies: [
|
208
|
+
{ name: 'Peanuts' },
|
209
|
+
{ name: 'Lactose' },
|
210
|
+
]
|
211
|
+
}
|
212
|
+
},
|
213
|
+
health: {}
|
214
|
+
})
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'simplecov'
|
2
|
-
|
4
|
+
require 'simplecov-lcov'
|
5
|
+
|
6
|
+
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
7
|
+
|
8
|
+
SimpleCov.start do
|
9
|
+
enable_coverage :branch
|
10
|
+
formatter(
|
11
|
+
SimpleCov::Formatter::MultiFormatter.new(
|
12
|
+
[
|
13
|
+
SimpleCov::Formatter::HTMLFormatter,
|
14
|
+
SimpleCov::Formatter::LcovFormatter
|
15
|
+
]
|
16
|
+
)
|
17
|
+
)
|
18
|
+
end
|
3
19
|
|
4
|
-
require 'byebug'
|
5
20
|
require_relative '../lib/cache_crispies'
|
6
21
|
|
7
22
|
RSpec.configure do |config|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cache_crispies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Crownoble
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-23 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: railties
|
@@ -16,20 +15,20 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
18
|
+
version: 7.0.0
|
20
19
|
- - "<"
|
21
20
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
21
|
+
version: '9.0'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
26
25
|
requirements:
|
27
26
|
- - ">="
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
28
|
+
version: 7.0.0
|
30
29
|
- - "<"
|
31
30
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
31
|
+
version: '9.0'
|
33
32
|
- !ruby/object:Gem::Dependency
|
34
33
|
name: oj
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,105 +49,132 @@ dependencies:
|
|
50
49
|
requirements:
|
51
50
|
- - ">="
|
52
51
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
52
|
+
version: 7.0.0
|
54
53
|
- - "<"
|
55
54
|
- !ruby/object:Gem::Version
|
56
|
-
version: '
|
55
|
+
version: '9.0'
|
57
56
|
type: :development
|
58
57
|
prerelease: false
|
59
58
|
version_requirements: !ruby/object:Gem::Requirement
|
60
59
|
requirements:
|
61
60
|
- - ">="
|
62
61
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
62
|
+
version: 7.0.0
|
64
63
|
- - "<"
|
65
64
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
65
|
+
version: '9.0'
|
67
66
|
- !ruby/object:Gem::Dependency
|
68
67
|
name: appraisal
|
69
68
|
requirement: !ruby/object:Gem::Requirement
|
70
69
|
requirements:
|
71
70
|
- - "~>"
|
72
71
|
- !ruby/object:Gem::Version
|
73
|
-
version: '2.
|
72
|
+
version: '2.5'
|
74
73
|
type: :development
|
75
74
|
prerelease: false
|
76
75
|
version_requirements: !ruby/object:Gem::Requirement
|
77
76
|
requirements:
|
78
77
|
- - "~>"
|
79
78
|
- !ruby/object:Gem::Version
|
80
|
-
version: '2.
|
79
|
+
version: '2.5'
|
81
80
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
81
|
+
name: base64
|
83
82
|
requirement: !ruby/object:Gem::Requirement
|
84
83
|
requirements:
|
85
84
|
- - "~>"
|
86
85
|
- !ruby/object:Gem::Version
|
87
|
-
version: '2
|
86
|
+
version: '0.2'
|
88
87
|
type: :development
|
89
88
|
prerelease: false
|
90
89
|
version_requirements: !ruby/object:Gem::Requirement
|
91
90
|
requirements:
|
92
91
|
- - "~>"
|
93
92
|
- !ruby/object:Gem::Version
|
94
|
-
version: '2
|
93
|
+
version: '0.2'
|
95
94
|
- !ruby/object:Gem::Dependency
|
96
|
-
name:
|
95
|
+
name: debug
|
97
96
|
requirement: !ruby/object:Gem::Requirement
|
98
97
|
requirements:
|
99
98
|
- - "~>"
|
100
99
|
- !ruby/object:Gem::Version
|
101
|
-
version: '
|
100
|
+
version: '1.10'
|
102
101
|
type: :development
|
103
102
|
prerelease: false
|
104
103
|
version_requirements: !ruby/object:Gem::Requirement
|
105
104
|
requirements:
|
106
105
|
- - "~>"
|
107
106
|
- !ruby/object:Gem::Version
|
108
|
-
version: '
|
107
|
+
version: '1.10'
|
109
108
|
- !ruby/object:Gem::Dependency
|
110
|
-
name:
|
109
|
+
name: mutex_m
|
111
110
|
requirement: !ruby/object:Gem::Requirement
|
112
111
|
requirements:
|
113
112
|
- - "~>"
|
114
113
|
- !ruby/object:Gem::Version
|
115
|
-
version: 3
|
114
|
+
version: '0.3'
|
116
115
|
type: :development
|
117
116
|
prerelease: false
|
118
117
|
version_requirements: !ruby/object:Gem::Requirement
|
119
118
|
requirements:
|
120
119
|
- - "~>"
|
121
120
|
- !ruby/object:Gem::Version
|
122
|
-
version: 3
|
121
|
+
version: '0.3'
|
123
122
|
- !ruby/object:Gem::Dependency
|
124
123
|
name: rspec_junit_formatter
|
125
124
|
requirement: !ruby/object:Gem::Requirement
|
126
125
|
requirements:
|
127
126
|
- - "~>"
|
128
127
|
- !ruby/object:Gem::Version
|
129
|
-
version: '0.
|
128
|
+
version: '0.6'
|
129
|
+
type: :development
|
130
|
+
prerelease: false
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0.6'
|
136
|
+
- !ruby/object:Gem::Dependency
|
137
|
+
name: rspec
|
138
|
+
requirement: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 3.13.0
|
130
143
|
type: :development
|
131
144
|
prerelease: false
|
132
145
|
version_requirements: !ruby/object:Gem::Requirement
|
133
146
|
requirements:
|
134
147
|
- - "~>"
|
135
148
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
149
|
+
version: 3.13.0
|
137
150
|
- !ruby/object:Gem::Dependency
|
138
151
|
name: simplecov
|
139
152
|
requirement: !ruby/object:Gem::Requirement
|
140
153
|
requirements:
|
141
|
-
- -
|
154
|
+
- - "~>"
|
142
155
|
- !ruby/object:Gem::Version
|
143
|
-
version: '0.
|
156
|
+
version: '0.22'
|
144
157
|
type: :development
|
145
158
|
prerelease: false
|
146
159
|
version_requirements: !ruby/object:Gem::Requirement
|
147
160
|
requirements:
|
148
|
-
- -
|
161
|
+
- - "~>"
|
149
162
|
- !ruby/object:Gem::Version
|
150
|
-
version: '0.
|
151
|
-
|
163
|
+
version: '0.22'
|
164
|
+
- !ruby/object:Gem::Dependency
|
165
|
+
name: simplecov-lcov
|
166
|
+
requirement: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - "~>"
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0.8'
|
171
|
+
type: :development
|
172
|
+
prerelease: false
|
173
|
+
version_requirements: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - "~>"
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0.8'
|
152
178
|
email: adam@codenoble.com
|
153
179
|
executables: []
|
154
180
|
extensions: []
|
@@ -163,6 +189,7 @@ files:
|
|
163
189
|
- lib/cache_crispies/configuration.rb
|
164
190
|
- lib/cache_crispies/controller.rb
|
165
191
|
- lib/cache_crispies/hash_builder.rb
|
192
|
+
- lib/cache_crispies/json_builder.rb
|
166
193
|
- lib/cache_crispies/memoizer.rb
|
167
194
|
- lib/cache_crispies/optional.rb
|
168
195
|
- lib/cache_crispies/plan.rb
|
@@ -174,16 +201,18 @@ files:
|
|
174
201
|
- spec/cache_crispies/configuration_spec.rb
|
175
202
|
- spec/cache_crispies/controller_spec.rb
|
176
203
|
- spec/cache_crispies/hash_builder_spec.rb
|
204
|
+
- spec/cache_crispies/json_builder_spec.rb
|
177
205
|
- spec/cache_crispies/memoizer_spec.rb
|
178
206
|
- spec/cache_crispies/plan_spec.rb
|
179
207
|
- spec/cache_crispies_spec.rb
|
208
|
+
- spec/fixtures/engine_test_serializer.rb
|
180
209
|
- spec/fixtures/test_serializer.rb
|
181
210
|
- spec/spec_helper.rb
|
182
211
|
homepage: https://github.com/codenoble/cache-crispies
|
183
212
|
licenses:
|
184
213
|
- MIT
|
185
|
-
metadata:
|
186
|
-
|
214
|
+
metadata:
|
215
|
+
source_code_uri: https://github.com/codenoble/cache-crispies
|
187
216
|
rdoc_options: []
|
188
217
|
require_paths:
|
189
218
|
- lib
|
@@ -191,15 +220,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
191
220
|
requirements:
|
192
221
|
- - ">="
|
193
222
|
- !ruby/object:Gem::Version
|
194
|
-
version:
|
223
|
+
version: 3.1.0
|
195
224
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
225
|
requirements:
|
197
226
|
- - ">="
|
198
227
|
- !ruby/object:Gem::Version
|
199
228
|
version: '0'
|
200
229
|
requirements: []
|
201
|
-
rubygems_version: 3.
|
202
|
-
signing_key:
|
230
|
+
rubygems_version: 3.6.2
|
203
231
|
specification_version: 4
|
204
232
|
summary: Fast Rails serializer with built-in caching
|
205
233
|
test_files:
|
@@ -210,8 +238,10 @@ test_files:
|
|
210
238
|
- spec/cache_crispies/configuration_spec.rb
|
211
239
|
- spec/cache_crispies/controller_spec.rb
|
212
240
|
- spec/cache_crispies/hash_builder_spec.rb
|
241
|
+
- spec/cache_crispies/json_builder_spec.rb
|
213
242
|
- spec/cache_crispies/memoizer_spec.rb
|
214
243
|
- spec/cache_crispies/plan_spec.rb
|
215
244
|
- spec/cache_crispies_spec.rb
|
245
|
+
- spec/fixtures/engine_test_serializer.rb
|
216
246
|
- spec/fixtures/test_serializer.rb
|
217
247
|
- spec/spec_helper.rb
|