cache_crispies 0.3.1 → 1.0.1

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