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.
@@ -0,0 +1,289 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe CacheCrispies::Base do
5
+ class NutritionSerializer < CacheCrispies::Base
6
+ serialize :calories
7
+ end
8
+
9
+ class CacheCrispiesTestSerializer < CacheCrispies::Base
10
+ serialize :id, :company, to: String
11
+
12
+ show_if -> { true } do
13
+ show_if -> { true } do
14
+ show_if -> { true } do
15
+ serialize :name, from: :brand
16
+ end
17
+ end
18
+ end
19
+
20
+ nest_in :nested do
21
+ nest_in :nested_again do
22
+ serialize :deeply_nested do |model, _opts|
23
+ model.deeply_nested.to_s.upcase
24
+ end
25
+ end
26
+ end
27
+
28
+ serialize :nutrition_info, with: NutritionSerializer
29
+
30
+ serialize :organic, to: :bool
31
+ serialize :parent_company, through: :legal
32
+
33
+ def id
34
+ model.id.to_s
35
+ end
36
+ end
37
+
38
+ let(:model) do
39
+ OpenStruct.new(
40
+ id: 42,
41
+ brand: 'Cookie Crisp',
42
+ company: 'General Mills',
43
+ deeply_nested: true,
44
+ nutrition_info: OpenStruct.new(calories: 1_000),
45
+ organic: 'true',
46
+ legal: OpenStruct.new(parent_company: 'Disney probably')
47
+ )
48
+ end
49
+
50
+ let(:serializer) { CacheCrispiesTestSerializer }
51
+ let(:instance) { serializer.new(model) }
52
+ subject { instance }
53
+
54
+ describe '#as_json' do
55
+ it 'serializes to a hash' do
56
+ expect(subject.as_json).to eq(
57
+ id: '42',
58
+ name: 'Cookie Crisp',
59
+ company: 'General Mills',
60
+ nested: {
61
+ nested_again: {
62
+ deeply_nested: 'TRUE'
63
+ }
64
+ },
65
+ nutrition_info: {
66
+ calories: 1000
67
+ },
68
+ organic: true,
69
+ parent_company: 'Disney probably'
70
+ )
71
+ end
72
+ end
73
+
74
+ describe '.key' do
75
+ it 'underscores the demodulized class name by default' do
76
+ expect(serializer.key).to eq :cache_crispies_test
77
+ end
78
+
79
+ context 'with a custom key' do
80
+ after { serializer.remove_instance_variable :@key }
81
+
82
+ it 'sets and returns a custom key' do
83
+ expect {
84
+ serializer.key 'custom_key'
85
+ }.to change { serializer.key }.from(:cache_crispies_test).to :custom_key
86
+ end
87
+
88
+ it 'sets and returns a nil key' do
89
+ expect {
90
+ serializer.key nil
91
+ }.to change { serializer.key }.from(:cache_crispies_test).to nil
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '.collection_key' do
97
+ it 'pluralizes the #key by default' do
98
+ expect(serializer.collection_key).to eq :cache_crispies_tests
99
+ end
100
+
101
+ context 'with a custom key' do
102
+ after { serializer.remove_instance_variable :@collection_key }
103
+
104
+ it 'sets and returns a custom key' do
105
+ expect {
106
+ serializer.collection_key 'custom_key'
107
+ }.to change {
108
+ serializer.collection_key
109
+ }.from(:cache_crispies_tests).to :custom_key
110
+ end
111
+
112
+ it 'sets and returns a nil key' do
113
+ expect {
114
+ serializer.collection_key nil
115
+ }.to change {
116
+ serializer.collection_key
117
+ }.from(:cache_crispies_tests).to nil
118
+ end
119
+ end
120
+ end
121
+
122
+ describe '.do_caching' do
123
+ it 'is false by default' do
124
+ expect(serializer.do_caching).to be false
125
+ end
126
+
127
+ context 'when called with an argument' do
128
+ after { serializer.remove_instance_variable :@do_caching }
129
+
130
+ it 'sets and returns a value' do
131
+ expect {
132
+ serializer.do_caching true
133
+ }.to change {
134
+ serializer.do_caching
135
+ }.from(false).to true
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '.cache_key_addons' do
141
+ it 'returns an empty array by default' do
142
+ expect(serializer.cache_key_addons).to eq []
143
+ end
144
+
145
+ context 'when given a block' do
146
+ it 'stores the block for later execution' do
147
+ serializer.cache_key_addons { |opts| opts[:username].downcase }
148
+ expect(
149
+ serializer.cache_key_addons(username: 'CapnCrunch')
150
+ ).to eq ['capncrunch']
151
+ end
152
+
153
+ it 'returns nil' do
154
+ expect(serializer.cache_key_addons { |_opts| }).to be nil
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '.dependency_key' do
160
+ it 'returns nil by default' do
161
+ expect(serializer.dependency_key).to be nil
162
+ end
163
+
164
+ context 'after being set' do
165
+ let(:key) { SecureRandom.hex }
166
+ before { serializer.dependency_key key }
167
+
168
+ it 'returns the set value' do
169
+ expect(serializer.dependency_key).to be key
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '.cache_key_base' do
175
+ let(:nested_serializer_digest) { 'nutrition-serializer-digest' }
176
+ let(:serializer_file_path) {
177
+ File.expand_path('../fixtures/test_serializer.rb', __dir__)
178
+ }
179
+ let(:serializer_file_digest) {
180
+ Digest::MD5.file(serializer_file_path).to_s
181
+ }
182
+
183
+ before do
184
+ allow(NutritionSerializer).to receive(:file_hash).and_return(
185
+ nested_serializer_digest
186
+ )
187
+ allow(Rails).to receive_message_chain(:root, :join).and_return(
188
+ serializer_file_path
189
+ )
190
+ end
191
+
192
+ it 'includes the file name' do
193
+ expect(serializer.cache_key_base).to include serializer.to_s
194
+ end
195
+
196
+ it "includes a digest of the serializer class file's contents" do
197
+ expect(serializer.cache_key_base).to include serializer_file_digest
198
+ end
199
+
200
+ it "includes a digest of the nested serializer class file's contents" do
201
+ expect(serializer.cache_key_base).to include nested_serializer_digest
202
+ end
203
+
204
+ it 'correctly formats the key' do
205
+ expect(serializer.cache_key_base).to eq(
206
+ "#{serializer}-#{serializer_file_digest}+#{nested_serializer_digest}"
207
+ )
208
+ end
209
+ end
210
+
211
+ describe '.attributes' do
212
+ subject { instance.class.attributes }
213
+
214
+ it 'contains all the attributes' do
215
+ expect(subject.length).to eq 7
216
+ end
217
+
218
+ it 'preserves the attribute order' do
219
+ expect(subject.map(&:key)).to eq(
220
+ %i[id company name deeply_nested nutrition_info organic parent_company]
221
+ )
222
+ end
223
+
224
+ it 'contains the correct attribute values' do
225
+ expect(subject[0].method_name).to eq :id
226
+ expect(subject[0].key).to eq :id
227
+ expect(subject[0].serializer).to be nil
228
+ expect(subject[0].through).to be nil
229
+ expect(subject[0].coerce_to).to be String
230
+ expect(subject[0].nesting).to eq []
231
+ expect(subject[0].conditions).to eq []
232
+ expect(subject[0].block).to be nil
233
+
234
+ expect(subject[1].method_name).to eq :company
235
+ expect(subject[1].key).to eq :company
236
+ expect(subject[1].serializer).to be nil
237
+ expect(subject[1].through).to be nil
238
+ expect(subject[1].coerce_to).to be String
239
+ expect(subject[1].nesting).to eq []
240
+ expect(subject[1].conditions).to eq []
241
+ expect(subject[1].block).to be nil
242
+
243
+ expect(subject[2].method_name).to eq :brand
244
+ expect(subject[2].key).to eq :name
245
+ expect(subject[2].serializer).to be nil
246
+ expect(subject[2].through).to be nil
247
+ expect(subject[2].coerce_to).to be nil
248
+ expect(subject[2].nesting).to eq []
249
+ expect(subject[2].conditions.length).to be 3
250
+ expect(subject[2].block).to be nil
251
+
252
+ expect(subject[3].method_name).to eq :deeply_nested
253
+ expect(subject[3].key).to eq :deeply_nested
254
+ expect(subject[3].serializer).to be nil
255
+ expect(subject[3].through).to be nil
256
+ expect(subject[3].coerce_to).to be nil
257
+ expect(subject[3].nesting).to eq %i[nested nested_again]
258
+ expect(subject[3].conditions).to eq []
259
+ expect(subject[3].block).to be_a_kind_of Proc
260
+
261
+ expect(subject[4].method_name).to eq :nutrition_info
262
+ expect(subject[4].key).to eq :nutrition_info
263
+ expect(subject[4].serializer).to be NutritionSerializer
264
+ expect(subject[4].through).to be nil
265
+ expect(subject[4].coerce_to).to be nil
266
+ expect(subject[4].nesting).to eq []
267
+ expect(subject[4].conditions).to eq []
268
+ expect(subject[4].block).to be nil
269
+
270
+ expect(subject[5].method_name).to eq :organic
271
+ expect(subject[5].key).to eq :organic
272
+ expect(subject[5].serializer).to be nil
273
+ expect(subject[5].through).to be nil
274
+ expect(subject[5].coerce_to).to be :bool
275
+ expect(subject[5].nesting).to eq []
276
+ expect(subject[5].conditions).to eq []
277
+ expect(subject[5].block).to be nil
278
+
279
+ expect(subject[6].method_name).to eq :parent_company
280
+ expect(subject[6].key).to eq :parent_company
281
+ expect(subject[6].serializer).to be nil
282
+ expect(subject[6].through).to be :legal
283
+ expect(subject[6].coerce_to).to be nil
284
+ expect(subject[6].nesting).to eq []
285
+ expect(subject[6].conditions).to eq []
286
+ expect(subject[6].block).to be nil
287
+ end
288
+ end
289
+ end
@@ -7,10 +7,7 @@ describe CacheCrispies::Collection do
7
7
 
