jsonapi-renderer 0.1.3 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +41 -3
- data/lib/jsonapi/include_directive.rb +15 -0
- data/lib/jsonapi/renderer/cached_resources_processor.rb +47 -0
- data/lib/jsonapi/renderer/document.rb +52 -10
- data/lib/jsonapi/renderer/resources_processor.rb +3 -11
- data/lib/jsonapi/renderer/simple_resources_processor.rb +19 -0
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 221e35b89366443f6a4e545e42b0026b824c0fc7915170354f5e2b4989cd6d76
|
4
|
+
data.tar.gz: 22912dac4be273f88d701e07fcc154de4a745962113a07e3bac3f075bec7fb8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbe2e5a3ec1c4b7c2fe25b04f386b8fc792c75faf1f924dba3e647f46962db8ab87d2a575f03622ab5dd3dba84528f0721423f11ad761344e9c5c1527da15716
|
7
|
+
data.tar.gz: efa85300205e568b7e4348bfe8f76d182be85948287170fee67f2f2d5220a9aa02a07dd64af7d370e8d921d7eaff05eb6d2e11ba79db453f7e250bcb14ce6415
|
data/README.md
CHANGED
@@ -48,7 +48,7 @@ class ResourceInterface
|
|
48
48
|
# @return [String]
|
49
49
|
def jsonapi_id; end
|
50
50
|
|
51
|
-
# Returns a hash containing, for each included relationship, an array of the
|
51
|
+
# Returns a hash containing, for each included relationship, an array of the
|
52
52
|
# resources to be included from that one.
|
53
53
|
# @param included_relationships [Array<Symbol>] The keys of the relationships
|
54
54
|
# to be included.
|
@@ -57,8 +57,8 @@ class ResourceInterface
|
|
57
57
|
|
58
58
|
# Returns a JSON API-compliant representation of the resource as a hash.
|
59
59
|
# @param options [Hash]
|
60
|
-
# @option fields [
|
61
|
-
# @option include [
|
60
|
+
# @option fields [Set<Symbol>, Nil] The requested fields, or nil.
|
61
|
+
# @option include [Set<Symbol>] The requested relationships to
|
62
62
|
# include (defaults to []).
|
63
63
|
# @return [Hash]
|
64
64
|
def as_jsonapi(options = {}); end
|
@@ -87,6 +87,18 @@ JSONAPI.render(data: resources,
|
|
87
87
|
|
88
88
|
This returns a JSON API compliant hash representing the described document.
|
89
89
|
|
90
|
+
#### Rendering a relationship
|
91
|
+
```ruby
|
92
|
+
JSONAPI.render(data: resource,
|
93
|
+
relationship: :posts,
|
94
|
+
include: include_string,
|
95
|
+
fields: fields_hash,
|
96
|
+
meta: meta_hash,
|
97
|
+
links: links_hash)
|
98
|
+
```
|
99
|
+
|
100
|
+
This returns a JSON API compliant hash representing the described document.
|
101
|
+
|
90
102
|
### Rendering errors
|
91
103
|
|
92
104
|
```ruby
|
@@ -100,6 +112,32 @@ returns a JSON API-compliant representation of the error.
|
|
100
112
|
|
101
113
|
This returns a JSON API compliant hash representing the described document.
|
102
114
|
|
115
|
+
### Caching
|
116
|
+
|
117
|
+
The generated JSON fragments can be cached in any cache implementation
|
118
|
+
supporting the `fetch_multi` method.
|
119
|
+
|
120
|
+
When using caching, the serializable resources must implement an
|
121
|
+
additional `jsonapi_cache_key` method:
|
122
|
+
```ruby
|
123
|
+
# Returns a cache key for the resource, parameterized by the `include` and
|
124
|
+
# `fields` options.
|
125
|
+
# @param options [Hash]
|
126
|
+
# @option fields [Set<Symbol>, Nil] The requested fields, or nil.
|
127
|
+
# @option include [Set<Symbol>] The requested relationships to
|
128
|
+
# include (defaults to []).
|
129
|
+
# @return [String]
|
130
|
+
def jsonapi_cache_key(options = {}); end
|
131
|
+
```
|
132
|
+
|
133
|
+
The cache instance must be passed to the renderer as follows:
|
134
|
+
```ruby
|
135
|
+
JSONAPI.render(data: resources,
|
136
|
+
include: include_string,
|
137
|
+
fields: fields_hash,
|
138
|
+
cache: cache_instance)
|
139
|
+
```
|
140
|
+
|
103
141
|
## License
|
104
142
|
|
105
143
|
jsonapi-renderer is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
@@ -16,6 +16,8 @@ module JSONAPI
|
|
16
16
|
def initialize(include_args, options = {})
|
17
17
|
include_hash = Parser.parse_include_args(include_args)
|
18
18
|
@hash = include_hash.each_with_object({}) do |(key, value), hash|
|
19
|
+
raise InvalidKey, key unless valid?(key)
|
20
|
+
|
19
21
|
hash[key] = self.class.new(value, options)
|
20
22
|
end
|
21
23
|
@options = options
|
@@ -68,5 +70,18 @@ module JSONAPI
|
|
68
70
|
|
69
71
|
string_array.join(',')
|
70
72
|
end
|
73
|
+
|
74
|
+
class InvalidKey < StandardError; end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def valid?(key)
|
79
|
+
key.match(valid_json_key_name_regex)
|
80
|
+
end
|
81
|
+
|
82
|
+
def valid_json_key_name_regex
|
83
|
+
# https://jsonapi.org/format/#document-member-names
|
84
|
+
/^(?![\s\-_])[\u0080-\u10FFA-Za-z0-9* _-]+(?<![\s\-_])$/
|
85
|
+
end
|
71
86
|
end
|
72
87
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'jsonapi/renderer/resources_processor'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
class Renderer
|
5
|
+
# @private
|
6
|
+
class CachedResourcesProcessor < ResourcesProcessor
|
7
|
+
class JSONString < String
|
8
|
+
def to_json(*)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(cache)
|
14
|
+
@cache = cache
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_resources
|
18
|
+
# NOTE(beauby): This is necessary for cache keys consistency.
|
19
|
+
@include_rels = @include_rels.each_with_object({}) do |(k, v), h|
|
20
|
+
h[k] = v.to_a.sort!
|
21
|
+
end
|
22
|
+
|
23
|
+
[@primary, @included].each do |resources|
|
24
|
+
cache_hash = cache_key_map(resources)
|
25
|
+
processed_resources = @cache.fetch_multi(*cache_hash.keys) do |key|
|
26
|
+
res, include, fields = cache_hash[key]
|
27
|
+
json = res.as_jsonapi(include: include, fields: fields).to_json
|
28
|
+
|
29
|
+
JSONString.new(json)
|
30
|
+
end
|
31
|
+
|
32
|
+
resources.replace(processed_resources.values)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cache_key_map(resources)
|
37
|
+
resources.each_with_object({}) do |res, h|
|
38
|
+
ri = [res.jsonapi_type, res.jsonapi_id]
|
39
|
+
include_dir = @include_rels[ri]
|
40
|
+
fields = @fields[ri.first.to_sym]
|
41
|
+
h[res.jsonapi_cache_key(include: include_dir, fields: fields)] =
|
42
|
+
[res, include_dir, fields]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
require 'jsonapi/include_directive'
|
2
|
-
require 'jsonapi/renderer/
|
2
|
+
require 'jsonapi/renderer/simple_resources_processor'
|
3
|
+
require 'jsonapi/renderer/cached_resources_processor'
|
3
4
|
|
4
5
|
module JSONAPI
|
5
6
|
class Renderer
|
7
|
+
# @private
|
6
8
|
class Document
|
7
9
|
def initialize(params = {})
|
8
10
|
@data = params.fetch(:data, :no_data)
|
9
11
|
@errors = params.fetch(:errors, [])
|
10
12
|
@meta = params[:meta]
|
11
13
|
@links = params[:links] || {}
|
12
|
-
@fields =
|
14
|
+
@fields = _canonize_fields(params[:fields] || {})
|
13
15
|
@jsonapi = params[:jsonapi]
|
14
16
|
@include = JSONAPI::IncludeDirective.new(params[:include] || {})
|
17
|
+
@relationship = params[:relationship]
|
18
|
+
@cache = params[:cache]
|
15
19
|
end
|
16
20
|
|
17
21
|
def to_hash
|
@@ -21,37 +25,75 @@ module JSONAPI
|
|
21
25
|
|
22
26
|
private
|
23
27
|
|
28
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
|
29
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
24
30
|
def document_hash
|
25
31
|
{}.tap do |hash|
|
26
|
-
if @
|
32
|
+
if @relationship
|
33
|
+
hash.merge!(relationship_hash)
|
34
|
+
elsif @data != :no_data
|
27
35
|
hash.merge!(data_hash)
|
28
36
|
elsif @errors.any?
|
29
37
|
hash.merge!(errors_hash)
|
30
38
|
end
|
31
|
-
hash[:links] = @links
|
32
|
-
hash[:meta] = @meta
|
33
|
-
hash[:jsonapi] = @jsonapi
|
39
|
+
hash[:links] = @links if @links.any?
|
40
|
+
hash[:meta] = @meta unless @meta.nil?
|
41
|
+
hash[:jsonapi] = @jsonapi unless @jsonapi.nil?
|
34
42
|
end
|
35
43
|
end
|
44
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/MethodLength
|
45
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
36
46
|
|
37
47
|
def data_hash
|
38
48
|
primary, included =
|
39
|
-
|
49
|
+
resources_processor.process(Array(@data), @include, @fields)
|
40
50
|
{}.tap do |hash|
|
41
51
|
hash[:data] = @data.respond_to?(:to_ary) ? primary : primary[0]
|
42
52
|
hash[:included] = included if included.any?
|
43
53
|
end
|
44
54
|
end
|
45
55
|
|
56
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
57
|
+
def relationship_hash
|
58
|
+
rel_name = @relationship.to_sym
|
59
|
+
data = @data.jsonapi_related([rel_name])[rel_name]
|
60
|
+
included =
|
61
|
+
if @include.key?(rel_name)
|
62
|
+
resources_processor.process(data, @include[rel_name], @fields)
|
63
|
+
.flatten!
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
|
68
|
+
res = @data.as_jsonapi(fields: [rel_name], include: [rel_name])
|
69
|
+
rel = res[:relationships][rel_name]
|
70
|
+
@links = rel[:links].merge!(@links)
|
71
|
+
@meta ||= rel[:meta]
|
72
|
+
|
73
|
+
{}.tap do |hash|
|
74
|
+
hash[:data] = rel[:data]
|
75
|
+
hash[:included] = included if included.any?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
79
|
+
|
46
80
|
def errors_hash
|
47
81
|
{}.tap do |hash|
|
48
|
-
hash[:errors] = @errors.
|
82
|
+
hash[:errors] = @errors.flat_map(&:as_jsonapi)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def resources_processor
|
87
|
+
if @cache
|
88
|
+
CachedResourcesProcessor.new(@cache)
|
89
|
+
else
|
90
|
+
SimpleResourcesProcessor.new
|
49
91
|
end
|
50
92
|
end
|
51
93
|
|
52
|
-
def
|
94
|
+
def _canonize_fields(fields)
|
53
95
|
fields.each_with_object({}) do |(k, v), h|
|
54
|
-
h[k.to_sym] = v.map(&:to_sym)
|
96
|
+
h[k.to_sym] = v.map(&:to_sym).sort!
|
55
97
|
end
|
56
98
|
end
|
57
99
|
end
|
@@ -2,14 +2,13 @@ require 'set'
|
|
2
2
|
|
3
3
|
module JSONAPI
|
4
4
|
class Renderer
|
5
|
+
# @private
|
5
6
|
class ResourcesProcessor
|
6
|
-
def
|
7
|
+
def process(resources, include, fields)
|
7
8
|
@resources = resources
|
8
9
|
@include = include
|
9
10
|
@fields = fields
|
10
|
-
end
|
11
11
|
|
12
|
-
def process
|
13
12
|
traverse_resources
|
14
13
|
process_resources
|
15
14
|
|
@@ -73,14 +72,7 @@ module JSONAPI
|
|
73
72
|
end
|
74
73
|
|
75
74
|
def process_resources
|
76
|
-
|
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
|
83
|
-
end
|
75
|
+
raise 'Not implemented'
|
84
76
|
end
|
85
77
|
end
|
86
78
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'jsonapi/renderer/resources_processor'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
class Renderer
|
5
|
+
# @api private
|
6
|
+
class SimpleResourcesProcessor < ResourcesProcessor
|
7
|
+
def process_resources
|
8
|
+
[@primary, @included].each do |resources|
|
9
|
+
resources.map! do |res|
|
10
|
+
ri = [res.jsonapi_type, res.jsonapi_id]
|
11
|
+
include_dir = @include_rels[ri]
|
12
|
+
fields = @fields[res.jsonapi_type.to_sym]
|
13
|
+
res.as_jsonapi(include: include_dir, fields: fields)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-renderer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Hosseini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '3.5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: simplecov
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0
|
54
|
+
version: '0'
|
55
55
|
description: Efficiently render JSON API documents.
|
56
56
|
email: lucas.hosseini@gmail.com
|
57
57
|
executables: []
|
@@ -62,8 +62,10 @@ files:
|
|
62
62
|
- lib/jsonapi/include_directive.rb
|
63
63
|
- lib/jsonapi/include_directive/parser.rb
|
64
64
|
- lib/jsonapi/renderer.rb
|
65
|
+
- lib/jsonapi/renderer/cached_resources_processor.rb
|
65
66
|
- lib/jsonapi/renderer/document.rb
|
66
67
|
- lib/jsonapi/renderer/resources_processor.rb
|
68
|
+
- lib/jsonapi/renderer/simple_resources_processor.rb
|
67
69
|
homepage: https://github.com/jsonapi-rb/jsonapi-renderer
|
68
70
|
licenses:
|
69
71
|
- MIT
|
@@ -84,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
86
|
version: '0'
|
85
87
|
requirements: []
|
86
88
|
rubyforge_project:
|
87
|
-
rubygems_version: 2.
|
89
|
+
rubygems_version: 2.7.8
|
88
90
|
signing_key:
|
89
91
|
specification_version: 4
|
90
92
|
summary: Render JSONAPI documents.
|