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
@@ -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])
|