8
8
  class CacheableCerealSerializerForCollection < CacheCrispies::Base
9
9
  serialize :name
10
-
11
- def self.do_caching?
12
- true
13
- end
10
+ do_caching true
14
11
  end
15
12
 
16
13
  let(:name1) { 'Cinnamon Toast Crunch' }
@@ -35,8 +32,8 @@ describe CacheCrispies::Collection do
35
32
 
36
33
  it "doesn't cache the results" do
37
34
  expect(CacheCrispies::Plan).to_not receive(:new)
38
- expect(Rails).to_not receive :cache
39
- expect(subject.as_json).to eq [ {name: name1}, {name: name2} ]
35
+ expect(CacheCrispies).to_not receive :cache
36
+ expect(subject.as_json).to eq [{ name: name1 }, { name: name2 }]
40
37
  end
41
38
  end
42
39
 
@@ -45,29 +42,42 @@ describe CacheCrispies::Collection do
45
42
 
46
43
  it "doesn't cache the results" do
47
44
  expect(CacheCrispies::Plan).to_not receive(:new)
48
- expect(Rails).to_not receive :cache
49
- expect(subject.as_json).to eq [ {name: name1}, {name: name2} ]
45
+ expect(CacheCrispies).to_not receive :cache
46
+ expect(subject.as_json).to eq [{ name: name1 }, { name: name2 }]
50
47
  end
