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
@@ -0,0 +1,78 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Scoping::FilterGroupValidator
|
3
|
+
VALID_REQUIRED_VALUES = %i[all any]
|
4
|
+
|
5
|
+
def self.raise_unless_filter_group_requirement_valid!(resource, requirement)
|
6
|
+
unless VALID_REQUIRED_VALUES.include?(requirement)
|
7
|
+
raise Errors::FilterGroupInvalidRequirement.new(
|
8
|
+
resource,
|
9
|
+
VALID_REQUIRED_VALUES
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(resource, query_hash)
|
17
|
+
@resource = resource
|
18
|
+
@query_hash = query_hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def raise_unless_filter_group_requirements_met!
|
22
|
+
return if grouped_filters.empty?
|
23
|
+
|
24
|
+
case filter_group_requirement
|
25
|
+
when :all
|
26
|
+
raise_unless_all_requirements_met!
|
27
|
+
when :any
|
28
|
+
raise_unless_any_requirements_met!
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :resource, :query_hash
|
37
|
+
|
38
|
+
def raise_unless_all_requirements_met!
|
39
|
+
met = filter_group_names.all? { |filter_name| filter_group_filter_param.key?(filter_name) }
|
40
|
+
|
41
|
+
unless met
|
42
|
+
raise Errors::FilterGroupMissingRequiredFilters.new(
|
43
|
+
resource,
|
44
|
+
filter_group_names,
|
45
|
+
filter_group_requirement
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def raise_unless_any_requirements_met!
|
51
|
+
met = filter_group_names.any? { |filter_name| filter_group_filter_param.key?(filter_name) }
|
52
|
+
|
53
|
+
unless met
|
54
|
+
raise Errors::FilterGroupMissingRequiredFilters.new(
|
55
|
+
resource,
|
56
|
+
filter_group_names,
|
57
|
+
filter_group_requirement
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def filter_group_names
|
63
|
+
grouped_filters.fetch(:names, [])
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_group_requirement
|
67
|
+
grouped_filters.fetch(:required, :invalid)
|
68
|
+
end
|
69
|
+
|
70
|
+
def grouped_filters
|
71
|
+
resource.grouped_filters
|
72
|
+
end
|
73
|
+
|
74
|
+
def filter_group_filter_param
|
75
|
+
query_hash.fetch(:filter, {})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class Scoping::Paginate < Scoping::Base
|
3
3
|
DEFAULT_PAGE_SIZE = 20
|
4
|
+
PARAMS = [:number, :size, :offset, :before, :after]
|
4
5
|
|
5
6
|
def apply
|
6
7
|
if size > resource.max_page_size
|
@@ -33,24 +34,67 @@ module Graphiti
|
|
33
34
|
|
34
35
|
# Apply default pagination proc via the Resource adapter
|
35
36
|
def apply_standard_scope
|
36
|
-
resource.adapter.paginate
|
37
|
+
meth = resource.adapter.method(:paginate)
|
38
|
+
|
39
|
+
if meth.arity == 4 # backwards-compat
|
40
|
+
resource.adapter.paginate(@scope, number, size, offset)
|
41
|
+
else
|
42
|
+
resource.adapter.paginate(@scope, number, size)
|
43
|
+
end
|
37
44
|
end
|
38
45
|
|
39
46
|
# Apply the custom pagination proc
|
40
47
|
def apply_custom_scope
|
41
|
-
resource.instance_exec
|
48
|
+
resource.instance_exec \
|
49
|
+
@scope,
|
50
|
+
number,
|
51
|
+
size,
|
52
|
+
resource.context,
|
53
|
+
offset,
|
54
|
+
&custom_scope
|
42
55
|
end
|
43
56
|
|
44
57
|
private
|
45
58
|
|
46
59
|
def requested?
|
47
|
-
!
|
60
|
+
!PARAMS.map { |p| page_param[p] }.all?(&:nil?)
|
48
61
|
end
|
49
62
|
|
50
63
|
def page_param
|
51
64
|
@page_param ||= (query_hash[:page] || {})
|
52
65
|
end
|
53
66
|
|
67
|
+
def offset
|
68
|
+
offset = nil
|
69
|
+
|
70
|
+
if (value = page_param[:offset])
|
71
|
+
offset = value.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
if before_cursor&.key?(:offset)
|
75
|
+
if page_param.key?(:number)
|
76
|
+
raise Errors::UnsupportedBeforeCursor
|
77
|
+
end
|
78
|
+
|
79
|
+
offset = before_cursor[:offset] - (size * number) - 1
|
80
|
+
offset = 0 if offset.negative?
|
81
|
+
end
|
82
|
+
|
83
|
+
if after_cursor&.key?(:offset)
|
84
|
+
offset = after_cursor[:offset]
|
85
|
+
end
|
86
|
+
|
87
|
+
offset
|
88
|
+
end
|
89
|
+
|
90
|
+
def after_cursor
|
91
|
+
page_param[:after]
|
92
|
+
end
|
93
|
+
|
94
|
+
def before_cursor
|
95
|
+
page_param[:before]
|
96
|
+
end
|
97
|
+
|
54
98
|
def number
|
55
99
|
(page_param[:number] || 1).to_i
|
56
100
|
end
|
data/lib/graphiti/serializer.rb
CHANGED
@@ -12,14 +12,55 @@ module Graphiti
|
|
12
12
|
# go through type checking/coercion
|
13
13
|
class_attribute :attributes_applied_via_resource
|
14
14
|
class_attribute :extra_attributes_applied_via_resource
|
15
|
+
class_attribute :relationship_condition_blocks
|
15
16
|
self.attributes_applied_via_resource = []
|
16
17
|
self.extra_attributes_applied_via_resource = []
|
18
|
+
# See #requested_relationships
|
19
|
+
self.relationship_condition_blocks ||= {}
|
17
20
|
|
18
21
|
def self.inherited(klass)
|
19
22
|
super
|
20
23
|
klass.class_eval do
|
21
24
|
extend JSONAPI::Serializable::Resource::ConditionalFields
|
25
|
+
|
26
|
+
# See #requested_relationships
|
27
|
+
def self.relationship(name, options = {}, &block)
|
28
|
+
prev = Util::Hash.deep_dup(field_condition_blocks)
|
29
|
+
super
|
30
|
+
self.field_condition_blocks = prev
|
31
|
+
_register_condition(relationship_condition_blocks, name, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# NB - avoid clobbering includes when sparse fieldset
|
35
|
+
# https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
|
36
|
+
#
|
37
|
+
# We also override this method to ensure attributes and relationships
|
38
|
+
# have separate condition blocks. This way an attribute and
|
39
|
+
# relationship can have the same name, and the attribute can be
|
40
|
+
# conditional without affecting the relationship.
|
41
|
+
def requested_relationships(fields)
|
42
|
+
@_relationships.select do |k, _|
|
43
|
+
_conditionally_included?(self.class.relationship_condition_blocks, k)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def cursor
|
50
|
+
starting_offset = 0
|
51
|
+
page_param = @proxy.query.pagination
|
52
|
+
if (page_number = page_param[:number])
|
53
|
+
page_size = page_param[:size] || @resource.default_page_size
|
54
|
+
starting_offset = (page_number - 1) * page_size
|
22
55
|
end
|
56
|
+
|
57
|
+
if (cursor = page_param[:after])
|
58
|
+
starting_offset = cursor[:offset]
|
59
|
+
end
|
60
|
+
|
61
|
+
current_offset = @object.instance_variable_get(:@__graphiti_index)
|
62
|
+
offset = starting_offset + current_offset + 1 # (+ 1 b/c o-base index)
|
63
|
+
Base64.encode64({offset: offset}.to_json).chomp
|
23
64
|
end
|
24
65
|
|
25
66
|
def as_jsonapi(kwargs = {})
|
@@ -29,12 +70,6 @@ module Graphiti
|
|
29
70
|
end
|
30
71
|
end
|
31
72
|
|
32
|
-
# Temporary fix until fixed upstream
|
33
|
-
# https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
|
34
|
-
def requested_relationships(fields)
|
35
|
-
@_relationships
|
36
|
-
end
|
37
|
-
|
38
73
|
# Allow access to resource methods
|
39
74
|
def method_missing(id, *args, &blk)
|
40
75
|
if @resource.respond_to?(id, true)
|
data/lib/graphiti/sideload.rb
CHANGED
@@ -136,6 +136,19 @@ module Graphiti
|
|
136
136
|
base_filter(parents)
|
137
137
|
end
|
138
138
|
|
139
|
+
def link_extra_fields
|
140
|
+
return unless context&.respond_to?(:params)
|
141
|
+
|
142
|
+
extra_fields_name = [association_name, resource.type].find { |param|
|
143
|
+
context.params.dig(:extra_fields, param)
|
144
|
+
}
|
145
|
+
|
146
|
+
if extra_fields_name
|
147
|
+
extra_fields = context.params.dig(:extra_fields, extra_fields_name)
|
148
|
+
{resource.type => extra_fields}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
139
152
|
# The parent resource is a remote,
|
140
153
|
# AND the sideload is a remote to the same endpoint
|
141
154
|
def shared_remote?
|
@@ -209,7 +222,9 @@ module Graphiti
|
|
209
222
|
end
|
210
223
|
|
211
224
|
with_error_handling(Errors::SideloadQueryBuildingError) do
|
212
|
-
|
225
|
+
scope = base_scope
|
226
|
+
scope[:foreign_key] = foreign_key if remote?
|
227
|
+
proxy = resource.class._all(params, opts, scope)
|
213
228
|
pre_load_proc&.call(proxy, parents)
|
214
229
|
end
|
215
230
|
|
data/lib/graphiti/util/class.rb
CHANGED
@@ -20,6 +20,12 @@ module Graphiti
|
|
20
20
|
split = namespace.split("::")
|
21
21
|
split[0, split.length - 1].join("::")
|
22
22
|
end
|
23
|
+
|
24
|
+
def self.graphql_type_name(name)
|
25
|
+
name.gsub("Resource", "")
|
26
|
+
.gsub("::", "") # remove modules
|
27
|
+
.gsub(".", "__") # remove remote resource .
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
data/lib/graphiti/util/link.rb
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
module Graphiti
|
3
3
|
module Util
|
4
4
|
class RemoteParams
|
5
|
-
def self.generate(resource, query)
|
6
|
-
new(resource, query).generate
|
5
|
+
def self.generate(resource, query, foreign_key)
|
6
|
+
new(resource, query, foreign_key).generate
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize(resource, query)
|
9
|
+
def initialize(resource, query, foreign_key)
|
10
10
|
@resource = resource
|
11
11
|
@query = query
|
12
|
+
@foreign_key = foreign_key
|
12
13
|
@sorts = []
|
13
14
|
@filters = {}
|
14
15
|
@fields = {}
|
@@ -97,7 +98,11 @@ module Graphiti
|
|
97
98
|
return unless fields
|
98
99
|
|
99
100
|
fields.each_pair do |type, attrs|
|
100
|
-
|
101
|
+
all_attrs = attrs
|
102
|
+
if @foreign_key
|
103
|
+
all_attrs |= [@foreign_key]
|
104
|
+
end
|
105
|
+
@fields[type] = all_attrs.join(",")
|
101
106
|
end
|
102
107
|
end
|
103
108
|
|
@@ -44,6 +44,7 @@ module Graphiti
|
|
44
44
|
model.delete_field(:_relationships)
|
45
45
|
# If this isn't set, Array(resources) will return []
|
46
46
|
# This is important, because jsonapi-serializable makes this call
|
47
|
+
# To see the test failure, remote resource position > department
|
47
48
|
model.instance_variable_set(:@__graphiti_resource, 1)
|
48
49
|
model.instance_variable_set(:@__graphiti_serializer, serializer)
|
49
50
|
end
|
@@ -12,26 +12,48 @@ module Graphiti
|
|
12
12
|
def apply
|
13
13
|
return unless readable?
|
14
14
|
|
15
|
+
remove_guard if previously_guarded?
|
16
|
+
|
15
17
|
if @name == :id
|
16
18
|
@serializer.id(&proc)
|
17
|
-
elsif @attr[:proc]
|
18
|
-
|
19
|
-
|
19
|
+
elsif @attr[:proc] ||
|
20
|
+
!previously_applied? ||
|
21
|
+
previously_applied_via_resource?
|
20
22
|
@serializer.send(_method, @name, serializer_options, &proc)
|
21
|
-
else
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@serializer.send(_method, @name, serializer_options, &wrapped)
|
26
|
-
end
|
23
|
+
else # Previously applied via explicit serializer, so wrap it
|
24
|
+
inner = @serializer.attribute_blocks.delete(@name)
|
25
|
+
wrapped = wrap_proc(inner)
|
26
|
+
@serializer.send(_method, @name, serializer_options, &wrapped)
|
27
27
|
end
|
28
28
|
|
29
29
|
existing = @serializer.send(applied_method)
|
30
30
|
@serializer.send(:"#{applied_method}=", [@name] | existing)
|
31
|
+
|
32
|
+
@serializer.meta do
|
33
|
+
if !!@resource.try(:cursor_paginatable?) && !Graphiti.context[:graphql]
|
34
|
+
{cursor: cursor}
|
35
|
+
end
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
private
|
34
40
|
|
41
|
+
def previously_applied?
|
42
|
+
@serializer.attribute_blocks[@name].present?
|
43
|
+
end
|
44
|
+
|
45
|
+
def previously_applied_via_resource?
|
46
|
+
@serializer.send(applied_method).include?(@name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def previously_guarded?
|
50
|
+
@serializer.field_condition_blocks[@name]
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_guard
|
54
|
+
@serializer.field_condition_blocks.delete(@name)
|
55
|
+
end
|
56
|
+
|
35
57
|
def applied_method
|
36
58
|
if extra?
|
37
59
|
:extra_attributes_applied_via_resource
|
@@ -56,16 +78,21 @@ module Graphiti
|
|
56
78
|
method_name = @attr[:readable]
|
57
79
|
instance = @resource.new
|
58
80
|
attribute = @name.to_s
|
81
|
+
resource_class = @resource
|
59
82
|
|
60
83
|
-> {
|
61
84
|
method = instance.method(method_name)
|
62
|
-
if method.arity.zero?
|
85
|
+
result = if method.arity.zero?
|
63
86
|
instance.instance_eval(&method_name)
|
64
87
|
elsif method.arity == 1
|
65
88
|
instance.instance_exec(@object, &method)
|
66
89
|
else
|
67
90
|
instance.instance_exec(@object, attribute, &method)
|
68
91
|
end
|
92
|
+
if Graphiti.context[:graphql] && !result
|
93
|
+
raise ::Graphiti::Errors::UnreadableAttribute.new(resource_class, attribute)
|
94
|
+
end
|
95
|
+
result
|
69
96
|
}
|
70
97
|
end
|
71
98
|
|
data/lib/graphiti/version.rb
CHANGED
data/lib/graphiti.rb
CHANGED
@@ -6,6 +6,7 @@ require "active_support/core_ext/class/attribute"
|
|
6
6
|
require "active_support/core_ext/hash/conversions" # to_xml
|
7
7
|
require "active_support/concern"
|
8
8
|
require "active_support/time"
|
9
|
+
require "active_support/deprecation"
|
9
10
|
|
10
11
|
require "dry-types"
|
11
12
|
require "graphiti_errors"
|
@@ -141,6 +142,7 @@ require "graphiti/scoping/sort"
|
|
141
142
|
require "graphiti/scoping/paginate"
|
142
143
|
require "graphiti/scoping/extra_attributes"
|
143
144
|
require "graphiti/scoping/filterable"
|
145
|
+
require "graphiti/scoping/filter_group_validator"
|
144
146
|
require "graphiti/scoping/default_filter"
|
145
147
|
require "graphiti/scoping/filter"
|
146
148
|
require "graphiti/stats/dsl"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jsonapi-serializable
|
@@ -308,6 +308,7 @@ files:
|
|
308
308
|
- lib/graphiti/scoping/default_filter.rb
|
309
309
|
- lib/graphiti/scoping/extra_attributes.rb
|
310
310
|
- lib/graphiti/scoping/filter.rb
|
311
|
+
- lib/graphiti/scoping/filter_group_validator.rb
|
311
312
|
- lib/graphiti/scoping/filterable.rb
|
312
313
|
- lib/graphiti/scoping/paginate.rb
|
313
314
|
- lib/graphiti/scoping/sort.rb
|
@@ -360,7 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
360
361
|
- !ruby/object:Gem::Version
|
361
362
|
version: '0'
|
362
363
|
requirements: []
|
363
|
-
rubygems_version: 3.
|
364
|
+
rubygems_version: 3.1.6
|
364
365
|
signing_key:
|
365
366
|
specification_version: 4
|
366
367
|
summary: Easily build jsonapi.org-compatible APIs
|