jat 0.0.1 → 0.0.3
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.
- 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
|