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
@@ -5,10 +5,15 @@ module Graphiti
|
|
5
5
|
|
6
6
|
class_methods do
|
7
7
|
def filter(name, *args, &blk)
|
8
|
+
name = name.to_sym
|
8
9
|
opts = args.extract_options!
|
9
10
|
type_override = args[0]
|
10
11
|
|
11
|
-
if (att =
|
12
|
+
if (att = (attributes[name] || extra_attributes[name]))
|
13
|
+
# We're opting in to filtering, so force this
|
14
|
+
# UNLESS the filter is guarded at the attribute level
|
15
|
+
att[:filterable] = true if att[:filterable] == false
|
16
|
+
|
12
17
|
aliases = [name, opts[:aliases]].flatten.compact
|
13
18
|
operators = FilterOperators.build(self, att[:type], opts, &blk)
|
14
19
|
|
@@ -22,6 +27,8 @@ module Graphiti
|
|
22
27
|
end
|
23
28
|
|
24
29
|
required = att[:filterable] == :required || !!opts[:required]
|
30
|
+
schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
|
31
|
+
|
25
32
|
config[:filters][name.to_sym] = {
|
26
33
|
aliases: aliases,
|
27
34
|
name: name.to_sym,
|
@@ -31,8 +38,10 @@ module Graphiti
|
|
31
38
|
single: !!opts[:single],
|
32
39
|
dependencies: opts[:dependent],
|
33
40
|
required: required,
|
41
|
+
schema: schema,
|
34
42
|
operators: operators.to_hash,
|
35
|
-
allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default)
|
43
|
+
allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
|
44
|
+
deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
|
36
45
|
}
|
37
46
|
elsif (type = args[0])
|
38
47
|
attribute name, type, only: [:filterable], allow: opts[:allow]
|
@@ -42,8 +51,19 @@ module Graphiti
|
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
54
|
+
def filter_group(filter_names, *args)
|
55
|
+
opts = args.extract_options!
|
56
|
+
|
57
|
+
Scoping::FilterGroupValidator.raise_unless_filter_group_requirement_valid!(self, opts[:required])
|
58
|
+
|
59
|
+
config[:grouped_filters] = {
|
60
|
+
names: filter_names,
|
61
|
+
required: opts[:required]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
45
65
|
def sort_all(&blk)
|
46
|
-
if
|
66
|
+
if blk
|
47
67
|
config[:_sort_all] = blk
|
48
68
|
else
|
49
69
|
config[:_sort_all]
|
@@ -55,7 +75,7 @@ module Graphiti
|
|
55
75
|
|
56
76
|
if get_attr(name, :sortable, raise_error: :only_unsupported)
|
57
77
|
config[:sorts][name] = {
|
58
|
-
proc: blk
|
78
|
+
proc: blk
|
59
79
|
}.merge(opts.slice(:only))
|
60
80
|
elsif (type = args[0])
|
61
81
|
attribute name, type, only: [:sortable]
|
@@ -78,7 +98,7 @@ module Graphiti
|
|
78
98
|
def default_filter(name = nil, &blk)
|
79
99
|
name ||= :__default
|
80
100
|
config[:default_filters][name.to_sym] = {
|
81
|
-
filter: blk
|
101
|
+
filter: blk
|
82
102
|
}
|
83
103
|
end
|
84
104
|
|
@@ -117,7 +137,7 @@ module Graphiti
|
|
117
137
|
options[:sortable] ? sort(name) : config[:sorts].delete(name)
|
118
138
|
|
119
139
|
if options[:filterable]
|
120
|
-
filter(name, allow: options[:allow])
|
140
|
+
filter(name, allow: options[:allow], via_attribute_dsl: true)
|
121
141
|
else
|
122
142
|
config[:filters].delete(name)
|
123
143
|
end
|
@@ -132,8 +152,10 @@ module Graphiti
|
|
132
152
|
writable: false,
|
133
153
|
sortable: false,
|
134
154
|
filterable: false,
|
155
|
+
schema: true
|
135
156
|
}
|
136
157
|
options = defaults.merge(options)
|
158
|
+
attribute_option(options, :readable)
|
137
159
|
config[:extra_attributes][name] = options
|
138
160
|
apply_extra_attributes_to_serializer
|
139
161
|
end
|
@@ -146,6 +168,10 @@ module Graphiti
|
|
146
168
|
end
|
147
169
|
end
|
148
170
|
|
171
|
+
def link(name, &blk)
|
172
|
+
config[:links][name.to_sym] = blk
|
173
|
+
end
|
174
|
+
|
149
175
|
def all_attributes
|
150
176
|
attributes.merge(extra_attributes)
|
151
177
|
end
|
@@ -163,9 +189,9 @@ module Graphiti
|
|
163
189
|
def attribute_option(options, name, exclusive = false)
|
164
190
|
if options[name] != false
|
165
191
|
default = if (only = options[:only]) && !exclusive
|
166
|
-
Array(only).include?(name)
|
192
|
+
Array(only).include?(name)
|
167
193
|
elsif (except = options[:except]) && !exclusive
|
168
|
-
Array(except).include?(name)
|
194
|
+
!Array(except).include?(name)
|
169
195
|
else
|
170
196
|
send(:"attributes_#{name}_by_default")
|
171
197
|
end
|
@@ -11,7 +11,7 @@ module Graphiti
|
|
11
11
|
|
12
12
|
# @api private
|
13
13
|
def _all(params, opts, base_scope)
|
14
|
-
runner = Runner.new(self, params, opts.delete(:query))
|
14
|
+
runner = Runner.new(self, params, opts.delete(:query), :all)
|
15
15
|
opts[:params] = params
|
16
16
|
runner.proxy(base_scope, opts)
|
17
17
|
end
|
@@ -23,11 +23,14 @@ module Graphiti
|
|
23
23
|
|
24
24
|
# @api private
|
25
25
|
def _find(params = {}, base_scope = nil)
|
26
|
+
guard_nil_id!(params[:data])
|
27
|
+
guard_nil_id!(params)
|
28
|
+
|
26
29
|
id = params[:data].try(:[], :id) || params.delete(:id)
|
27
30
|
params[:filter] ||= {}
|
28
31
|
params[:filter][:id] = id if id
|
29
32
|
|
30
|
-
runner = Runner.new(self, params)
|
33
|
+
runner = Runner.new(self, params, nil, :find)
|
31
34
|
runner.proxy base_scope,
|
32
35
|
single: true,
|
33
36
|
raise_on_missing: true,
|
@@ -43,7 +46,7 @@ module Graphiti
|
|
43
46
|
private
|
44
47
|
|
45
48
|
def validate!(params)
|
46
|
-
return
|
49
|
+
return if Graphiti.context[:graphql] || !validate_endpoints?
|
47
50
|
|
48
51
|
if context&.respond_to?(:request)
|
49
52
|
path = context.request.env["PATH_INFO"]
|
@@ -52,6 +55,13 @@ module Graphiti
|
|
52
55
|
end
|
53
56
|
end
|
54
57
|
end
|
58
|
+
|
59
|
+
def guard_nil_id!(params)
|
60
|
+
return unless params
|
61
|
+
if params.key?(:id) && params[:id].nil?
|
62
|
+
raise Errors::UndefinedIDLookup.new(self)
|
63
|
+
end
|
64
|
+
end
|
55
65
|
end
|
56
66
|
end
|
57
67
|
end
|
@@ -39,7 +39,7 @@ module Graphiti
|
|
39
39
|
path: path,
|
40
40
|
full_path: full_path_for(path),
|
41
41
|
url: url_for(path),
|
42
|
-
actions: DEFAULT_ACTIONS.dup
|
42
|
+
actions: DEFAULT_ACTIONS.dup
|
43
43
|
}
|
44
44
|
end
|
45
45
|
|
@@ -49,7 +49,7 @@ module Graphiti
|
|
49
49
|
path: path,
|
50
50
|
full_path: full_path_for(path),
|
51
51
|
url: url_for(path),
|
52
|
-
actions: actions
|
52
|
+
actions: actions
|
53
53
|
}
|
54
54
|
end
|
55
55
|
|
@@ -60,7 +60,7 @@ module Graphiti
|
|
60
60
|
path: path,
|
61
61
|
full_path: full_path_for(path),
|
62
62
|
url: url_for(path),
|
63
|
-
actions: actions
|
63
|
+
actions: actions
|
64
64
|
}]
|
65
65
|
end
|
66
66
|
|
@@ -89,7 +89,8 @@ module Graphiti
|
|
89
89
|
|
90
90
|
def update(update_params, meta = nil)
|
91
91
|
model_instance = nil
|
92
|
-
id = update_params
|
92
|
+
id = update_params[:id]
|
93
|
+
update_params = update_params.except(:id)
|
93
94
|
|
94
95
|
run_callbacks :persistence, :update, update_params, meta do
|
95
96
|
run_callbacks :attributes, :update, update_params, meta do |params|
|
@@ -42,9 +42,14 @@ module Graphiti
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def sideload(name)
|
45
|
-
|
45
|
+
if (split_on = name.to_s.split(/^on__/)).length > 1
|
46
|
+
on_type, name = split_on[1].split("--").map(&:to_sym)
|
47
|
+
end
|
48
|
+
|
49
|
+
sl = super(name)
|
46
50
|
if !polymorphic_child? && sl.nil?
|
47
51
|
children.each do |c|
|
52
|
+
next if on_type && c.type != on_type
|
48
53
|
break if (sl = c.sideloads[name])
|
49
54
|
end
|
50
55
|
end
|
@@ -67,7 +72,8 @@ module Graphiti
|
|
67
72
|
end
|
68
73
|
|
69
74
|
def resource_for_model(model)
|
70
|
-
resource = children.find { |c| model.
|
75
|
+
resource = children.find { |c| model.instance_of?(c.model) } ||
|
76
|
+
children.find { |c| model.is_a?(c.model) }
|
71
77
|
if resource.nil?
|
72
78
|
raise Errors::PolymorphicResourceChildNotFound.new(self, model: model)
|
73
79
|
else
|
@@ -19,7 +19,7 @@ module Graphiti
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def save(model, meta)
|
22
|
-
if meta[:attributes] == {} && meta[:method] == :update
|
22
|
+
if meta[:attributes].except(:id) == {} && meta[:method] == :update
|
23
23
|
model
|
24
24
|
else
|
25
25
|
raise Errors::RemoteWrite.new(self.class)
|
@@ -31,7 +31,7 @@ module Graphiti
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def before_resolve(scope, query)
|
34
|
-
scope[:params] = Util::RemoteParams.generate(self, query)
|
34
|
+
scope[:params] = Util::RemoteParams.generate(self, query, scope[:foreign_key])
|
35
35
|
scope
|
36
36
|
end
|
37
37
|
|
@@ -68,10 +68,10 @@ module Graphiti
|
|
68
68
|
model_ref = model
|
69
69
|
has_many name, opts do
|
70
70
|
params do |hash|
|
71
|
-
hash[:filter][:"#{as}_type"] = model_ref.name
|
71
|
+
hash[:filter][:"#{as}_type"] = { eql: model_ref.name }
|
72
72
|
end
|
73
73
|
|
74
|
-
instance_eval(&blk) if
|
74
|
+
instance_eval(&blk) if blk
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -82,10 +82,10 @@ module Graphiti
|
|
82
82
|
model_ref = model
|
83
83
|
has_one name, opts do
|
84
84
|
params do |hash|
|
85
|
-
hash[:filter][:"#{as}_type"] = model_ref.name
|
85
|
+
hash[:filter][:"#{as}_type"] = { eql: model_ref.name }
|
86
86
|
end
|
87
87
|
|
88
|
-
instance_eval(&blk) if
|
88
|
+
instance_eval(&blk) if blk
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
data/lib/graphiti/resource.rb
CHANGED
@@ -28,11 +28,12 @@ module Graphiti
|
|
28
28
|
serializer
|
29
29
|
end
|
30
30
|
|
31
|
-
def decorate_record(record)
|
31
|
+
def decorate_record(record, index = nil)
|
32
32
|
unless record.instance_variable_get(:@__graphiti_serializer)
|
33
33
|
serializer = serializer_for(record)
|
34
34
|
record.instance_variable_set(:@__graphiti_serializer, serializer)
|
35
35
|
record.instance_variable_set(:@__graphiti_resource, self)
|
36
|
+
record.instance_variable_set(:@__graphiti_index, index) if index
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -148,5 +149,15 @@ module Graphiti
|
|
148
149
|
end
|
149
150
|
response
|
150
151
|
end
|
152
|
+
|
153
|
+
def links?
|
154
|
+
self.class.links.any?
|
155
|
+
end
|
156
|
+
|
157
|
+
def links(model)
|
158
|
+
self.class.links.each_with_object({}) do |(name, blk), memo|
|
159
|
+
memo[name] = instance_exec(model, &blk)
|
160
|
+
end
|
161
|
+
end
|
151
162
|
end
|
152
163
|
end
|
@@ -47,10 +47,22 @@ module Graphiti
|
|
47
47
|
Renderer.new(self, options).to_json
|
48
48
|
end
|
49
49
|
|
50
|
+
def as_json(options = {})
|
51
|
+
Renderer.new(self, options).as_json
|
52
|
+
end
|
53
|
+
|
50
54
|
def to_xml(options = {})
|
51
55
|
Renderer.new(self, options).to_xml
|
52
56
|
end
|
53
57
|
|
58
|
+
def to_graphql(options = {})
|
59
|
+
Renderer.new(self, options).to_graphql
|
60
|
+
end
|
61
|
+
|
62
|
+
def as_graphql(options = {})
|
63
|
+
Renderer.new(self, options).as_graphql
|
64
|
+
end
|
65
|
+
|
54
66
|
def data
|
55
67
|
@data ||= begin
|
56
68
|
records = @scope.resolve
|
@@ -61,7 +73,7 @@ module Graphiti
|
|
61
73
|
records
|
62
74
|
end
|
63
75
|
end
|
64
|
-
|
76
|
+
alias_method :to_a, :data
|
65
77
|
|
66
78
|
def meta
|
67
79
|
@meta ||= data.respond_to?(:meta) ? data.meta : {}
|
@@ -73,9 +85,15 @@ module Graphiti
|
|
73
85
|
|
74
86
|
def stats
|
75
87
|
@stats ||= if @query.hash[:stats]
|
88
|
+
scope = @scope.unpaginated_object
|
89
|
+
if resource.adapter.can_group?
|
90
|
+
if (group = @query.hash[:stats].delete(:group_by))
|
91
|
+
scope = resource.adapter.group(scope, group[0])
|
92
|
+
end
|
93
|
+
end
|
76
94
|
payload = Stats::Payload.new @resource,
|
77
95
|
@query,
|
78
|
-
|
96
|
+
scope,
|
79
97
|
data
|
80
98
|
payload.generate
|
81
99
|
else
|
@@ -93,7 +111,7 @@ module Graphiti
|
|
93
111
|
original = Graphiti.context[:namespace]
|
94
112
|
begin
|
95
113
|
Graphiti.context[:namespace] = action
|
96
|
-
::Graphiti::RequestValidator.new(@resource, @payload.params).validate!
|
114
|
+
::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
|
97
115
|
validator = persist {
|
98
116
|
@resource.persist_with_relationships \
|
99
117
|
@payload.meta(action: action),
|
@@ -118,6 +136,7 @@ module Graphiti
|
|
118
136
|
end
|
119
137
|
|
120
138
|
def destroy
|
139
|
+
data
|
121
140
|
transaction_response = @resource.transaction do
|
122
141
|
metadata = {method: :destroy}
|
123
142
|
model = @resource.destroy(@query.filters[:id], metadata)
|
@@ -135,6 +154,7 @@ module Graphiti
|
|
135
154
|
end
|
136
155
|
|
137
156
|
def update_attributes
|
157
|
+
data
|
138
158
|
save(action: :update)
|
139
159
|
end
|
140
160
|
|
data/lib/graphiti/runner.rb
CHANGED
@@ -3,13 +3,13 @@ module Graphiti
|
|
3
3
|
attr_reader :params
|
4
4
|
attr_reader :deserialized_payload
|
5
5
|
|
6
|
-
def initialize(resource_class, params, query = nil)
|
6
|
+
def initialize(resource_class, params, query = nil, action = nil)
|
7
7
|
@resource_class = resource_class
|
8
8
|
@params = params
|
9
9
|
@query = query
|
10
|
+
@action = action
|
10
11
|
|
11
|
-
validator = RequestValidator.new(jsonapi_resource, params)
|
12
|
-
|
12
|
+
validator = RequestValidator.new(jsonapi_resource, params, action)
|
13
13
|
validator.validate!
|
14
14
|
|
15
15
|
@deserialized_payload = validator.deserialized_payload
|
@@ -30,7 +30,7 @@ module Graphiti
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def query
|
33
|
-
@query ||= Query.new(jsonapi_resource, params)
|
33
|
+
@query ||= Query.new(jsonapi_resource, params, nil, nil, [], @action)
|
34
34
|
end
|
35
35
|
|
36
36
|
def query_hash
|
@@ -50,7 +50,7 @@ module Graphiti
|
|
50
50
|
def jsonapi_render_options
|
51
51
|
options = {}
|
52
52
|
options.merge!(default_jsonapi_render_options)
|
53
|
-
options[:meta]
|
53
|
+
options[:meta] ||= {}
|
54
54
|
options[:expose] ||= {}
|
55
55
|
options[:expose][:context] = jsonapi_context
|
56
56
|
options
|
data/lib/graphiti/schema.rb
CHANGED
@@ -7,6 +7,7 @@ module Graphiti
|
|
7
7
|
::Rails.application.eager_load! if defined?(::Rails)
|
8
8
|
resources ||= Graphiti.resources.reject(&:abstract_class?)
|
9
9
|
resources.reject! { |r| r.name.nil? }
|
10
|
+
|
10
11
|
new(resources).generate
|
11
12
|
end
|
12
13
|
|
@@ -25,7 +26,7 @@ module Graphiti
|
|
25
26
|
|
26
27
|
def initialize(resources)
|
27
28
|
@resources = resources.sort_by(&:name)
|
28
|
-
@remote_resources = resources.select(&:remote?)
|
29
|
+
@remote_resources = @resources.select(&:remote?)
|
29
30
|
@local_resources = @resources - @remote_resources
|
30
31
|
end
|
31
32
|
|
@@ -33,7 +34,7 @@ module Graphiti
|
|
33
34
|
{
|
34
35
|
resources: generate_resources,
|
35
36
|
endpoints: generate_endpoints,
|
36
|
-
types: generate_types
|
37
|
+
types: generate_types
|
37
38
|
}
|
38
39
|
end
|
39
40
|
|
@@ -89,14 +90,20 @@ module Graphiti
|
|
89
90
|
config = {
|
90
91
|
name: r.name,
|
91
92
|
type: r.type.to_s,
|
93
|
+
graphql_entrypoint: r.graphql_entrypoint.to_s,
|
92
94
|
description: r.description,
|
93
95
|
attributes: attributes(r),
|
94
96
|
extra_attributes: extra_attributes(r),
|
95
97
|
sorts: sorts(r),
|
96
98
|
filters: filters(r),
|
97
99
|
relationships: relationships(r),
|
100
|
+
stats: stats(r)
|
98
101
|
}
|
99
102
|
|
103
|
+
if r.grouped_filters.any?
|
104
|
+
config[:filter_group] = r.grouped_filters
|
105
|
+
end
|
106
|
+
|
100
107
|
if r.default_sort
|
101
108
|
default_sort = r.default_sort.map { |s|
|
102
109
|
{s.keys.first.to_s => s.values.first.to_s}
|
@@ -108,7 +115,7 @@ module Graphiti
|
|
108
115
|
config[:default_page_size] = r.default_page_size
|
109
116
|
end
|
110
117
|
|
111
|
-
if r.polymorphic?
|
118
|
+
if r.polymorphic? && !r.polymorphic_child?
|
112
119
|
config[:polymorphic] = true
|
113
120
|
config[:children] = r.children.map(&:name)
|
114
121
|
end
|
@@ -121,7 +128,7 @@ module Graphiti
|
|
121
128
|
name: r.name,
|
122
129
|
description: r.description,
|
123
130
|
remote: r.remote_url,
|
124
|
-
relationships: relationships(r)
|
131
|
+
relationships: relationships(r)
|
125
132
|
}
|
126
133
|
}
|
127
134
|
|
@@ -136,7 +143,7 @@ module Graphiti
|
|
136
143
|
type: config[:type].to_s,
|
137
144
|
readable: flag(config[:readable]),
|
138
145
|
writable: flag(config[:writable]),
|
139
|
-
description: resource.attribute_description(name)
|
146
|
+
description: resource.attribute_description(name)
|
140
147
|
}
|
141
148
|
end
|
142
149
|
end
|
@@ -146,10 +153,12 @@ module Graphiti
|
|
146
153
|
def extra_attributes(resource)
|
147
154
|
{}.tap do |attrs|
|
148
155
|
resource.extra_attributes.each_pair do |name, config|
|
156
|
+
next unless config[:schema]
|
157
|
+
|
149
158
|
attrs[name] = {
|
150
159
|
type: config[:type].to_s,
|
151
160
|
readable: flag(config[:readable]),
|
152
|
-
description: resource.attribute_description(name)
|
161
|
+
description: resource.attribute_description(name)
|
153
162
|
}
|
154
163
|
end
|
155
164
|
end
|
@@ -163,14 +172,22 @@ module Graphiti
|
|
163
172
|
end
|
164
173
|
end
|
165
174
|
|
175
|
+
def stats(resource)
|
176
|
+
{}.tap do |stats|
|
177
|
+
resource.stats.each_pair do |name, config|
|
178
|
+
stats[name] = config.calculations.keys
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
166
183
|
def sorts(resource)
|
167
184
|
{}.tap do |s|
|
168
185
|
resource.sorts.each_pair do |name, sort|
|
169
|
-
|
186
|
+
attr = resource.all_attributes[name]
|
187
|
+
next unless attr[:schema]
|
170
188
|
|
171
189
|
config = {}
|
172
190
|
config[:only] = sort[:only] if sort[:only]
|
173
|
-
attr = resource.attributes[name]
|
174
191
|
if attr[:sortable].is_a?(Symbol)
|
175
192
|
config[:guard] = true
|
176
193
|
end
|
@@ -182,11 +199,11 @@ module Graphiti
|
|
182
199
|
def filters(resource)
|
183
200
|
{}.tap do |f|
|
184
201
|
resource.filters.each_pair do |name, filter|
|
185
|
-
next unless resource.
|
202
|
+
next unless resource.filters[name][:schema]
|
186
203
|
|
187
204
|
config = {
|
188
205
|
type: filter[:type].to_s,
|
189
|
-
operators: filter[:operators].keys.map(&:to_s)
|
206
|
+
operators: filter[:operators].keys.map(&:to_s)
|
190
207
|
}
|
191
208
|
|
192
209
|
config[:single] = true if filter[:single]
|
@@ -194,7 +211,7 @@ module Graphiti
|
|
194
211
|
config[:deny] = filter[:deny].map(&:to_s) if filter[:deny]
|
195
212
|
config[:dependencies] = filter[:dependencies].map(&:to_s) if filter[:dependencies]
|
196
213
|
|
197
|
-
attr = resource.
|
214
|
+
attr = resource.all_attributes[name]
|
198
215
|
if attr[:filterable].is_a?(Symbol)
|
199
216
|
if attr[:filterable] == :required
|
200
217
|
config[:required] = true
|
@@ -202,11 +219,18 @@ module Graphiti
|
|
202
219
|
config[:guard] = true
|
203
220
|
end
|
204
221
|
end
|
222
|
+
if filter[:required] # one-off filter, not attribute
|
223
|
+
config[:required] = true
|
224
|
+
end
|
205
225
|
f[name] = config
|
206
226
|
end
|
207
227
|
end
|
208
228
|
end
|
209
229
|
|
230
|
+
def filter_group(resource)
|
231
|
+
resource.config[:grouped_filters]
|
232
|
+
end
|
233
|
+
|
210
234
|
def relationships(resource)
|
211
235
|
{}.tap do |r|
|
212
236
|
resource.sideloads.each_pair do |name, config|
|
@@ -214,6 +238,7 @@ module Graphiti
|
|
214
238
|
if config.type == :polymorphic_belongs_to
|
215
239
|
schema[:resources] = config.children.values
|
216
240
|
.map(&:resource).map(&:class).map(&:name)
|
241
|
+
schema[:parent_resource] = config.parent_resource.class.name
|
217
242
|
else
|
218
243
|
schema[:resource] = config.resource.class.name
|
219
244
|
end
|
data/lib/graphiti/schema_diff.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class SchemaDiff
|
3
3
|
def initialize(old, new)
|
4
|
-
@old = old.deep_symbolize_keys
|
5
|
-
@new = new.deep_symbolize_keys
|
4
|
+
@old = JSON.parse(old.to_json).deep_symbolize_keys
|
5
|
+
@new = JSON.parse(new.to_json).deep_symbolize_keys
|
6
6
|
@errors = []
|
7
7
|
end
|
8
8
|
|
@@ -30,6 +30,8 @@ module Graphiti
|
|
30
30
|
compare_extra_attributes(r, new_resource)
|
31
31
|
compare_sorts(r, new_resource)
|
32
32
|
compare_filters(r, new_resource)
|
33
|
+
compare_filter_group(r, new_resource)
|
34
|
+
compare_stats(r, new_resource)
|
33
35
|
compare_relationships(r, new_resource)
|
34
36
|
end
|
35
37
|
end
|
@@ -133,12 +135,12 @@ module Graphiti
|
|
133
135
|
end
|
134
136
|
|
135
137
|
if new_sort[:only] && !old_sort[:only]
|
136
|
-
@errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].inspect}."
|
138
|
+
@errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].to_sym.inspect}."
|
137
139
|
end
|
138
140
|
|
139
141
|
if new_sort[:only] && old_sort[:only]
|
140
142
|
if new_sort[:only] != old_sort[:only]
|
141
|
-
@errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].inspect}, now limited to only #{new_sort[:only].inspect}."
|
143
|
+
@errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].to_sym.inspect}, now limited to only #{new_sort[:only].to_sym.inspect}."
|
142
144
|
end
|
143
145
|
end
|
144
146
|
end
|
@@ -204,6 +206,44 @@ module Graphiti
|
|
204
206
|
end
|
205
207
|
end
|
206
208
|
|
209
|
+
def compare_filter_group(old_resource, new_resource)
|
210
|
+
if new_resource[:filter_group]
|
211
|
+
if old_resource[:filter_group]
|
212
|
+
new_names = new_resource[:filter_group][:names]
|
213
|
+
old_names = old_resource[:filter_group][:names]
|
214
|
+
diff = new_names - old_names
|
215
|
+
if !diff.empty? && new_resource[:filter_group][:required] == "all"
|
216
|
+
@errors << "#{old_resource[:name]}: all required filter group #{old_names.map(&:to_sym).inspect} added #{"member".pluralize(diff.length)} #{diff.map(&:to_sym).inspect}."
|
217
|
+
end
|
218
|
+
|
219
|
+
old_required = old_resource[:filter_group][:required]
|
220
|
+
new_required = new_resource[:filter_group][:required]
|
221
|
+
if old_required == "any" && new_required == "all"
|
222
|
+
@errors << "#{old_resource[:name]}: filter group #{old_names.map(&:to_sym).inspect} moved from required: :any to required: :all"
|
223
|
+
end
|
224
|
+
else
|
225
|
+
@errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].map(&:to_sym).inspect} was added."
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def compare_stats(old_resource, new_resource)
|
231
|
+
return unless old_resource.key?(:stats)
|
232
|
+
|
233
|
+
old_resource[:stats].each_pair do |name, old_calculations|
|
234
|
+
new_calculations = new_resource[:stats][name]
|
235
|
+
if new_calculations
|
236
|
+
old_calculations.each do |calc|
|
237
|
+
unless new_calculations.include?(calc)
|
238
|
+
@errors << "#{old_resource[:name]}: calculation #{calc.to_sym.inspect} was removed from stat #{name.inspect}."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
@errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
207
247
|
def compare_endpoints
|
208
248
|
@old[:endpoints].each_pair do |path, old_endpoint|
|
209
249
|
unless (new_endpoint = @new[:endpoints][path])
|