51
48
  end
52
49
  end
53
50
 
54
51
  context 'when it is cacheable' do
55
- it 'caches the results' do
56
- expect(CacheCrispies::Plan).to receive(:new).with(
57
- serializer, model1, options
58
- ).and_return double(cache_key: 'cereal-key-1')
52
+ context 'when the collection cache key misses' do
53
+ before do
54
+ allow(CacheCrispies).to receive_message_chain(
55
+ :cache, :fetch
56
+ ).with('cacheable-collection-key').and_yield
57
+ end
59
58
 
60
- expect(CacheCrispies::Plan).to receive(:new).with(
61
- serializer, model2, options
62
- ).and_return double(cache_key: 'cereal-key-2')
59
+ it 'fetches the cache for each object in the collection' do
60
+ expect(CacheCrispies::Plan).to receive(:new).with(
61
+ serializer, model1, options
62
+ ).and_return double('plan-dbl-1', cache_key: 'cereal-key-1')
63
63
 
64
- expect(Rails).to receive_message_chain(:cache, :fetch_multi).with(
65
- %w[cereal-key-1 cereal-key-2]
66
- ).and_yield('cereal-key-1').and_return(name: name1)
67
- .and_yield('cereal-key-2').and_return(name: name2)
64
+ expect(CacheCrispies::Plan).to receive(:new).with(
65
+ serializer, model2, options
66
+ ).and_return double('plan-dbl-2', cache_key: 'cereal-key-2')
68
67
 
69
- subject.as_json
68
+ expect(CacheCrispies).to receive_message_chain(
69
+ :cache, :fetch_multi
70
+ ).with(
71
+ 'cereal-key-1', 'cereal-key-2'
72
+ ).and_yield('cereal-key-1').and_return(
73
+ name: name1
74
+ ).and_yield('cereal-key-2').and_return(
75
+ name: name2
76
+ )
77
+
78
+ subject.as_json
79
+ end
70
80
  end
