Skip to content

Commit a60a926

Browse files
committed
Dirty PoC implem of caching.
1 parent 2eb7fe8 commit a60a926

File tree

4 files changed

+88
-41
lines changed

4 files changed

+88
-41
lines changed

lib/jsonapi/renderer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ module JSONAPI
2020
# @option [Hash] links Top-level links to be included.
2121
# @option [Hash] jsonapi_object JSON API object.
2222
def render(params)
23-
Renderer::Document.new(params).to_hash
23+
Renderer::Document.new(params).to_json
2424
end
2525
end

lib/jsonapi/renderer/document.rb

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,44 @@ def initialize(params = {})
1414
@include = JSONAPI::IncludeDirective.new(params.fetch(:include, {}))
1515
end
1616

17-
def to_hash
18-
@hash ||= document_hash
17+
def to_json
18+
@json ||= document_hash
1919
end
20-
alias to_h to_hash
2120

2221
private
2322

2423
def document_hash
25-
{}.tap do |hash|
26-
if @data != :no_data
27-
hash.merge!(data_hash)
28-
elsif @errors.any?
29-
hash.merge!(errors_hash)
30-
end
31-
hash[:links] = @links if @links.any?
32-
hash[:meta] = @meta unless @meta.nil?
33-
hash[:jsonapi] = @jsonapi unless @jsonapi.nil?
24+
parts = []
25+
if @data != :no_data
26+
parts.concat(data_hash)
27+
elsif @errors.any?
28+
parts << errors_hash
3429
end
30+
parts << "\"links\":#{@links.to_json}" if @links.any?
31+
parts << "\"meta\":#{@meta.to_json}" unless @meta.nil?
32+
parts << "\"jsonapi\": #{@jsonapi.to_json}" unless @jsonapi.nil?
33+
34+
parts.any? ? "{#{parts.join(',')}}" : ''
3535
end
3636

3737
def data_hash
3838
primary, included =
3939
ResourcesProcessor.new(Array(@data), @include, @fields).process
40-
{}.tap do |hash|
41-
hash[:data] = @data.respond_to?(:each) ? primary : primary[0]
42-
hash[:included] = included if included.any?
40+
[].tap do |arr|
41+
data = if @data.respond_to?(:each)
42+
"[#{primary.join(',')}]"
43+
elsif @data.nil?
44+
'null'
45+
else
46+
primary.first
47+
end
48+
arr << "\"data\":#{data}"
49+
arr << "\"included\":[#{included.join(',')}]" if included.any?
4350
end
4451
end
4552

4653
def errors_hash
47-
{}.tap do |hash|
48-
hash[:errors] = @errors.map(&:as_jsonapi)
49-
end
54+
"\"errors\":[#{@errors.map(&:as_jsonapi).map(&:to_json).join(',')}]"
5055
end
5156

5257
def _symbolize_fields(fields)

lib/jsonapi/renderer/resources_processor.rb

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
require 'set'
22

3+
class Cache
4+
def initialize
5+
@cache = {}
6+
end
7+
8+
def fetch_multi(keys, &block)
9+
keys.each_with_object({}) do |k, h|
10+
@cache[k] = block.call(k) unless @cache.key?(k)
11+
h[k] = @cache[k]
12+
end
13+
end
14+
end
15+
316
module JSONAPI
417
module Renderer
518
class ResourcesProcessor
619
def initialize(resources, include, fields)
720
@resources = resources
821
@include = include
922
@fields = fields
23+
24+
25+
@cache = Cache.new
1026
end
1127

1228
def process
1329
traverse_resources
14-
process_resources
15-
16-
[@primary, @included]
30+
[@primary, @included].map { |res| process_resources(res) }
1731
end
1832

1933
private
@@ -72,14 +86,34 @@ def enqueue_resource(res, prefix, include_dir)
7286
@queue << [res, prefix, include_dir]
7387
end
7488

