graphiti 1.2.31 → 1.3.4
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 +3 -0
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/lib/graphiti/adapters/abstract.rb +3 -2
- data/lib/graphiti/adapters/active_record.rb +7 -3
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/null.rb +1 -1
- data/lib/graphiti/adapters/persistence/associations.rb +10 -2
- data/lib/graphiti/configuration.rb +3 -1
- data/lib/graphiti/delegates/pagination.rb +33 -10
- data/lib/graphiti/errors.rb +71 -11
- data/lib/graphiti/extensions/extra_attribute.rb +3 -3
- data/lib/graphiti/hash_renderer.rb +194 -21
- data/lib/graphiti/query.rb +32 -6
- data/lib/graphiti/renderer.rb +19 -1
- data/lib/graphiti/request_validators/update_validator.rb +3 -3
- data/lib/graphiti/request_validators/validator.rb +29 -21
- data/lib/graphiti/resource/configuration.rb +22 -1
- data/lib/graphiti/resource/dsl.rb +20 -2
- data/lib/graphiti/resource/interface.rb +1 -1
- data/lib/graphiti/resource/polymorphism.rb +6 -1
- data/lib/graphiti/resource/remote.rb +1 -1
- data/lib/graphiti/resource.rb +2 -1
- data/lib/graphiti/resource_proxy.rb +12 -0
- data/lib/graphiti/schema.rb +27 -4
- data/lib/graphiti/schema_diff.rb +44 -4
- data/lib/graphiti/scope.rb +2 -2
- data/lib/graphiti/scoping/filter.rb +19 -8
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/serializer.rb +41 -6
- data/lib/graphiti/sideload.rb +16 -1
- data/lib/graphiti/util/class.rb +6 -0
- data/lib/graphiti/util/link.rb +4 -0
- data/lib/graphiti/util/remote_params.rb +9 -4
- data/lib/graphiti/util/remote_serializer.rb +1 -0
- data/lib/graphiti/util/serializer_attributes.rb +37 -10
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +2 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbdb96a78aeac1f48bcea53a3b068638456e703b17fccf68d2f3813270db4bd8
|
4
|
+
data.tar.gz: c1dab2eff065e8ba70193cac81c6c5c44c57f81f9074082887ed7b52f2bcb000
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a860a2d6cc8bfc494207ddd6bac1643c58ba09148fdf85b07c9198840728ace0f752fefb94bafa6599a97eb5ccfa6408544b08312aca5c6e01d77c874141821
|
7
|
+
data.tar.gz: 4a821372147d76d03a6d3e4dc5dce6ca205464d852892484567f725355459b0d5477418340ed9d53831a0062218e7b9daaf3560a0951c2decd8fd177a499954b
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
3
|
Features:
|
4
|
+
- [329](https://github.com/graphiti-api/graphiti/pull/329) Propagate `extra_fields` to related resource links.
|
4
5
|
- [242](https://github.com/graphiti-api/graphiti/pull/242) Bump `jsonapi-renderer` to `~0.2.2` now that (https://github.com/jsonapi-rb/jsonapi-renderer/pull/36) is fixed.
|
5
6
|
- [158](https://github.com/graphiti-api/graphiti/pull/158) Filters options `allow_nil: true`
|
6
7
|
Option can be set at the resource level `Resource.filters_accept_nil_by_default = true`.
|
@@ -10,6 +11,8 @@ Features:
|
|
10
11
|
|
11
12
|
Fixes:
|
12
13
|
- [282] Support model names including "Resource"
|
14
|
+
- [313](https://github.com/graphiti-api/graphiti/pull/313) Sort remote resources in schema generation
|
15
|
+
- [374](https://github.com/graphiti-api/graphiti/pull/374) Trim leading spaces from error messages
|
13
16
|
|
14
17
|
## 1.1.0
|
15
18
|
|
@@ -244,14 +244,15 @@ module Graphiti
|
|
244
244
|
# @param scope The scope object we are chaining
|
245
245
|
# @param [Integer] current_page The current page number
|
246
246
|
# @param [Integer] per_page The number of results per page
|
247
|
+
# @param [Integer] offset The offset to start from
|
247
248
|
# @return the scope
|
248
249
|
#
|
249
250
|
# @example ActiveRecord default
|
250
251
|
# # via kaminari gem
|
251
|
-
# def paginate(scope, current_page, per_page)
|
252
|
+
# def paginate(scope, current_page, per_page, offset)
|
252
253
|
# scope.page(current_page).per(per_page)
|
253
254
|
# end
|
254
|
-
def paginate(scope, current_page, per_page)
|
255
|
+
def paginate(scope, current_page, per_page, offset)
|
255
256
|
raise "you must override #paginate in an adapter subclass"
|
256
257
|
end
|
257
258
|
|
@@ -184,8 +184,11 @@ module Graphiti
|
|
184
184
|
end
|
185
185
|
|
186
186
|
# (see Adapters::Abstract#paginate)
|
187
|
-
def paginate(scope, current_page, per_page)
|
188
|
-
scope.page(current_page)
|
187
|
+
def paginate(scope, current_page, per_page, offset)
|
188
|
+
scope = scope.page(current_page) if current_page
|
189
|
+
scope = scope.per(per_page) if per_page
|
190
|
+
scope = scope.padding(offset) if offset
|
191
|
+
scope
|
189
192
|
end
|
190
193
|
|
191
194
|
# (see Adapters::Abstract#count)
|
@@ -240,7 +243,8 @@ module Graphiti
|
|
240
243
|
children.each do |child|
|
241
244
|
if association_type == :many_to_many &&
|
242
245
|
[:create, :update].include?(Graphiti.context[:namespace]) &&
|
243
|
-
!parent.send(association_name).exists?(child.id)
|
246
|
+
!parent.send(association_name).exists?(child.id) &&
|
247
|
+
child.errors.blank?
|
244
248
|
parent.send(association_name) << child
|
245
249
|
else
|
246
250
|
target = association.instance_variable_get(:@target)
|
@@ -34,7 +34,7 @@ module Graphiti
|
|
34
34
|
|
35
35
|
def build_url(scope)
|
36
36
|
url = resource.remote_url
|
37
|
-
params = scope[:params].merge(scope.except(:params))
|
37
|
+
params = scope[:params].merge(scope.except(:params, :foreign_key))
|
38
38
|
params[:page] ||= {}
|
39
39
|
params[:page][:size] ||= 999
|
40
40
|
params = CGI.unescape(params.to_query)
|
@@ -11,8 +11,16 @@ module Graphiti
|
|
11
11
|
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
|
12
12
|
processed << x
|
13
13
|
rescue Graphiti::Errors::RecordNotFound
|
14
|
-
|
15
|
-
|
14
|
+
if Graphiti.config.raise_on_missing_sidepost
|
15
|
+
path = "relationships/#{x.dig(:meta, :jsonapi_type)}"
|
16
|
+
raise Graphiti::Errors::RecordNotFound.new(x[:sideload].name, id, path)
|
17
|
+
else
|
18
|
+
pointer = "data/relationships/#{x.dig(:meta, :jsonapi_type)}"
|
19
|
+
object = Graphiti::Errors::NullRelation.new(id.to_s, pointer)
|
20
|
+
object.errors.add(:base, :not_found, message: "could not be found")
|
21
|
+
x[:object] = object
|
22
|
+
processed << x
|
23
|
+
end
|
16
24
|
end
|
17
25
|
end
|
18
26
|
end
|
@@ -14,6 +14,7 @@ module Graphiti
|
|
14
14
|
attr_accessor :pagination_links_on_demand
|
15
15
|
attr_accessor :pagination_links
|
16
16
|
attr_accessor :typecast_reads
|
17
|
+
attr_accessor :raise_on_missing_sidepost
|
17
18
|
|
18
19
|
attr_reader :debug, :debug_models
|
19
20
|
|
@@ -29,6 +30,7 @@ module Graphiti
|
|
29
30
|
@pagination_links_on_demand = false
|
30
31
|
@pagination_links = false
|
31
32
|
@typecast_reads = true
|
33
|
+
@raise_on_missing_sidepost = true
|
32
34
|
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
|
33
35
|
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
|
34
36
|
|
@@ -43,7 +45,7 @@ module Graphiti
|
|
43
45
|
end
|
44
46
|
|
45
47
|
if (logger = ::Rails.logger)
|
46
|
-
self.debug = logger.
|
48
|
+
self.debug = logger.debug? && debug
|
47
49
|
Graphiti.logger = logger
|
48
50
|
end
|
49
51
|
end
|
@@ -11,13 +11,24 @@ module Graphiti
|
|
11
11
|
|
12
12
|
def links
|
13
13
|
@links ||= {}.tap do |links|
|
14
|
+
links[:self] = pagination_link(current_page)
|
14
15
|
links[:first] = pagination_link(1)
|
15
16
|
links[:last] = pagination_link(last_page)
|
16
|
-
links[:prev] = pagination_link(current_page - 1)
|
17
|
-
links[:next] = pagination_link(current_page + 1)
|
17
|
+
links[:prev] = pagination_link(current_page - 1) if has_previous_page?
|
18
|
+
links[:next] = pagination_link(current_page + 1) if has_next_page?
|
18
19
|
end.select { |k, v| !v.nil? }
|
19
20
|
end
|
20
21
|
|
22
|
+
def has_next_page?
|
23
|
+
current_page != last_page && last_page.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_previous_page?
|
27
|
+
current_page != 1 ||
|
28
|
+
!!pagination_params.try(:[], :page).try(:[], :after) ||
|
29
|
+
!!pagination_params.try(:[], :page).try(:[], :offset)
|
30
|
+
end
|
31
|
+
|
21
32
|
private
|
22
33
|
|
23
34
|
def pagination_params
|
@@ -29,13 +40,14 @@ module Graphiti
|
|
29
40
|
|
30
41
|
uri = URI(@proxy.resource.endpoint[:url].to_s)
|
31
42
|
|
43
|
+
page_params = {
|
44
|
+
number: page,
|
45
|
+
size: page_size
|
46
|
+
}
|
47
|
+
page_params[:offset] = offset if offset
|
48
|
+
|
32
49
|
# Overwrite the pagination query params with the desired page
|
33
|
-
uri.query = pagination_params.merge(
|
34
|
-
page: {
|
35
|
-
number: page,
|
36
|
-
size: page_size
|
37
|
-
}
|
38
|
-
}).to_query
|
50
|
+
uri.query = pagination_params.merge(page: page_params).to_query
|
39
51
|
uri.to_s
|
40
52
|
end
|
41
53
|
|
@@ -45,8 +57,11 @@ module Graphiti
|
|
45
57
|
elsif page_size == 0 || item_count == 0
|
46
58
|
return nil
|
47
59
|
end
|
48
|
-
|
49
|
-
|
60
|
+
|
61
|
+
count = item_count
|
62
|
+
count = item_count - offset if offset
|
63
|
+
@last_page = (count / page_size)
|
64
|
+
@last_page += 1 if count % page_size > 0
|
50
65
|
@last_page
|
51
66
|
end
|
52
67
|
|
@@ -81,6 +96,14 @@ module Graphiti
|
|
81
96
|
@current_page ||= (page_param[:number] || 1).to_i
|
82
97
|
end
|
83
98
|
|
99
|
+
def offset
|
100
|
+
@offset ||= begin
|
101
|
+
if (value = page_param[:offset])
|
102
|
+
value.to_i
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
84
107
|
def page_size
|
85
108
|
@page_size ||= (page_param[:size] ||
|
86
109
|
@proxy.resource.default_page_size ||
|
data/lib/graphiti/errors.rb
CHANGED
@@ -2,6 +2,31 @@ module Graphiti
|
|
2
2
|
module Errors
|
3
3
|
class Base < StandardError; end
|
4
4
|
|
5
|
+
class UnreadableAttribute < Base
|
6
|
+
def initialize(resource_class, name)
|
7
|
+
@resource_class = resource_class
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{@resource_class}: Requested field #{@name}, but not authorized to read it"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NullRelation
|
17
|
+
attr_accessor :id, :errors, :pointer
|
18
|
+
|
19
|
+
def initialize(id, pointer)
|
20
|
+
@id = id
|
21
|
+
@pointer = pointer
|
22
|
+
@errors = Graphiti::Util::SimpleErrors.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.human_attribute_name(attr, options = {})
|
26
|
+
attr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
5
30
|
class AdapterNotImplemented < Base
|
6
31
|
def initialize(adapter, attribute, method)
|
7
32
|
@adapter = adapter
|
@@ -10,7 +35,7 @@ module Graphiti
|
|
10
35
|
end
|
11
36
|
|
12
37
|
def message
|
13
|
-
|
38
|
+
<<~MSG
|
14
39
|
The adapter #{@adapter.class} does not implement method '#{@method}', which was requested for attribute '#{@attribute}'. Add this method to your adapter to support this filter operator.
|
15
40
|
MSG
|
16
41
|
end
|
@@ -24,7 +49,7 @@ module Graphiti
|
|
24
49
|
end
|
25
50
|
|
26
51
|
def message
|
27
|
-
|
52
|
+
<<~MSG
|
28
53
|
#{@parent_resource_class} sideload :#{@name} - #{@message}
|
29
54
|
MSG
|
30
55
|
end
|
@@ -53,7 +78,7 @@ module Graphiti
|
|
53
78
|
end
|
54
79
|
|
55
80
|
def message
|
56
|
-
|
81
|
+
<<~MSG
|
57
82
|
#{@resource_class}: Tried to pass block to .#{@method_name}, which only accepts a method name.
|
58
83
|
MSG
|
59
84
|
end
|
@@ -65,7 +90,7 @@ module Graphiti
|
|
65
90
|
end
|
66
91
|
|
67
92
|
def message
|
68
|
-
|
93
|
+
<<~MSG
|
69
94
|
#{@resource_class}: Tried to perform write operation. Writes are not supported for remote resources - hit the endpoint directly.
|
70
95
|
MSG
|
71
96
|
end
|
@@ -80,7 +105,7 @@ module Graphiti
|
|
80
105
|
end
|
81
106
|
|
82
107
|
def message
|
83
|
-
|
108
|
+
<<~MSG
|
84
109
|
#{@resource.class}: Tried to filter #{@filter_name.inspect} on operator #{@operator.inspect}, but not supported! Supported operators are #{@supported}.
|
85
110
|
MSG
|
86
111
|
end
|
@@ -93,7 +118,7 @@ module Graphiti
|
|
93
118
|
end
|
94
119
|
|
95
120
|
def message
|
96
|
-
|
121
|
+
<<~MSG
|
97
122
|
#{@sideload.parent_resource.class.name}: tried to sideload #{@sideload.name.inspect}, but more than one #{@sideload.parent_resource.model.name} was passed!
|
98
123
|
|
99
124
|
This is because you marked the sideload #{@sideload.name.inspect} with single: true
|
@@ -114,7 +139,7 @@ module Graphiti
|
|
114
139
|
end
|
115
140
|
|
116
141
|
def message
|
117
|
-
|
142
|
+
<<~MSG
|
118
143
|
#{@resource.class.name}: tried to sort on attribute #{@attribute.inspect}, but passed #{@direction.inspect} when only #{@allowlist.inspect} is supported.
|
119
144
|
MSG
|
120
145
|
end
|
@@ -127,7 +152,7 @@ module Graphiti
|
|
127
152
|
end
|
128
153
|
|
129
154
|
def message
|
130
|
-
|
155
|
+
<<~MSG
|
131
156
|
#{@resource_class.name}: called .on_extra_attribute #{@name.inspect}, but extra attribute #{@name.inspect} does not exist!
|
132
157
|
MSG
|
133
158
|
end
|
@@ -148,7 +173,7 @@ module Graphiti
|
|
148
173
|
else
|
149
174
|
"value #{@value.inspect}"
|
150
175
|
end
|
151
|
-
msg =
|
176
|
+
msg = <<~MSG
|
152
177
|
#{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid #{value_string}.
|
153
178
|
MSG
|
154
179
|
msg << "\nAllowlist: #{allow.inspect}" if allow
|
@@ -165,7 +190,7 @@ module Graphiti
|
|
165
190
|
end
|
166
191
|
|
167
192
|
def message
|
168
|
-
|
193
|
+
<<~MSG
|
169
194
|
#{@resource_class.name} You declared an attribute or filter of type "#{@enum_type}" without providing a list of permitted values, which is required.
|
170
195
|
|
171
196
|
When declaring an attribute:
|
@@ -189,7 +214,7 @@ module Graphiti
|
|
189
214
|
end
|
190
215
|
|
191
216
|
def message
|
192
|
-
|
217
|
+
<<~MSG
|
193
218
|
#{@resource_class.name}: Cannot link to sideload #{@sideload.name.inspect}!
|
194
219
|
|
195
220
|
Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
|
@@ -708,6 +733,12 @@ module Graphiti
|
|
708
733
|
end
|
709
734
|
end
|
710
735
|
|
736
|
+
class UnsupportedBeforeCursor < Base
|
737
|
+
def message
|
738
|
+
"Passing in page[before] and page[number] is not supported. Please create an issue if you need it!"
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
711
742
|
class InvalidInclude < Base
|
712
743
|
def initialize(resource, relationship)
|
713
744
|
@resource = resource
|
@@ -791,5 +822,34 @@ module Graphiti
|
|
791
822
|
|
792
823
|
class ConflictRequest < InvalidRequest
|
793
824
|
end
|
825
|
+
|
826
|
+
class FilterGroupInvalidRequirement < Base
|
827
|
+
def initialize(resource, valid_required_values)
|
828
|
+
@resource = resource
|
829
|
+
@valid_required_values = valid_required_values
|
830
|
+
end
|
831
|
+
|
832
|
+
def message
|
833
|
+
<<-MSG.gsub(/\s+/, " ").strip
|
834
|
+
The filter group required: value on resource #{@resource.class} must be one of the following:
|
835
|
+
#{@valid_required_values.join(", ")}
|
836
|
+
MSG
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
class FilterGroupMissingRequiredFilters < Base
|
841
|
+
def initialize(resource, filter_names, required)
|
842
|
+
@resource = resource
|
843
|
+
@filter_names = filter_names
|
844
|
+
@required_label = required == :all ? "All" : "One"
|
845
|
+
end
|
846
|
+
|
847
|
+
def message
|
848
|
+
<<-MSG.gsub(/\s+/, " ").strip
|
849
|
+
#{@required_label} of the following filters must be provided on resource #{@resource.type}:
|
850
|
+
#{@filter_names.join(", ")}
|
851
|
+
MSG
|
852
|
+
end
|
853
|
+
end
|
794
854
|
end
|
795
855
|
end
|
@@ -46,9 +46,9 @@ module Graphiti
|
|
46
46
|
next false unless instance_exec(&options[:if])
|
47
47
|
end
|
48
48
|
|
49
|
-
@extra_fields
|
50
|
-
|
51
|
-
|
49
|
+
next false unless @extra_fields
|
50
|
+
|
51
|
+
@extra_fields[@_type]&.include?(name) || @extra_fields[@resource&.type]&.include?(name)
|
52
52
|
}
|
53
53
|
|
54
54
|
attribute name, if: allow_field, &blk
|
@@ -1,55 +1,228 @@
|
|
1
1
|
module Graphiti
|
2
2
|
module SerializableHash
|
3
|
-
def to_hash(fields: nil, include: {})
|
3
|
+
def to_hash(fields: nil, include: {}, name_chain: [], graphql: false)
|
4
4
|
{}.tap do |hash|
|
5
|
-
|
5
|
+
if fields
|
6
|
+
fields_list = nil
|
7
|
+
|
8
|
+
# Dot syntax wins over jsonapi type
|
9
|
+
if name_chain.length > 0
|
10
|
+
fields_list = fields[name_chain.join(".").to_sym]
|
11
|
+
end
|
12
|
+
|
13
|
+
if fields_list.nil?
|
14
|
+
fields_list = fields[jsonapi_type]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# polymorphic resources - merge the PARENT type
|
19
|
+
if polymorphic_subclass?
|
20
|
+
if fields[@resource.type]
|
21
|
+
fields_list ||= []
|
22
|
+
fields_list |= fields[@resource.type]
|
23
|
+
end
|
24
|
+
|
25
|
+
if fields[jsonapi_type]
|
26
|
+
fields_list ||= []
|
27
|
+
fields_list |= fields[jsonapi_type]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
6
31
|
attrs = requested_attributes(fields_list).each_with_object({}) { |(k, v), h|
|
7
|
-
|
32
|
+
name = graphql ? k.to_s.camelize(:lower).to_sym : k
|
33
|
+
h[name] = instance_eval(&v)
|
34
|
+
}
|
35
|
+
|
36
|
+
# The main logic here is just !!include[k]
|
37
|
+
# But we also have special on__<type>--<name> includes
|
38
|
+
# Where we only include when matching the polymorphic type
|
39
|
+
rels = @_relationships.select { |k, v|
|
40
|
+
if include[k]
|
41
|
+
true
|
42
|
+
else
|
43
|
+
included = false
|
44
|
+
include.keys.each do |key|
|
45
|
+
split = key.to_s.split(/^on__/)
|
46
|
+
if split.length > 1
|
47
|
+
requested_type, key = split[1].split("--")
|
48
|
+
if requested_type.to_sym == jsonapi_type
|
49
|
+
included = k == key.to_sym
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
included
|
55
|
+
end
|
8
56
|
}
|
9
|
-
|
57
|
+
|
10
58
|
rels.each_with_object({}) do |(k, v), h|
|
59
|
+
nested_include = include[k]
|
60
|
+
|
61
|
+
# This logic only fires if it's a special on__<type>--<name> include
|
62
|
+
unless include.has_key?(k)
|
63
|
+
include.keys.each do |include_key|
|
64
|
+
if k == include_key.to_s.split("--")[1].to_sym
|
65
|
+
nested_include = include[include_key]
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
11
71
|
serializers = v.send(:resources)
|
12
|
-
|
13
|
-
|
14
|
-
|
72
|
+
name = graphql ? k.to_s.camelize(:lower) : k
|
73
|
+
name_chain = name_chain.dup
|
74
|
+
name_chain << k unless name_chain.last == k
|
75
|
+
|
76
|
+
unless remote_resource? && serializers.nil?
|
77
|
+
payload = if serializers.is_a?(Array)
|
78
|
+
data = serializers.map { |rr|
|
79
|
+
rr.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
|
80
|
+
}
|
81
|
+
graphql ? {nodes: data} : data
|
82
|
+
elsif serializers.nil?
|
83
|
+
if @resource.class.respond_to?(:sideload)
|
84
|
+
if @resource.class.sideload(k).type.to_s.include?("_many")
|
85
|
+
graphql ? {nodes: []} : []
|
86
|
+
end
|
87
|
+
end
|
88
|
+
else
|
89
|
+
serializers.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
|
15
90
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
91
|
+
|
92
|
+
attrs[name.to_sym] = payload
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
if !graphql || (fields_list || []).include?(:id)
|
97
|
+
hash[:id] = jsonapi_id
|
98
|
+
end
|
99
|
+
|
100
|
+
if (fields_list || []).include?(:_type)
|
101
|
+
hash[:_type] = jsonapi_type.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
if (fields_list || []).include?(:_cursor)
|
105
|
+
hash[:_cursor] = cursor
|
106
|
+
end
|
107
|
+
|
108
|
+
if (fields_list || []).include?(:__typename)
|
109
|
+
resource_class = @resource.class
|
110
|
+
if polymorphic_subclass?
|
111
|
+
resource_class = @resource.class.resource_for_type(jsonapi_type)
|
20
112
|
end
|
113
|
+
hash[:__typename] = ::Graphiti::Util::Class
|
114
|
+
.graphql_type_name(resource_class.name)
|
21
115
|
end
|
22
116
|
|
23
|
-
hash[:id] = jsonapi_id
|
24
117
|
hash.merge!(attrs) if attrs.any?
|
25
118
|
end
|
26
119
|
end
|
120
|
+
|
121
|
+
def polymorphic_subclass?
|
122
|
+
!remote_resource? &&
|
123
|
+
@resource.polymorphic? &&
|
124
|
+
@resource.type != jsonapi_type
|
125
|
+
end
|
126
|
+
|
127
|
+
# See hack in util/remote_serializer.rb
|
128
|
+
def remote_resource?
|
129
|
+
@resource == 1
|
130
|
+
end
|
27
131
|
end
|
28
132
|
|
29
133
|
class HashRenderer
|
30
|
-
def initialize(resource)
|
134
|
+
def initialize(resource, graphql: false)
|
31
135
|
@resource = resource
|
136
|
+
@graphql = graphql
|
32
137
|
end
|
33
138
|
|
34
139
|
def render(options)
|
35
140
|
serializers = options[:data]
|
36
141
|
opts = options.slice(:fields, :include)
|
37
|
-
|
38
|
-
|
142
|
+
opts[:graphql] = @graphql
|
143
|
+
top_level_key = get_top_level_key(@resource, serializers.is_a?(Array))
|
144
|
+
|
145
|
+
hash = {top_level_key => {}}
|
146
|
+
nodes = get_nodes(serializers, opts)
|
147
|
+
add_nodes(hash, top_level_key, options, nodes, @graphql)
|
148
|
+
add_stats(hash, top_level_key, options, @graphql)
|
149
|
+
if @graphql
|
150
|
+
add_page_info(hash, serializers, top_level_key, options)
|
39
151
|
end
|
152
|
+
|
153
|
+
hash
|
40
154
|
end
|
41
155
|
|
42
156
|
private
|
43
157
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
158
|
+
def get_top_level_key(resource, is_many)
|
159
|
+
key = :data
|
160
|
+
|
161
|
+
if @graphql
|
162
|
+
key = @resource.graphql_entrypoint
|
163
|
+
key = key.to_s.singularize.to_sym unless is_many
|
164
|
+
end
|
165
|
+
|
166
|
+
key
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_nodes(serializers, opts)
|
170
|
+
if serializers.is_a?(Array)
|
171
|
+
serializers.each_with_index.map do |s, index|
|
172
|
+
s.to_hash(**opts)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
serializers.to_hash(**opts)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_nodes(hash, top_level_key, opts, nodes, graphql)
|
180
|
+
payload = nodes
|
181
|
+
if graphql && nodes.is_a?(Array)
|
182
|
+
payload = {nodes: nodes}
|
183
|
+
end
|
184
|
+
|
185
|
+
# Don't render nodes if we only requested stats
|
186
|
+
unless graphql && opts[:fields].values == [[:stats]]
|
187
|
+
hash[top_level_key] = payload
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def add_stats(hash, top_level_key, options, graphql)
|
192
|
+
if options[:meta] && !options[:meta].empty?
|
193
|
+
if @graphql
|
194
|
+
if (stats = options[:meta][:stats])
|
195
|
+
hash[top_level_key][:stats] = stats
|
49
196
|
end
|
50
197
|
else
|
51
|
-
|
198
|
+
hash.merge!(options.slice(:meta))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# NB - this is only for top-level right now
|
204
|
+
# The casing here is GQL-specific, we can update later if needed.
|
205
|
+
def add_page_info(hash, serializers, top_level_key, options)
|
206
|
+
if (fields = options[:fields].try(:[], :page_info))
|
207
|
+
info = {}
|
208
|
+
|
209
|
+
if fields.include?(:has_next_page)
|
210
|
+
info[:hasNextPage] = options[:proxy].pagination.has_next_page?
|
211
|
+
end
|
212
|
+
|
213
|
+
if fields.include?(:has_previous_page)
|
214
|
+
info[:hasPreviousPage] = options[:proxy].pagination.has_previous_page?
|
215
|
+
end
|
216
|
+
|
217
|
+
if fields.include?(:start_cursor)
|
218
|
+
info[:startCursor] = serializers.first.try(:cursor)
|
219
|
+
end
|
220
|
+
|
221
|
+
if fields.include?(:end_cursor)
|
222
|
+
info[:endCursor] = serializers.last.try(:cursor)
|
52
223
|
end
|
224
|
+
|
225
|
+
hash[top_level_key][:pageInfo] = info
|
53
226
|
end
|
54
227
|
end
|
55
228
|
end
|