graphiti 1.2.16 → 1.3.9
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/.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