jat 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +21 -0
- data/jat.gemspec +37 -0
- data/lib/jat/attribute.rb +85 -0
- data/lib/jat/config.rb +38 -0
- data/lib/jat/plugins/_activerecord_preloads/_activerecord_preloads.rb +29 -0
- data/lib/jat/plugins/_activerecord_preloads/lib/preloader.rb +89 -0
- data/lib/jat/plugins/_json_api_activerecord/_json_api_activerecord.rb +22 -0
- data/lib/jat/plugins/_json_api_activerecord/lib/preloads.rb +84 -0
- data/lib/jat/plugins/_preloads/_preloads.rb +53 -0
- data/lib/jat/plugins/_preloads/lib/format_user_preloads.rb +52 -0
- data/lib/jat/plugins/_preloads/lib/preloads_with_path.rb +78 -0
- data/lib/jat/plugins/cache/cache.rb +39 -0
- data/lib/jat/plugins/camel_lower/camel_lower.rb +18 -0
- data/lib/jat/plugins/json_api/json_api.rb +207 -0
- data/lib/jat/plugins/json_api/lib/construct_traversal_map.rb +91 -0
- data/lib/jat/plugins/json_api/lib/map.rb +54 -0
- data/lib/jat/plugins/json_api/lib/params/fields/parse.rb +27 -0
- data/lib/jat/plugins/json_api/lib/params/fields/validate.rb +55 -0
- data/lib/jat/plugins/json_api/lib/params/fields.rb +23 -0
- data/lib/jat/plugins/json_api/lib/params/include/parse.rb +55 -0
- data/lib/jat/plugins/json_api/lib/params/include/validate.rb +29 -0
- data/lib/jat/plugins/json_api/lib/params/include.rb +49 -0
- data/lib/jat/plugins/json_api/lib/presenters/document_links_presenter.rb +48 -0
- data/lib/jat/plugins/json_api/lib/presenters/document_meta_presenter.rb +48 -0
- data/lib/jat/plugins/json_api/lib/presenters/jsonapi_presenter.rb +48 -0
- data/lib/jat/plugins/json_api/lib/presenters/links_presenter.rb +48 -0
- data/lib/jat/plugins/json_api/lib/presenters/meta_presenter.rb +48 -0
- data/lib/jat/plugins/json_api/lib/presenters/relationship_links_presenter.rb +53 -0
- data/lib/jat/plugins/json_api/lib/presenters/relationship_meta_presenter.rb +53 -0
- data/lib/jat/plugins/json_api/lib/response.rb +239 -0
- data/lib/jat/plugins/json_api/lib/traversal_map.rb +34 -0
- data/lib/jat/plugins/simple_api/lib/construct_traversal_map.rb +45 -0
- data/lib/jat/plugins/simple_api/lib/map.rb +29 -0
- data/lib/jat/plugins/simple_api/lib/params/parse.rb +68 -0
- data/lib/jat/plugins/simple_api/lib/response.rb +134 -0
- data/lib/jat/plugins/simple_api/simple_api.rb +65 -0
- data/lib/jat/plugins/to_str/to_str.rb +44 -0
- data/lib/jat/plugins.rb +39 -0
- data/lib/jat/presenter.rb +51 -0
- data/lib/jat/utils/enum_deep_dup.rb +29 -0
- data/lib/jat/utils/enum_deep_freeze.rb +19 -0
- data/lib/jat.rb +66 -144
- data/test/lib/jat/attribute_test.rb +142 -0
- data/test/lib/jat/config_test.rb +57 -0
- data/test/lib/jat/plugins/_activerecord_preloads/_activerecord_preloads_test.rb +40 -0
- data/test/lib/jat/plugins/_activerecord_preloads/lib/preloader_test.rb +98 -0
- data/test/lib/jat/plugins/_json_api_activerecord/_json_api_activerecord_test.rb +29 -0
- data/test/lib/jat/plugins/_json_api_activerecord/lib/preloads_test.rb +191 -0
- data/test/lib/jat/plugins/_preloads/_preloads_test.rb +68 -0
- data/test/lib/jat/plugins/_preloads/lib/format_user_preloads_test.rb +47 -0
- data/test/lib/jat/plugins/_preloads/lib/preloads_with_path_test.rb +33 -0
- data/test/lib/jat/plugins/cache/cache_test.rb +82 -0
- data/test/lib/jat/plugins/camel_lower/camel_lower_test.rb +78 -0
- data/test/lib/jat/plugins/json_api/json_api_test.rb +154 -0
- data/test/lib/jat/plugins/json_api/lib/construct_traversal_map_test.rb +119 -0
- data/test/lib/jat/plugins/json_api/lib/map_test.rb +117 -0
- data/test/lib/jat/plugins/json_api/lib/params/fields/parse_test.rb +24 -0
- data/test/lib/jat/plugins/json_api/lib/params/fields/validate_test.rb +47 -0
- data/test/lib/jat/plugins/json_api/lib/params/fields_test.rb +37 -0
- data/test/lib/jat/plugins/json_api/lib/params/include/parse_test.rb +46 -0
- data/test/lib/jat/plugins/json_api/lib/params/include/validate_test.rb +51 -0
- data/test/lib/jat/plugins/json_api/lib/params/include_test.rb +41 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/document_links_presenter_test.rb +69 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/document_meta_presenter_test.rb +69 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/jsonapi_presenter_test.rb +69 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/links_presenter_test.rb +69 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/meta_presenter_test.rb +69 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/relationship_links_presenter_test.rb +75 -0
- data/test/lib/jat/plugins/json_api/lib/presenters/relationship_meta_presenter_test.rb +75 -0
- data/test/lib/jat/plugins/json_api/lib/response_test.rb +489 -0
- data/test/lib/jat/plugins/json_api/lib/traversal_map_test.rb +58 -0
- data/test/lib/jat/plugins/simple_api/lib/construct_traversal_map_test.rb +100 -0
- data/test/lib/jat/plugins/simple_api/lib/map_test.rb +56 -0
- data/test/lib/jat/plugins/simple_api/lib/params/parse_test.rb +71 -0
- data/test/lib/jat/plugins/simple_api/lib/response_test.rb +342 -0
- data/test/lib/jat/plugins/simple_api/simple_api_test.rb +81 -0
- data/test/lib/jat/plugins/to_str/to_str_test.rb +52 -0
- data/test/lib/jat/presenter_test.rb +61 -0
- data/test/lib/jat/utils/enum_deep_dup_test.rb +31 -0
- data/test/lib/jat/utils/enum_deep_freeze_test.rb +28 -0
- data/test/lib/jat_test.rb +120 -0
- data/test/lib/plugin_test.rb +49 -0
- data/test/support/activerecord.rb +24 -0
- data/test/test_helper.rb +16 -0
- data/test/test_plugin.rb +59 -0
- metadata +240 -11
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./lib/response"
|
4
|
+
require_relative "./lib/traversal_map"
|
5
|
+
require_relative "./lib/presenters/document_links_presenter"
|
6
|
+
require_relative "./lib/presenters/document_meta_presenter"
|
7
|
+
require_relative "./lib/presenters/jsonapi_presenter"
|
8
|
+
require_relative "./lib/presenters/links_presenter"
|
9
|
+
require_relative "./lib/presenters/meta_presenter"
|
10
|
+
require_relative "./lib/presenters/relationship_links_presenter"
|
11
|
+
require_relative "./lib/presenters/relationship_meta_presenter"
|
12
|
+
|
13
|
+
# Serializes to JSON-API format
|
14
|
+
class Jat
|
15
|
+
module Plugins
|
16
|
+
module JsonApi
|
17
|
+
def self.after_load(jat_class, **opts)
|
18
|
+
jat_class.plugin(:_json_api_activerecord, **opts) if opts[:activerecord]
|
19
|
+
|
20
|
+
jat_class.const_set(:JsonapiPresenter, Class.new(Jat::Presenters::JsonapiPresenter))
|
21
|
+
jat_class::JsonapiPresenter.jat_class = jat_class
|
22
|
+
|
23
|
+
jat_class.const_set(:LinksPresenter, Class.new(Jat::Presenters::LinksPresenter))
|
24
|
+
jat_class::LinksPresenter.jat_class = jat_class
|
25
|
+
|
26
|
+
jat_class.const_set(:DocumentLinksPresenter, Class.new(Jat::Presenters::DocumentLinksPresenter))
|
27
|
+
jat_class::DocumentLinksPresenter.jat_class = jat_class
|
28
|
+
|
29
|
+
jat_class.const_set(:RelationshipLinksPresenter, Class.new(Jat::Presenters::RelationshipLinksPresenter))
|
30
|
+
jat_class::RelationshipLinksPresenter.jat_class = jat_class
|
31
|
+
|
32
|
+
jat_class.const_set(:MetaPresenter, Class.new(Jat::Presenters::MetaPresenter))
|
33
|
+
jat_class::MetaPresenter.jat_class = jat_class
|
34
|
+
|
35
|
+
jat_class.const_set(:DocumentMetaPresenter, Class.new(Jat::Presenters::DocumentMetaPresenter))
|
36
|
+
jat_class::DocumentMetaPresenter.jat_class = jat_class
|
37
|
+
|
38
|
+
jat_class.const_set(:RelationshipMetaPresenter, Class.new(Jat::Presenters::RelationshipMetaPresenter))
|
39
|
+
jat_class::RelationshipMetaPresenter.jat_class = jat_class
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
def to_h
|
44
|
+
Response.new(self).response
|
45
|
+
end
|
46
|
+
|
47
|
+
def traversal_map
|
48
|
+
@traversal_map ||= TraversalMap.new(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def inherited(subclass)
|
54
|
+
subclass.type(@type) if defined?(@type)
|
55
|
+
|
56
|
+
# Initialize JsonApi data Presenters
|
57
|
+
jsonapi_data_presenter_class = Class.new(self::Presenters::JsonapiPresenter)
|
58
|
+
jsonapi_data_presenter_class.jat_class = subclass
|
59
|
+
subclass.const_set(:JsonapiPresenter, jsonapi_data_presenter_class)
|
60
|
+
jsonapi_data.each { |key, block| subclass.jsonapi(key, &block) }
|
61
|
+
|
62
|
+
# Initialize Links Presenters
|
63
|
+
links_presenter_class = Class.new(self::Presenters::LinksPresenter)
|
64
|
+
links_presenter_class.jat_class = subclass
|
65
|
+
subclass.const_set(:LinksPresenter, links_presenter_class)
|
66
|
+
object_links.each { |key, block| subclass.object_link(key, &block) }
|
67
|
+
|
68
|
+
# Initialize DocumentLinks Presenters
|
69
|
+
document_links_presenter_class = Class.new(self::Presenters::DocumentLinksPresenter)
|
70
|
+
document_links_presenter_class.jat_class = subclass
|
71
|
+
subclass.const_set(:DocumentLinksPresenter, document_links_presenter_class)
|
72
|
+
document_links.each { |key, block| subclass.document_link(key, &block) }
|
73
|
+
|
74
|
+
# Initialize RelationshipLinks Presenters
|
75
|
+
relationship_links_presenter_class = Class.new(self::Presenters::RelationshipLinksPresenter)
|
76
|
+
relationship_links_presenter_class.jat_class = subclass
|
77
|
+
subclass.const_set(:RelationshipLinksPresenter, relationship_links_presenter_class)
|
78
|
+
relationship_links.each { |key, block| subclass.relationship_link(key, &block) }
|
79
|
+
|
80
|
+
# Initialize Meta Presenters
|
81
|
+
meta_presenter_class = Class.new(self::Presenters::MetaPresenter)
|
82
|
+
meta_presenter_class.jat_class = subclass
|
83
|
+
subclass.const_set(:MetaPresenter, meta_presenter_class)
|
84
|
+
added_object_meta.each { |key, block| subclass.object_meta(key, &block) }
|
85
|
+
|
86
|
+
# Initialize DocumentMeta Presenters
|
87
|
+
document_meta_presenter_class = Class.new(self::Presenters::DocumentMetaPresenter)
|
88
|
+
document_meta_presenter_class.jat_class = subclass
|
89
|
+
subclass.const_set(:DocumentMetaPresenter, document_meta_presenter_class)
|
90
|
+
added_document_meta.each { |key, block| subclass.document_meta(key, &block) }
|
91
|
+
|
92
|
+
# Initialize RelationshipMeta Presenters
|
93
|
+
relationship_meta_presenter_class = Class.new(self::Presenters::RelationshipMetaPresenter)
|
94
|
+
relationship_meta_presenter_class.jat_class = subclass
|
95
|
+
subclass.const_set(:RelationshipMetaPresenter, relationship_meta_presenter_class)
|
96
|
+
added_relationship_meta.each { |key, block| subclass.relationship_meta(key, &block) }
|
97
|
+
|
98
|
+
super
|
99
|
+
end
|
100
|
+
|
101
|
+
def type(new_type = nil)
|
102
|
+
return (defined?(@type) && @type) || raise(Error, "#{self} has no defined type") unless new_type
|
103
|
+
|
104
|
+
new_type = new_type.to_sym
|
105
|
+
@type = new_type
|
106
|
+
end
|
107
|
+
|
108
|
+
def relationship(name, serializer:, **opts, &block)
|
109
|
+
attribute(name, serializer: serializer, **opts, &block)
|
110
|
+
end
|
111
|
+
|
112
|
+
# JSON API block values
|
113
|
+
#
|
114
|
+
# https://jsonapi.org/format/#document-jsonapi-object
|
115
|
+
def jsonapi_data(value = nil)
|
116
|
+
@jsonapi_data ||= {}
|
117
|
+
end
|
118
|
+
|
119
|
+
def jsonapi(key, &block)
|
120
|
+
jsonapi_data[key.to_sym] = block
|
121
|
+
self::JsonapiPresenter.add_method(key, block)
|
122
|
+
block
|
123
|
+
end
|
124
|
+
|
125
|
+
# Links related to the resource
|
126
|
+
#
|
127
|
+
# https://jsonapi.org/format/#document-resource-object-links
|
128
|
+
def object_links
|
129
|
+
@object_links ||= {}
|
130
|
+
end
|
131
|
+
|
132
|
+
def object_link(key, &block)
|
133
|
+
object_links[key.to_sym] = block
|
134
|
+
self::LinksPresenter.add_method(key, block)
|
135
|
+
block
|
136
|
+
end
|
137
|
+
|
138
|
+
# Top-level document links
|
139
|
+
#
|
140
|
+
# https://jsonapi.org/format/#document-top-level
|
141
|
+
def document_links
|
142
|
+
@document_links ||= {}
|
143
|
+
end
|
144
|
+
|
145
|
+
def document_link(key, &block)
|
146
|
+
document_links[key.to_sym] = block
|
147
|
+
self::DocumentLinksPresenter.add_method(key, block)
|
148
|
+
block
|
149
|
+
end
|
150
|
+
|
151
|
+
# Relationship links
|
152
|
+
#
|
153
|
+
# https://jsonapi.org/format/#document-resource-object-linkage
|
154
|
+
def relationship_links
|
155
|
+
@relationship_links ||= {}
|
156
|
+
end
|
157
|
+
|
158
|
+
def relationship_link(key, &block)
|
159
|
+
relationship_links[key.to_sym] = block
|
160
|
+
self::RelationshipLinksPresenter.add_method(key, block)
|
161
|
+
block
|
162
|
+
end
|
163
|
+
|
164
|
+
# Object meta
|
165
|
+
#
|
166
|
+
# https://jsonapi.org/format/#document-resource-objects
|
167
|
+
def added_object_meta
|
168
|
+
@added_object_meta ||= {}
|
169
|
+
end
|
170
|
+
|
171
|
+
def object_meta(key, &block)
|
172
|
+
added_object_meta[key.to_sym] = block
|
173
|
+
self::MetaPresenter.add_method(key, block)
|
174
|
+
block
|
175
|
+
end
|
176
|
+
|
177
|
+
# Top-level document meta
|
178
|
+
#
|
179
|
+
# https://jsonapi.org/format/#document-meta
|
180
|
+
def added_document_meta
|
181
|
+
@added_document_meta ||= {}
|
182
|
+
end
|
183
|
+
|
184
|
+
def document_meta(key, &block)
|
185
|
+
added_document_meta[key.to_sym] = block
|
186
|
+
self::DocumentMetaPresenter.add_method(key, block)
|
187
|
+
block
|
188
|
+
end
|
189
|
+
|
190
|
+
# Relationship meta
|
191
|
+
#
|
192
|
+
# https://jsonapi.org/format/#document-resource-object-relationships
|
193
|
+
def added_relationship_meta
|
194
|
+
@added_relationship_meta ||= {}
|
195
|
+
end
|
196
|
+
|
197
|
+
def relationship_meta(key, &block)
|
198
|
+
added_relationship_meta[key.to_sym] = block
|
199
|
+
self::RelationshipMetaPresenter.add_method(key, block)
|
200
|
+
block
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
register_plugin(:json_api, JsonApi)
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Combines all serializer and its realtions exposed fields into one hash.
|
5
|
+
# Returns Hash
|
6
|
+
# {
|
7
|
+
# type1 => {
|
8
|
+
# serializer: ser1,
|
9
|
+
# attributes: [attr1, attr2, ...],
|
10
|
+
# relationships: [attr1, attr2, ...]
|
11
|
+
# },
|
12
|
+
#
|
13
|
+
class Jat
|
14
|
+
module Plugins
|
15
|
+
module JsonApi
|
16
|
+
class ConstructTraversalMap
|
17
|
+
attr_reader :jat_class, :result, :exposed, :manually_exposed
|
18
|
+
|
19
|
+
EXPOSED_VALUES = {all: :all, exposed: :exposed, manual: :manual}.freeze
|
20
|
+
|
21
|
+
def initialize(jat_class, exposed, manually_exposed: {})
|
22
|
+
@jat_class = jat_class
|
23
|
+
@exposed = EXPOSED_VALUES.fetch(exposed)
|
24
|
+
@manually_exposed = manually_exposed
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
@result = {}
|
29
|
+
append(jat_class)
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def append(jat_class)
|
36
|
+
type = jat_class.type
|
37
|
+
return result if result.key?(type)
|
38
|
+
|
39
|
+
result[type] = {
|
40
|
+
serializer: jat_class,
|
41
|
+
attributes: [],
|
42
|
+
relationships: []
|
43
|
+
}
|
44
|
+
|
45
|
+
fill(jat_class)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fill(jat_class)
|
49
|
+
type = jat_class.type
|
50
|
+
type_result = result[type]
|
51
|
+
|
52
|
+
jat_class.attributes.each_value do |attribute|
|
53
|
+
next unless expose?(type, attribute)
|
54
|
+
|
55
|
+
fill_attr(type_result, attribute)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def fill_attr(type_result, attribute)
|
60
|
+
name = attribute.name
|
61
|
+
|
62
|
+
if attribute.relation?
|
63
|
+
type_result[:relationships] << name
|
64
|
+
append(attribute.serializer.call)
|
65
|
+
else
|
66
|
+
type_result[:attributes] << name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def expose?(type, attribute)
|
71
|
+
return false if attribute.name == :id
|
72
|
+
|
73
|
+
case exposed
|
74
|
+
when :all then true
|
75
|
+
when :manual then manually_exposed?(type, attribute)
|
76
|
+
else attribute.exposed? || manually_exposed?(type, attribute)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return `attribute.exposed?` when type is not provided
|
81
|
+
# Checks type is in exposed attributes
|
82
|
+
def manually_exposed?(type, attribute)
|
83
|
+
return attribute.exposed? unless manually_exposed.key?(type)
|
84
|
+
|
85
|
+
exposed_attrs = manually_exposed[type]
|
86
|
+
exposed_attrs.include?(attribute.name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./params/fields"
|
4
|
+
require_relative "./params/include"
|
5
|
+
require_relative "./construct_traversal_map"
|
6
|
+
|
7
|
+
class Jat
|
8
|
+
module Plugins
|
9
|
+
module JsonApi
|
10
|
+
class Map
|
11
|
+
class << self
|
12
|
+
# Returns structure like
|
13
|
+
# {
|
14
|
+
# type => {
|
15
|
+
# attributes: [attr1, attr2, ...],
|
16
|
+
# relationships: [rel1, rel2, ...]
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
def call(jat)
|
20
|
+
params = jat.context[:params]
|
21
|
+
fields = params && (params[:fields] || params["fields"])
|
22
|
+
includes = params && (params[:include] || params["include"])
|
23
|
+
|
24
|
+
default_attrs = jat.traversal_map.exposed
|
25
|
+
includes_attrs = requested_includes_fields(jat, includes)
|
26
|
+
fields_attrs = requested_fields(jat, fields)
|
27
|
+
|
28
|
+
default_attrs.merge!(includes_attrs).merge!(fields_attrs)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def requested_includes_fields(jat, includes)
|
34
|
+
return {} unless includes
|
35
|
+
|
36
|
+
include_types = Params::Include.call(jat, includes)
|
37
|
+
ConstructTraversalMap
|
38
|
+
.new(jat.class, :exposed, manually_exposed: include_types)
|
39
|
+
.to_h
|
40
|
+
end
|
41
|
+
|
42
|
+
def requested_fields(jat, fields)
|
43
|
+
return {} unless fields
|
44
|
+
|
45
|
+
fields_types = Params::Fields.call(jat, fields)
|
46
|
+
ConstructTraversalMap
|
47
|
+
.new(jat.class, :manual, manually_exposed: fields_types)
|
48
|
+
.to_h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Plugins
|
5
|
+
module JsonApi
|
6
|
+
module Params
|
7
|
+
class Fields
|
8
|
+
class Parse
|
9
|
+
COMMA = ","
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# returns Hash { type => [attr1, attr2] }
|
13
|
+
def call(fields)
|
14
|
+
return {} unless fields
|
15
|
+
|
16
|
+
fields.each_with_object({}) do |(type, attrs_string), obj|
|
17
|
+
attrs = attrs_string.split(COMMA).map!(&:to_sym)
|
18
|
+
obj[type.to_sym] = attrs
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Plugins
|
5
|
+
module JsonApi
|
6
|
+
module Params
|
7
|
+
class Fields
|
8
|
+
class Validate
|
9
|
+
class << self
|
10
|
+
def call(jat, fields)
|
11
|
+
fields.each do |type, attributes_names|
|
12
|
+
new(jat, type).validate(attributes_names)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :jat, :type
|
18
|
+
|
19
|
+
def initialize(jat, type)
|
20
|
+
@jat = jat
|
21
|
+
@type = type
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate(attributes_names)
|
25
|
+
check_fields_type
|
26
|
+
check_attributes_names(attributes_names)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check_fields_type
|
32
|
+
return if jat.traversal_map.full.key?(type)
|
33
|
+
|
34
|
+
raise Error, "#{jat.class} and its children have no requested type `#{type}`"
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_attributes_names(attributes_names)
|
38
|
+
attributes_names.each do |attribute_name|
|
39
|
+
check_attribute_name(attribute_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_attribute_name(attribute_name)
|
44
|
+
type_data = jat.traversal_map.full.fetch(type)
|
45
|
+
type_serializer = type_data.fetch(:serializer)
|
46
|
+
return if type_serializer.attributes.key?(attribute_name)
|
47
|
+
|
48
|
+
raise Error, "#{type_serializer} has no requested attribute or relationship `#{attribute_name}`"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./fields/parse"
|
4
|
+
require_relative "./fields/validate"
|
5
|
+
|
6
|
+
class Jat
|
7
|
+
module Plugins
|
8
|
+
module JsonApi
|
9
|
+
module Params
|
10
|
+
class Fields
|
11
|
+
class << self
|
12
|
+
# returns Hash { type => [key1, key2] }
|
13
|
+
def call(jat, fields)
|
14
|
+
parsed_fields = Parse.call(fields)
|
15
|
+
Validate.call(jat, parsed_fields)
|
16
|
+
parsed_fields
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Plugins
|
5
|
+
module JsonApi
|
6
|
+
module Params
|
7
|
+
class Include
|
8
|
+
class Parse
|
9
|
+
COMMA = ","
|
10
|
+
DOT = "."
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def call(includes_string)
|
14
|
+
return {} unless includes_string
|
15
|
+
|
16
|
+
string_to_hash(includes_string)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def string_to_hash(includes_string)
|
22
|
+
includes_string.split(COMMA).each_with_object({}) do |part, obj|
|
23
|
+
includes = parse_part(part)
|
24
|
+
deep_merge!(obj, includes)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_part(part)
|
29
|
+
val = {}
|
30
|
+
|
31
|
+
part.split(DOT).reverse_each do |inc|
|
32
|
+
val = {inc.to_sym => val}
|
33
|
+
end
|
34
|
+
|
35
|
+
val
|
36
|
+
end
|
37
|
+
|
38
|
+
def deep_merge!(this_hash, other_hash)
|
39
|
+
this_hash.merge!(other_hash) do |_key, this_val, other_val|
|
40
|
+
deep_merge(this_val, other_val)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def deep_merge(this_hash, other_hash)
|
45
|
+
this_hash.merge(other_hash) do |_key, this_val, other_val|
|
46
|
+
deep_merge(this_val, other_val)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Plugins
|
5
|
+
module JsonApi
|
6
|
+
module Params
|
7
|
+
class Include
|
8
|
+
class Validate
|
9
|
+
class << self
|
10
|
+
def call(jat_class, includes)
|
11
|
+
includes.each do |name, nested_includes|
|
12
|
+
attribute = jat_class.attributes[name]
|
13
|
+
raise_error(jat_class, name) if !attribute || !attribute.relation?
|
14
|
+
|
15
|
+
nested_serializer = attribute.serializer.call
|
16
|
+
call(nested_serializer, nested_includes)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def raise_error(jat_class, name)
|
21
|
+
raise Error, "#{jat_class} has no `#{name}` relationship"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./include/parse"
|
4
|
+
require_relative "./include/validate"
|
5
|
+
|
6
|
+
class Jat
|
7
|
+
module Plugins
|
8
|
+
module JsonApi
|
9
|
+
module Params
|
10
|
+
class Include
|
11
|
+
class << self
|
12
|
+
# returns Hash { type => [attr1, attr2] }
|
13
|
+
def call(jat, includes_string)
|
14
|
+
return {} unless includes_string
|
15
|
+
|
16
|
+
jat_class = jat.class
|
17
|
+
includes = Parse.call(includes_string)
|
18
|
+
Validate.call(jat_class, includes)
|
19
|
+
|
20
|
+
typed_includes(jat_class, includes, {})
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def typed_includes(jat_class, includes, result)
|
26
|
+
includes.each do |included_attr_name, nested_includes|
|
27
|
+
add_include(result, jat_class, included_attr_name)
|
28
|
+
|
29
|
+
nested_serializer = jat_class.attributes.fetch(included_attr_name).serializer.call
|
30
|
+
typed_includes(nested_serializer, nested_includes, result)
|
31
|
+
end
|
32
|
+
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_include(result, serializer, included_attr_name)
|
37
|
+
type = serializer.type
|
38
|
+
|
39
|
+
includes = result[type] || []
|
40
|
+
includes |= [included_attr_name]
|
41
|
+
|
42
|
+
result[type] = includes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Presenters
|
5
|
+
class DocumentLinksPresenter
|
6
|
+
module InstanceMethods
|
7
|
+
# Presented object
|
8
|
+
attr_reader :object
|
9
|
+
|
10
|
+
# Presented context
|
11
|
+
attr_reader :context
|
12
|
+
|
13
|
+
def initialize(object, context)
|
14
|
+
@object = object
|
15
|
+
@context = context
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
attr_accessor :jat_class
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#{jat_class.inspect}::Presenters::DocumentLinksPresenter"
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_method(name, block)
|
27
|
+
# Warning-free method redefinition
|
28
|
+
remove_method(name) if method_defined?(name, false)
|
29
|
+
define_method(name, &block_without_args(block))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def block_without_args(block)
|
35
|
+
case block.parameters.count
|
36
|
+
when 0 then block
|
37
|
+
when 1 then -> { instance_exec(object, &block) }
|
38
|
+
when 2 then -> { instance_exec(object, context, &block) }
|
39
|
+
else raise Error, "Invalid block arguments count"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
extend ClassMethods
|
45
|
+
include InstanceMethods
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Jat
|
4
|
+
module Presenters
|
5
|
+
class DocumentMetaPresenter
|
6
|
+
module InstanceMethods
|
7
|
+
# Presented object
|
8
|
+
attr_reader :object
|
9
|
+
|
10
|
+
# Presented context
|
11
|
+
attr_reader :context
|
12
|
+
|
13
|
+
def initialize(object, context)
|
14
|
+
@object = object
|
15
|
+
@context = context
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
attr_accessor :jat_class
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#{jat_class.inspect}::Presenters::DocumentMetaPresenter"
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_method(name, block)
|
27
|
+
# Warning-free method redefinition
|
28
|
+
remove_method(name) if method_defined?(name, false)
|
29
|
+
define_method(name, &block_without_args(block))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def block_without_args(block)
|
35
|
+
case block.parameters.count
|
36
|
+
when 0 then block
|
37
|
+
when 1 then -> { instance_exec(object, &block) }
|
38
|
+
when 2 then -> { instance_exec(object, context, &block) }
|
39
|
+
else raise Error, "Invalid block arguments count"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
extend ClassMethods
|
45
|
+
include InstanceMethods
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|