75-
def process_resources
76-
[@primary, @included].each do |resources|
77-
resources.map! do |res|
78-
ri = [res.jsonapi_type, res.jsonapi_id]
79-
include_dir = @include_rels[ri]
80-
fields = @fields[res.jsonapi_type.to_sym]
81-
res.as_jsonapi(include: include_dir, fields: fields)
82-
end
89+
def process_resources(resources)
90+
return process_resources_with_cache(resources) if @cache
91+
92+
resources.map do |res|
93+
ri = [res.jsonapi_type, res.jsonapi_id]
94+
include_dir = @include_rels[ri]
95+
fields = @fields[ri.first.to_sym]
96+
res.as_jsonapi(include: include_dir, fields: fields).to_json
97+
end
98+
end
99+
100+
def process_resources_with_cache(resources)
101+
hash = cache_key_map(resources)
102+
cached = @cache.fetch_multi(hash.keys) do |key|
103+
res, include, fields = hash[key]
104+
res.as_jsonapi(include: include, fields: fields).to_json
105+
end
106+
107+
cached.values
108+
end
109+
110+
def cache_key_map(resources)
111+
resources.each_with_object({}) do |res, h|
112+
ri = [res.jsonapi_type, res.jsonapi_id]
113+
include_dir = @include_rels[ri]
114+
fields = @fields[ri.first.to_sym]
115+
h[res.jsonapi_cache_key(include: include_dir, fields: fields)] =
116+
[res, include_dir, fields]
83117
end
84118
end
85119
end

spec/renderer_spec.rb

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def jsonapi_id
1818
@id.to_s
1919
end
2020

21+
def jsonapi_cache_key(options = {})
22+
"#{self.class.name}--#{jsonapi_type}--#{jsonapi_id}"
23+
end
24+
2125
def jsonapi_related(included)
2226
if included.include?(:posts)
2327
{ posts: @posts.map { |p| p } }
@@ -83,6 +87,10 @@ def jsonapi_id
8387
@id.to_s
8488
end
8589

90+
def jsonapi_cache_key(options = {})
91+
"#{self.class.name}--#{jsonapi_type}--#{jsonapi_id}"
92+
end
93+
8694
def jsonapi_related(included)
8795
included.include?(:author) ? { author: [@author] } : {}
8896
end
@@ -142,7 +150,7 @@ def as_jsonapi(options = {})
142150
data: nil
143151
}
144152

145-
expect(actual).to eq(expected)
153+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
146154
end
147155

148156
it 'renders an empty array' do
@@ -151,7 +159,7 @@ def as_jsonapi(options = {})
151159
data: []
152160
}
153161

154-
expect(actual).to eq(expected)
162+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
155163
end
156164

157165
it 'renders a single resource' do
@@ -189,7 +197,7 @@ def as_jsonapi(options = {})
189197
}
190198
}
191199

192-
expect(actual).to eq(expected)
200+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
193201
end
194202

195203
it 'renders a collection of resources' do
@@ -260,7 +268,7 @@ def as_jsonapi(options = {})
260268
]
261269
}
262270

263-
expect(actual).to eq(expected)
271+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
264272
end
265273

266274
it 'renders included relationships' do
@@ -321,7 +329,7 @@ def as_jsonapi(options = {})
321329
]
322330
}
323331

324-
expect(actual).to eq(expected)
332+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
325333
end
326334

327335
it 'filters out fields' do
@@ -343,7 +351,7 @@ def as_jsonapi(options = {})
343351
}
344352
}
345353

346-
expect(actual).to eq(expected)
354+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
347355
end
348356

349357
it 'renders a toplevel meta' do
@@ -354,7 +362,7 @@ def as_jsonapi(options = {})
354362
meta: { this: 'is_meta' }
355363
}
356364

357-
expect(actual).to eq(expected)
365+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
358366
end
359367

360368
it 'renders toplevel links' do
@@ -365,7 +373,7 @@ def as_jsonapi(options = {})
365373
links: { self: 'http://api.example.com/users' }
366374
}
367375

368-
expect(actual).to eq(expected)
376+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
369377
end
370378

371379
it 'renders a toplevel jsonapi object' do
@@ -382,12 +390,12 @@ def as_jsonapi(options = {})
382390
}
383391
}
384392

385-
expect(actual).to eq(expected)
393+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
386394
end
387395

388396
it 'renders an empty hash if neither errors nor data provided' do
389397
actual = JSONAPI.render({})
390-
expected = {}
398+
expected = ''
391399

392400
expect(actual).to eq(expected)
393401
end
@@ -412,6 +420,6 @@ def as_jsonapi
412420
{ id: '2', title: 'Works poorly' }]
413421
}
414422

415-
expect(actual).to eq(expected)
423+
expect(JSON.parse(actual)).to eq(JSON.parse(expected.to_json))
416424
end
417425
end

0 commit comments

Comments
 (0)