71
81
  end
72
82
  end
73
- end
83
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheCrispies::Configuration do
4
+ describe '#cache_store' do
5
+ context 'when Rails.cache is defined' do
6
+ let(:cache_double) { double }
7
+ before { expect(Rails).to receive(:cache).and_return cache_double }
8
+
9
+ it 'is Rails.cache by default' do
10
+ expect(subject.cache_store).to be cache_double
11
+ end
12
+ end
13
+
14
+ context 'when Rails.cache is nil' do
15
+ before { expect(Rails).to receive(:cache).and_return nil }
16
+
17
+ it 'is NullStore by default' do
18
+ expect(subject.cache_store).to be_kind_of ActiveSupport::Cache::NullStore
19
+ end
20
+ end
21
+
22
+ it 'can be changed' do
23
+ cache_store = ActiveSupport::Cache::NullStore.new
24
+
25
+ expect {
26
+ subject.cache_store = cache_store
27
+ }.to change { subject.cache_store }.to cache_store
28
+ end
29
+ end
30
+
31
+ describe '#etags' do
32
+ it 'is false by default' do
33
+ expect(subject.etags).to be false
34
+ end
35
+
36
+ it 'can be changed' do
37
+ expect {
38
+ subject.etags = true
39
+ }.to change { subject.etags }.to true
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+ require 'active_model'
3
+ require 'action_controller'
4
+
5
+ describe CacheCrispies::Controller do
6
+ class Cereal
7
+ include ActiveModel::Model
8
+ attr_accessor :name
9
+ end
10
+
11
+ class CerealController < ActionController::Base
12
+ include CacheCrispies::Controller
13
+ end
14
+
15
+ class CerealSerializerForController < CacheCrispies::Base
16
+ key 'cereal'
17
+ serialize :name
18
+ end
19
+
20
+ let(:cereal_names) { ['Count Chocula', 'Eyeholes'] }
21
+ let(:collection) { cereal_names.map { |name| Cereal.new(name: name) } }
22
+
23
+ subject { CerealController.new }
24
+
25
+ describe '#cache_render' do
26
+ let(:etags) { false }
27
+ let(:single_json) { { cereal: { name: cereal_names.first } }.to_json }
28
+ let(:collection_json) {
29
+ { cereals: cereal_names.map { |name| { name: name } } }.to_json
30
+ }
31
+
32
+ before do
33
+ expect(
34
+ CacheCrispies
35
+ ).to receive_message_chain(:config, :etags?).and_return etags
36
+ end
37
+
38
+ context 'with etags disabled' do
39
+ it 'does not set etags' do
40
+ expect(subject).to receive(:render).with json: collection_json
41
+ response_double = double
42
+ allow(subject).to receive(:response).and_return response_double
43
+ expect(response_double).to_not receive(:weak_etag=)
44
+
45
+ subject.cache_render CerealSerializerForController, collection
46
+ end
47
+ end
48
+
49
+ context 'with etags enabled' do
50
+ let(:etags) { true }
51
+
52
+ it 'sets etags' do
53
+ expect(subject).to receive(:render).with json: collection_json
54
+ expect_any_instance_of(
55
+ CacheCrispies::Plan
56
+ ).to receive(:etag).and_return 'test-etag'
57
+ expect(subject).to receive_message_chain(:response, :weak_etag=).with 'test-etag'
58
+
59
+ subject.cache_render CerealSerializerForController, collection
60
+ end
61
+ end
62
+
63
+ it 'renders a json collection' do
64
+ expect(subject).to receive(:render).with json: collection_json
65
+
66
+ subject.cache_render CerealSerializerForController, collection
67
+ end
68
+
69
+ it 'renders a single json object' do
70
+ expect(subject).to receive(:render).with json: single_json
71
+
72
+ subject.cache_render CerealSerializerForController, collection.first
73
+ end
74
+
75
+ context 'with a status: option' do
76
+ it 'passes the status option to the Rails render call' do
77
+ expect(subject).to receive(:render).with(
78
+ json: single_json,
79
+ status: 418
80
+ )
81
+
82
+ subject.cache_render(
83
+ CerealSerializerForController,
84
+ collection.first,
85
+ status: 418
86
+ )
87
+ end
88
+ end
89
+ end
90
+ end