graphiti 1.2.16 → 1.3.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +96 -0
- data/.standard.yml +4 -4
- data/Appraisals +23 -17
- data/CHANGELOG.md +7 -1
- data/Guardfile +5 -5
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/deprecated_generators/graphiti/resource_generator.rb +1 -1
- data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +2 -2
- data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +3 -4
- data/gemfiles/rails_6.gemfile +1 -1
- data/gemfiles/rails_6_graphiti_rails.gemfile +2 -3
- data/gemfiles/{rails_4.gemfile → rails_7.gemfile} +2 -2
- data/gemfiles/rails_7_graphiti_rails.gemfile +19 -0
- data/graphiti.gemspec +16 -16
- data/lib/graphiti/adapters/abstract.rb +20 -5
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/{inferrence.rb → inference.rb} +2 -2
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +19 -0
- data/lib/graphiti/adapters/active_record.rb +119 -74
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/null.rb +1 -1
- data/lib/graphiti/adapters/persistence/associations.rb +78 -0
- data/lib/graphiti/configuration.rb +3 -1
- data/lib/graphiti/debugger.rb +12 -8
- data/lib/graphiti/delegates/pagination.rb +47 -13
- data/lib/graphiti/deserializer.rb +3 -3
- data/lib/graphiti/errors.rb +109 -15
- data/lib/graphiti/extensions/extra_attribute.rb +4 -4
- data/lib/graphiti/extensions/temp_id.rb +1 -1
- data/lib/graphiti/filter_operators.rb +0 -1
- data/lib/graphiti/hash_renderer.rb +198 -21
- data/lib/graphiti/query.rb +105 -73
- data/lib/graphiti/railtie.rb +5 -5
- data/lib/graphiti/renderer.rb +19 -1
- data/lib/graphiti/request_validator.rb +10 -10
- data/lib/graphiti/request_validators/update_validator.rb +4 -5
- data/lib/graphiti/request_validators/validator.rb +38 -24
- data/lib/graphiti/resource/configuration.rb +35 -7
- data/lib/graphiti/resource/dsl.rb +34 -8
- data/lib/graphiti/resource/interface.rb +13 -3
- data/lib/graphiti/resource/links.rb +3 -3
- data/lib/graphiti/resource/persistence.rb +2 -1
- data/lib/graphiti/resource/polymorphism.rb +8 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +4 -4
- data/lib/graphiti/resource.rb +12 -1
- data/lib/graphiti/resource_proxy.rb +23 -3
- data/lib/graphiti/runner.rb +5 -5
- data/lib/graphiti/schema.rb +36 -11
- data/lib/graphiti/schema_diff.rb +44 -4
- data/lib/graphiti/scope.rb +8 -10
- data/lib/graphiti/scoping/base.rb +3 -3
- data/lib/graphiti/scoping/filter.rb +36 -15
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/scoping/sort.rb +5 -7
- data/lib/graphiti/serializer.rb +49 -7
- data/lib/graphiti/sideload/belongs_to.rb +1 -1
- data/lib/graphiti/sideload/has_many.rb +19 -1
- data/lib/graphiti/sideload/many_to_many.rb +11 -4
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -4
- data/lib/graphiti/sideload.rb +47 -23
- data/lib/graphiti/stats/dsl.rb +0 -1
- data/lib/graphiti/stats/payload.rb +12 -9
- data/lib/graphiti/types.rb +15 -15
- data/lib/graphiti/util/attribute_check.rb +1 -1
- data/lib/graphiti/util/class.rb +6 -0
- data/lib/graphiti/util/link.rb +10 -2
- data/lib/graphiti/util/persistence.rb +21 -78
- data/lib/graphiti/util/relationship_payload.rb +4 -4
- 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 +41 -11
- data/lib/graphiti/util/simple_errors.rb +4 -4
- data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +6 -3
- metadata +46 -37
- data/.travis.yml +0 -59
data/lib/graphiti/scope.rb
CHANGED
@@ -3,10 +3,10 @@ module Graphiti
|
|
3
3
|
attr_accessor :object, :unpaginated_object
|
4
4
|
attr_reader :pagination
|
5
5
|
def initialize(object, resource, query, opts = {})
|
6
|
-
@object
|
7
|
-
@resource
|
8
|
-
@query
|
9
|
-
@opts
|
6
|
+
@object = object
|
7
|
+
@resource = resource
|
8
|
+
@query = query
|
9
|
+
@opts = opts
|
10
10
|
|
11
11
|
@object = @resource.around_scoping(@object, @query.hash) { |scope|
|
12
12
|
apply_scoping(scope, opts)
|
@@ -45,9 +45,7 @@ module Graphiti
|
|
45
45
|
resolve_sideload = -> {
|
46
46
|
Graphiti.context = graphiti_context
|
47
47
|
sideload.resolve(results, q, parent_resource)
|
48
|
-
if concurrent
|
49
|
-
ActiveRecord::Base.clear_active_connections!
|
50
|
-
end
|
48
|
+
@resource.adapter.close if concurrent
|
51
49
|
}
|
52
50
|
if concurrent
|
53
51
|
promises << Concurrent::Promise.execute(&resolve_sideload)
|
@@ -75,7 +73,7 @@ module Graphiti
|
|
75
73
|
resource: @resource,
|
76
74
|
params: @opts[:params],
|
77
75
|
sideload: @opts[:sideload],
|
78
|
-
parent: @opts[:parent]
|
76
|
+
parent: @opts[:parent]
|
79
77
|
# Set once data is resolved within block
|
80
78
|
# results: ...
|
81
79
|
}
|
@@ -87,8 +85,8 @@ module Graphiti
|
|
87
85
|
# Used to ensure the resource's serializer is used
|
88
86
|
# Not one derived through the usual jsonapi-rb logic
|
89
87
|
def assign_serializer(records)
|
90
|
-
records.
|
91
|
-
@resource.decorate_record(r)
|
88
|
+
records.each_with_index do |r, index|
|
89
|
+
@resource.decorate_record(r, index)
|
92
90
|
end
|
93
91
|
end
|
94
92
|
|
@@ -25,9 +25,9 @@ module Graphiti
|
|
25
25
|
# @param [Hash] opts configuration options used by subclasses
|
26
26
|
def initialize(resource, query_hash, scope, opts = {})
|
27
27
|
@query_hash = query_hash
|
28
|
-
@resource
|
29
|
-
@scope
|
30
|
-
@opts
|
28
|
+
@resource = resource
|
29
|
+
@scope = scope
|
30
|
+
@opts = opts
|
31
31
|
end
|
32
32
|
|
33
33
|
# Apply this scoping criteria.
|
@@ -3,6 +3,13 @@ module Graphiti
|
|
3
3
|
include Scoping::Filterable
|
4
4
|
|
5
5
|
def apply
|
6
|
+
unless @opts[:bypass_required_filters]
|
7
|
+
Graphiti::Scoping::FilterGroupValidator.new(
|
8
|
+
resource,
|
9
|
+
query_hash
|
10
|
+
).raise_unless_filter_group_requirements_met!
|
11
|
+
end
|
12
|
+
|
6
13
|
if missing_required_filters.any? && !@opts[:bypass_required_filters]
|
7
14
|
raise Errors::RequiredFilter.new(resource, missing_required_filters)
|
8
15
|
end
|
@@ -31,7 +38,7 @@ module Graphiti
|
|
31
38
|
|
32
39
|
def filter_via_adapter(filter, operator, value)
|
33
40
|
type_name = Types.name_for(filter.values.first[:type])
|
34
|
-
method
|
41
|
+
method = :"filter_#{type_name}_#{operator}"
|
35
42
|
attribute = filter.keys.first
|
36
43
|
|
37
44
|
if resource.adapter.respond_to?(method)
|
@@ -54,6 +61,8 @@ module Graphiti
|
|
54
61
|
unless type[:canonical_name] == :hash || !value.is_a?(String)
|
55
62
|
value = parse_string_value(filter.values[0], value)
|
56
63
|
end
|
64
|
+
|
65
|
+
check_deny_empty_filters!(resource, filter, value)
|
57
66
|
value = parse_string_null(filter.values[0], value)
|
58
67
|
validate_singular(resource, filter, value)
|
59
68
|
value = coerce_types(filter.values[0], param_name.to_sym, value)
|
@@ -82,11 +91,11 @@ module Graphiti
|
|
82
91
|
type = Types[filter.values[0][:type]][:canonical_name]
|
83
92
|
if param_value.is_a?(Hash) && type == :hash
|
84
93
|
operators_keys = filter.values[0][:operators].keys
|
85
|
-
unless param_value.keys.all? {|k| operators_keys.include?(k)}
|
86
|
-
param_value = {
|
94
|
+
unless param_value.keys.all? { |k| operators_keys.include?(k) }
|
95
|
+
param_value = {eq: param_value}
|
87
96
|
end
|
88
97
|
elsif !param_value.is_a?(Hash) || param_value.empty?
|
89
|
-
param_value = {
|
98
|
+
param_value = {eq: param_value}
|
90
99
|
end
|
91
100
|
|
92
101
|
param_value.map do |operator, value|
|
@@ -162,14 +171,18 @@ module Graphiti
|
|
162
171
|
type = Graphiti::Types[filter[:type]]
|
163
172
|
array_or_string = [:string, :array].include?(type[:canonical_name])
|
164
173
|
if (arr = value.scan(/\[.*?\]/)).present? && array_or_string
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
174
|
+
begin
|
175
|
+
value = arr.map { |json|
|
176
|
+
begin
|
177
|
+
JSON.parse(json)
|
178
|
+
rescue
|
179
|
+
raise Errors::InvalidJSONArray.new(resource, value)
|
180
|
+
end
|
181
|
+
}
|
182
|
+
value = value[0] if value.length == 1
|
183
|
+
rescue Errors::InvalidJSONArray => e
|
184
|
+
raise(e) if type[:canonical_name] == :array
|
185
|
+
end
|
173
186
|
else
|
174
187
|
value = parse_string_arrays(value, !!filter[:single])
|
175
188
|
end
|
@@ -184,10 +197,10 @@ module Graphiti
|
|
184
197
|
# remove the quote characters from the quoted strings
|
185
198
|
quotes.each { |q| q.gsub!("{{", "").gsub!("}}", "") }
|
186
199
|
# merge everything back together into an array
|
187
|
-
if singular_filter
|
188
|
-
|
200
|
+
value = if singular_filter
|
201
|
+
Array(value) + quotes
|
189
202
|
else
|
190
|
-
|
203
|
+
Array(value.split(",")) + quotes
|
191
204
|
end
|
192
205
|
# remove any blanks that are left
|
193
206
|
value.reject! { |v| v.length.zero? }
|
@@ -200,5 +213,13 @@ module Graphiti
|
|
200
213
|
|
201
214
|
value
|
202
215
|
end
|
216
|
+
|
217
|
+
def check_deny_empty_filters!(resource, filter, value)
|
218
|
+
return unless filter.values[0][:deny_empty]
|
219
|
+
|
220
|
+
if value.nil? || value.empty? || value == "null"
|
221
|
+
raise Errors::InvalidFilterValue.new(resource, filter, "(empty)")
|
222
|
+
end
|
223
|
+
end
|
203
224
|
end
|
204
225
|
end
|
@@ -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
|
@@ -59,12 +59,10 @@ module Graphiti
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def sort_param
|
62
|
-
@sort_param ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
normalize(query_hash[:sort])
|
67
|
-
end
|
62
|
+
@sort_param ||= if query_hash[:sort].blank?
|
63
|
+
resource.default_sort || []
|
64
|
+
else
|
65
|
+
normalize(query_hash[:sort])
|
68
66
|
end
|
69
67
|
end
|
70
68
|
|
@@ -78,7 +76,7 @@ module Graphiti
|
|
78
76
|
|
79
77
|
def sort_hash(attr)
|
80
78
|
value = attr[0] == "-" ? :desc : :asc
|
81
|
-
key
|
79
|
+
key = attr.sub("-", "").to_sym
|
82
80
|
|
83
81
|
{key => value}
|
84
82
|
end
|
data/lib/graphiti/serializer.rb
CHANGED
@@ -12,26 +12,62 @@ 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
|
22
46
|
end
|
23
47
|
end
|
24
48
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
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
|
28
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
|
29
64
|
end
|
30
65
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
66
|
+
def as_jsonapi(kwargs = {})
|
67
|
+
super(**kwargs).tap do |hash|
|
68
|
+
strip_relationships!(hash) if strip_relationships?
|
69
|
+
add_links!(hash)
|
70
|
+
end
|
35
71
|
end
|
36
72
|
|
37
73
|
# Allow access to resource methods
|
@@ -49,6 +85,12 @@ module Graphiti
|
|
49
85
|
|
50
86
|
private
|
51
87
|
|
88
|
+
def add_links!(hash)
|
89
|
+
return unless @resource.respond_to?(:links?)
|
90
|
+
|
91
|
+
hash[:links] = @resource.links(@object) if @resource.links?
|
92
|
+
end
|
93
|
+
|
52
94
|
def strip_relationships!(hash)
|
53
95
|
hash[:relationships]&.select! do |name, payload|
|
54
96
|
payload.key?(:data)
|
@@ -1,8 +1,18 @@
|
|
1
1
|
class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
2
|
+
def initialize(name, opts)
|
3
|
+
@inverse_filter = opts[:inverse_filter]
|
4
|
+
|
5
|
+
super(name, opts)
|
6
|
+
end
|
7
|
+
|
2
8
|
def type
|
3
9
|
:has_many
|
4
10
|
end
|
5
11
|
|
12
|
+
def inverse_filter
|
13
|
+
@inverse_filter || foreign_key
|
14
|
+
end
|
15
|
+
|
6
16
|
def load_params(parents, query)
|
7
17
|
query.hash.tap do |hash|
|
8
18
|
hash[:filter] ||= {}
|
@@ -11,11 +21,19 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
|
11
21
|
end
|
12
22
|
|
13
23
|
def base_filter(parents)
|
14
|
-
{foreign_key =>
|
24
|
+
{foreign_key => parent_filter(parents)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def link_filter(parents)
|
28
|
+
{inverse_filter => parent_filter(parents)}
|
15
29
|
end
|
16
30
|
|
17
31
|
private
|
18
32
|
|
33
|
+
def parent_filter(parents)
|
34
|
+
ids_for_parents(parents).join(",")
|
35
|
+
end
|
36
|
+
|
19
37
|
def child_map(children)
|
20
38
|
children.group_by(&foreign_key)
|
21
39
|
end
|
@@ -11,8 +11,12 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
|
|
11
11
|
foreign_key.values.first
|
12
12
|
end
|
13
13
|
|
14
|
+
def inverse_filter
|
15
|
+
@inverse_filter || true_foreign_key
|
16
|
+
end
|
17
|
+
|
14
18
|
def base_filter(parents)
|
15
|
-
{true_foreign_key =>
|
19
|
+
{true_foreign_key => parent_filter(parents)}
|
16
20
|
end
|
17
21
|
|
18
22
|
def infer_foreign_key
|
@@ -32,9 +36,12 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
|
|
32
36
|
self_ref = self
|
33
37
|
fk_type = parent_resource_class.attributes[:id][:type]
|
34
38
|
fk_type = :hash if polymorphic?
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
# Do not recreate if filter already exists
|
40
|
+
unless resource_class.config[:filters].has_key?(inverse_filter.to_sym)
|
41
|
+
resource_class.filter inverse_filter, fk_type do
|
42
|
+
eq do |scope, value|
|
43
|
+
self_ref.belongs_to_many_filter(scope, value)
|
44
|
+
end
|
38
45
|
end
|
39
46
|
end
|
40
47
|
end
|
@@ -7,7 +7,6 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
|
|
7
7
|
@calls = []
|
8
8
|
end
|
9
9
|
|
10
|
-
# rubocop: disable Style/MethodMissingSuper
|
11
10
|
def method_missing(name, *args, &blk)
|
12
11
|
@calls << [name, args, blk]
|
13
12
|
end
|
@@ -56,9 +55,9 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
|
|
56
55
|
args = call[1]
|
57
56
|
opts = args.extract_options!
|
58
57
|
opts.merge! as: sideload.name,
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
parent: sideload,
|
59
|
+
group_name: group.name,
|
60
|
+
polymorphic_child: true
|
62
61
|
unless sideload.resource.class.abstract_class?
|
63
62
|
opts[:foreign_key] ||= sideload.foreign_key
|
64
63
|
opts[:primary_key] ||= sideload.primary_key
|
data/lib/graphiti/sideload.rb
CHANGED
@@ -18,31 +18,31 @@ module Graphiti
|
|
18
18
|
:link_proc
|
19
19
|
|
20
20
|
def initialize(name, opts)
|
21
|
-
@name
|
21
|
+
@name = name
|
22
22
|
validate_options!(opts)
|
23
|
-
@parent_resource_class
|
24
|
-
@resource_class
|
25
|
-
@primary_key
|
26
|
-
@foreign_key
|
27
|
-
@type
|
28
|
-
@base_scope
|
29
|
-
@readable
|
30
|
-
@writable
|
31
|
-
@as
|
32
|
-
@link
|
33
|
-
@single
|
34
|
-
@remote
|
23
|
+
@parent_resource_class = opts[:parent_resource]
|
24
|
+
@resource_class = opts[:resource]
|
25
|
+
@primary_key = opts[:primary_key]
|
26
|
+
@foreign_key = opts[:foreign_key]
|
27
|
+
@type = opts[:type]
|
28
|
+
@base_scope = opts[:base_scope]
|
29
|
+
@readable = opts[:readable]
|
30
|
+
@writable = opts[:writable]
|
31
|
+
@as = opts[:as]
|
32
|
+
@link = opts[:link]
|
33
|
+
@single = opts[:single]
|
34
|
+
@remote = opts[:remote]
|
35
35
|
apply_belongs_to_many_filter if type == :many_to_many
|
36
36
|
|
37
|
-
@description
|
37
|
+
@description = opts[:description]
|
38
38
|
|
39
39
|
# polymorphic has_many
|
40
|
-
@polymorphic_as
|
40
|
+
@polymorphic_as = opts[:polymorphic_as]
|
41
41
|
# polymorphic_belongs_to-specific
|
42
|
-
@group_name
|
43
|
-
@polymorphic_child
|
44
|
-
@parent
|
45
|
-
@always_include_resource_ids
|
42
|
+
@group_name = opts[:group_name]
|
43
|
+
@polymorphic_child = opts[:polymorphic_child]
|
44
|
+
@parent = opts[:parent]
|
45
|
+
@always_include_resource_ids = opts[:always_include_resource_ids]
|
46
46
|
|
47
47
|
if polymorphic_child?
|
48
48
|
parent.resource.polymorphic << resource_class
|
@@ -132,6 +132,23 @@ module Graphiti
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
+
def link_filter(parents)
|
136
|
+
base_filter(parents)
|
137
|
+
end
|
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
|
+
|
135
152
|
# The parent resource is a remote,
|
136
153
|
# AND the sideload is a remote to the same endpoint
|
137
154
|
def shared_remote?
|
@@ -197,7 +214,7 @@ module Graphiti
|
|
197
214
|
|
198
215
|
with_error_handling Errors::SideloadParamsError do
|
199
216
|
params = load_params(parents, query)
|
200
|
-
params_proc&.call(params, parents)
|
217
|
+
params_proc&.call(params, parents, context)
|
201
218
|
return [] if blank_query?(params)
|
202
219
|
opts = load_options(parents, query)
|
203
220
|
opts[:sideload] = self
|
@@ -205,7 +222,9 @@ module Graphiti
|
|
205
222
|
end
|
206
223
|
|
207
224
|
with_error_handling(Errors::SideloadQueryBuildingError) do
|
208
|
-
|
225
|
+
scope = base_scope
|
226
|
+
scope[:foreign_key] = foreign_key if remote?
|
227
|
+
proxy = resource.class._all(params, opts, scope)
|
209
228
|
pre_load_proc&.call(proxy, parents)
|
210
229
|
end
|
211
230
|
|
@@ -422,17 +441,22 @@ module Graphiti
|
|
422
441
|
Util::Class.namespace_for(klass)
|
423
442
|
end
|
424
443
|
|
444
|
+
# TODO: call this at runtime to support procs
|
425
445
|
def evaluate_flag(flag)
|
426
446
|
return false if flag.blank?
|
427
447
|
|
428
448
|
case flag.class.name
|
429
|
-
when "Symbol","String"
|
449
|
+
when "Symbol", "String"
|
430
450
|
resource.send(flag)
|
431
451
|
when "Proc"
|
432
|
-
|
452
|
+
resource.instance_exec(&flag)
|
433
453
|
else
|
434
454
|
!!flag
|
435
455
|
end
|
436
456
|
end
|
457
|
+
|
458
|
+
def context
|
459
|
+
Graphiti.context[:object]
|
460
|
+
end
|
437
461
|
end
|
438
462
|
end
|
data/lib/graphiti/stats/dsl.rb
CHANGED