graphiti 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/graphiti/adapters/active_record.rb +12 -1
- data/lib/graphiti/adapters/persistence/associations.rb +5 -1
- data/lib/graphiti/configuration.rb +1 -4
- data/lib/graphiti/debugger.rb +6 -5
- data/lib/graphiti/delegates/pagination.rb +1 -1
- data/lib/graphiti/query.rb +3 -4
- data/lib/graphiti/rails/controller.rb +4 -0
- data/lib/graphiti/rails.rb +2 -0
- data/lib/graphiti/resource_proxy.rb +1 -0
- data/lib/graphiti/schema.rb +5 -1
- data/lib/graphiti/spec_helpers/rspec.rb +1 -1
- data/lib/graphiti/types.rb +6 -0
- data/lib/graphiti/version.rb +1 -1
- data/lib/tasks/graphiti.rake +7 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d65d4265eb6a753309e1348e135296d3c114b0497ceadd09ccc0f626d0fcaad
|
|
4
|
+
data.tar.gz: c8e52211ff147a9607360ce19e2351fe10e5a670481e1d20cf2625034f761ed8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8c3fcadbaba93c23bdf505937443432abc2e0b120a2baadd76279c20f7ba0382798a416dd83832e4ad0e9697e5cb20570df93c0747eea5c832782a1cf442865
|
|
7
|
+
data.tar.gz: c3f790bed7b1cba2802e5a10b9c9f51c53ccc44e9da9823956266361f80a4af0df790d59ebe951f97768b4ba80da7a002ae919cc739225ed22cec8084e4292ce
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
graphiti changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.2](https://github.com/graphiti-api/graphiti/compare/v2.0.1...v2.0.2) (2026-09-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* stats[group_by] only groups by an attribute the resource reads ([5b52b36](https://github.com/graphiti-api/graphiti/commit/5b52b368de3a8fd94bfdaf61d05e0dd2e487bd92))
|
|
9
|
+
|
|
10
|
+
## [2.0.1](https://github.com/graphiti-api/graphiti/compare/v2.0.0...v2.0.1) (2026-09-02)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* count distinct on the primary key rather than every column ([be85c18](https://github.com/graphiti-api/graphiti/commit/be85c18b695bf482f95ae72496600b40e3531c44)), closes [#439](https://github.com/graphiti-api/graphiti/issues/439)
|
|
16
|
+
* keep the request body out of pagination links ([64040f2](https://github.com/graphiti-api/graphiti/commit/64040f200c307062d0b04204013e35963cec4ceb)), closes [#369](https://github.com/graphiti-api/graphiti/issues/369)
|
|
17
|
+
* read flags through one coercion ([72a39a7](https://github.com/graphiti-api/graphiti/commit/72a39a7c126e2d6d89fb4a829cad20efd207df4d))
|
|
18
|
+
* respect polymorphic_name when sideposting ([45d9eb7](https://github.com/graphiti-api/graphiti/commit/45d9eb73b1dba2e4587ee67387f0c3117c9c4fcf)), closes [#437](https://github.com/graphiti-api/graphiti/issues/437)
|
|
19
|
+
* stop the debugger requiring an id on every model ([8111b68](https://github.com/graphiti-api/graphiti/commit/8111b6897bd735bed84a63c312a6a01cf69317a1)), closes [#152](https://github.com/graphiti-api/graphiti/issues/152)
|
|
20
|
+
|
|
3
21
|
# [2.0.0](https://github.com/graphiti-api/graphiti/compare/v1.13.3...v2.0.0) (2026-09-01)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -198,7 +198,7 @@ module Graphiti
|
|
|
198
198
|
# (see Adapters::Abstract#count)
|
|
199
199
|
def count(scope, attr)
|
|
200
200
|
if attr.to_sym == :total
|
|
201
|
-
scope.distinct.count(
|
|
201
|
+
scope.distinct.count(distinct_count_column(scope))
|
|
202
202
|
else
|
|
203
203
|
scope.distinct.count(attr)
|
|
204
204
|
end
|
|
@@ -281,6 +281,10 @@ module Graphiti
|
|
|
281
281
|
# Nothing to do in the else case, happened when we merged foreign key
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
+
def polymorphic_type_value(parent_object)
|
|
285
|
+
parent_object.class.polymorphic_name
|
|
286
|
+
end
|
|
287
|
+
|
|
284
288
|
def save(model_instance)
|
|
285
289
|
model_instance.save
|
|
286
290
|
model_instance
|
|
@@ -309,6 +313,13 @@ module Graphiti
|
|
|
309
313
|
|
|
310
314
|
private
|
|
311
315
|
|
|
316
|
+
def distinct_count_column(scope)
|
|
317
|
+
primary_key = scope.klass.primary_key
|
|
318
|
+
return :all unless primary_key.is_a?(String)
|
|
319
|
+
|
|
320
|
+
scope.klass.arel_table[primary_key]
|
|
321
|
+
end
|
|
322
|
+
|
|
312
323
|
def column_for(scope, name)
|
|
313
324
|
table = scope.klass.arel_table
|
|
314
325
|
if (other = scope.attribute_alias(name))
|
|
@@ -61,13 +61,17 @@ module Graphiti
|
|
|
61
61
|
update_foreign_type(attrs, x, null: true) if x[:is_polymorphic]
|
|
62
62
|
else
|
|
63
63
|
if x[:sideload].polymorphic_has_one? || x[:sideload].polymorphic_has_many?
|
|
64
|
-
attrs[:"#{x[:sideload].polymorphic_as}_type"] = parent_object
|
|
64
|
+
attrs[:"#{x[:sideload].polymorphic_as}_type"] = polymorphic_type_value(parent_object)
|
|
65
65
|
end
|
|
66
66
|
attrs[x[:foreign_key]] = parent_object.send(x[:primary_key])
|
|
67
67
|
update_foreign_type(attrs, x) if x[:is_polymorphic]
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def polymorphic_type_value(parent_object)
|
|
72
|
+
parent_object.class.name
|
|
73
|
+
end
|
|
74
|
+
|
|
71
75
|
def update_foreign_type(attrs, x, null: false)
|
|
72
76
|
grouping_field = x[:sideload].parent.grouper.field_name
|
|
73
77
|
attrs[grouping_field] = null ? nil : x[:sideload].group_name
|
|
@@ -72,11 +72,8 @@ module Graphiti
|
|
|
72
72
|
Debugger.debug_models = @debug_models
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
# every value comes back from ENV as a string and "false" is truthy
|
|
76
75
|
def coerce_flag(val)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
!!val
|
|
76
|
+
Types.flag(val)
|
|
80
77
|
end
|
|
81
78
|
|
|
82
79
|
def with_option(key, value)
|
data/lib/graphiti/debugger.rb
CHANGED
|
@@ -63,12 +63,14 @@ module Graphiti
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
private def results(raw_results)
|
|
66
|
-
raw_results.map { |r|
|
|
66
|
+
raw_results.map { |r|
|
|
67
|
+
identifier = r.respond_to?(:id) ? r.id.inspect : "no id"
|
|
68
|
+
"[#{r.class.name}, #{identifier}]"
|
|
69
|
+
}.join(", ")
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
private def on_sideload_data(payload, params, took)
|
|
70
73
|
sideload = payload[:sideload]
|
|
71
|
-
results = results(payload[:results])
|
|
72
74
|
add_chunk(payload[:resource], payload[:parent]) do |logs, json|
|
|
73
75
|
logs << [" \\_ #{sideload.name}", :yellow, true]
|
|
74
76
|
json[:name] = sideload.name
|
|
@@ -79,14 +81,13 @@ module Graphiti
|
|
|
79
81
|
end
|
|
80
82
|
logs << [" #{query}", :cyan, true]
|
|
81
83
|
json[:query] = query
|
|
82
|
-
logs << [" Returned Models: #{results}"] if debug_models
|
|
84
|
+
logs << [" Returned Models: #{results(payload[:results])}"] if debug_models
|
|
83
85
|
logs << [" Took: #{took}ms", :magenta, true]
|
|
84
86
|
json[:took] = took
|
|
85
87
|
end
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
private def on_primary_data(payload, params, took)
|
|
89
|
-
results = results(payload[:results])
|
|
90
91
|
add_chunk(payload[:resource], payload[:parent]) do |logs, json|
|
|
91
92
|
logs << [""]
|
|
92
93
|
logs << ["=== Graphiti Debug", :green, true]
|
|
@@ -96,7 +97,7 @@ module Graphiti
|
|
|
96
97
|
query = "#{payload[:resource].class.name}.#{payload[:action]}(#{params.inspect})"
|
|
97
98
|
logs << [query, :cyan, true]
|
|
98
99
|
json[:query] = query
|
|
99
|
-
logs << ["Returned Models: #{results}"] if debug_models
|
|
100
|
+
logs << ["Returned Models: #{results(payload[:results])}"] if debug_models
|
|
100
101
|
logs << ["Took: #{took}ms", :magenta, true]
|
|
101
102
|
json[:took] = took
|
|
102
103
|
end
|
|
@@ -32,7 +32,7 @@ module Graphiti
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def pagination_params
|
|
35
|
-
@pagination_params ||= @proxy.query.params.except(:action, :controller, :format)
|
|
35
|
+
@pagination_params ||= @proxy.query.params.except(:action, :controller, :format, :data, :included)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def pagination_link(page)
|
data/lib/graphiti/query.rb
CHANGED
|
@@ -2,7 +2,6 @@ require "digest"
|
|
|
2
2
|
|
|
3
3
|
module Graphiti
|
|
4
4
|
class Query
|
|
5
|
-
TRUTHY_LINKS = [true, "true"].freeze
|
|
6
5
|
LINKLESS_FORMATS = [:json, :xml, "json", "xml"].freeze
|
|
7
6
|
|
|
8
7
|
attr_reader :resource, :association_name, :params, :action
|
|
@@ -80,7 +79,7 @@ module Graphiti
|
|
|
80
79
|
def links_requested?
|
|
81
80
|
return @links_requested if defined?(@links_requested)
|
|
82
81
|
|
|
83
|
-
@links_requested =
|
|
82
|
+
@links_requested = Types.flag(@params[:links])
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
def suppress_links?
|
|
@@ -107,11 +106,11 @@ module Graphiti
|
|
|
107
106
|
|
|
108
107
|
def page_links_requested?
|
|
109
108
|
[@params[:page_links], @params[:pagination_links]]
|
|
110
|
-
.any? { |value|
|
|
109
|
+
.any? { |value| Types.flag(value) }
|
|
111
110
|
end
|
|
112
111
|
|
|
113
112
|
def debug_requested?
|
|
114
|
-
|
|
113
|
+
Types.flag(@params[:debug])
|
|
115
114
|
end
|
|
116
115
|
|
|
117
116
|
def hash
|
|
@@ -36,6 +36,10 @@ module Graphiti
|
|
|
36
36
|
status: 400, detail: :exception, handler: Graphiti::Rails::ExceptionHandler
|
|
37
37
|
register_exception Graphiti::Errors::InvalidInclude,
|
|
38
38
|
status: 400, detail: :exception, handler: Graphiti::Rails::ExceptionHandler
|
|
39
|
+
register_exception Graphiti::Errors::UnknownAttribute,
|
|
40
|
+
status: 400, detail: :exception, handler: Graphiti::Rails::ExceptionHandler
|
|
41
|
+
register_exception Graphiti::Errors::InvalidAttributeAccess,
|
|
42
|
+
status: 400, detail: :exception, handler: Graphiti::Rails::ExceptionHandler
|
|
39
43
|
register_exception Graphiti::Errors::UnsupportedSort,
|
|
40
44
|
status: 400, detail: :exception, handler: Graphiti::Rails::ExceptionHandler
|
|
41
45
|
register_exception Graphiti::Errors::UnsupportedOperator,
|
data/lib/graphiti/rails.rb
CHANGED
|
@@ -52,6 +52,8 @@ module Graphiti
|
|
|
52
52
|
"Graphiti::Errors::UnsupportedBeforeCursor" => :bad_request,
|
|
53
53
|
"Graphiti::Errors::UnsupportedPagination" => :bad_request,
|
|
54
54
|
"Graphiti::Errors::FilterGroupMissingRequiredFilters" => :bad_request,
|
|
55
|
+
"Graphiti::Errors::UnknownAttribute" => :bad_request,
|
|
56
|
+
"Graphiti::Errors::InvalidAttributeAccess" => :bad_request,
|
|
55
57
|
"Graphiti::Errors::RecordNotFound" => :not_found,
|
|
56
58
|
"Graphiti::Errors::ConflictRequest" => :conflict
|
|
57
59
|
}.freeze
|
|
@@ -125,6 +125,7 @@ module Graphiti
|
|
|
125
125
|
scope = @scope.unpaginated_object
|
|
126
126
|
if resource.adapter.can_group?
|
|
127
127
|
if (group = @query.hash[:stats].delete(:group_by))
|
|
128
|
+
resource.get_attr!(group[0], :readable, request: true)
|
|
128
129
|
scope = resource.adapter.group(scope, group[0])
|
|
129
130
|
end
|
|
130
131
|
end
|
data/lib/graphiti/schema.rb
CHANGED
|
@@ -11,7 +11,11 @@ module Graphiti
|
|
|
11
11
|
new(resources).generate
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def self.
|
|
14
|
+
def self.forced?
|
|
15
|
+
Types.flag(ENV["FORCE_SCHEMA"])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.generate!(resources = nil, path: Graphiti.config.schema_path, force: forced?)
|
|
15
19
|
result = check(resources, path: path)
|
|
16
20
|
return result.errors if !force && !result.compatible?
|
|
17
21
|
|
|
@@ -131,7 +131,7 @@ module Graphiti
|
|
|
131
131
|
::RSpec.describe "Graphiti Schema" do
|
|
132
132
|
it "generates a backwards-compatible schema" do
|
|
133
133
|
check = Graphiti::Schema.check(resources, path: path || Graphiti.config.schema_path)
|
|
134
|
-
forced =
|
|
134
|
+
forced = Graphiti::Schema.forced?
|
|
135
135
|
check.write! if check.compatible? || forced
|
|
136
136
|
|
|
137
137
|
expect(check.compatible? || forced).to eq(true), check.message
|
data/lib/graphiti/types.rb
CHANGED
|
@@ -222,6 +222,12 @@ module Graphiti
|
|
|
222
222
|
map[key.to_sym] = value
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
+
def self.flag(value)
|
|
226
|
+
self[:boolean][:params].call(value.to_s.strip)
|
|
227
|
+
rescue Dry::Types::CoercionError
|
|
228
|
+
false
|
|
229
|
+
end
|
|
230
|
+
|
|
225
231
|
def self.name_for(key)
|
|
226
232
|
key = key.to_sym
|
|
227
233
|
type = map[key]
|
data/lib/graphiti/version.rb
CHANGED
data/lib/tasks/graphiti.rake
CHANGED
|
@@ -9,7 +9,7 @@ namespace :graphiti do
|
|
|
9
9
|
Graphiti.logger = Graphiti.stdout_logger
|
|
10
10
|
Graphiti::Debugger.preserve = true
|
|
11
11
|
require "pp"
|
|
12
|
-
path, debug = args[:path], args[:debug]
|
|
12
|
+
path, debug = args[:path], Graphiti::Types.flag(args[:debug])
|
|
13
13
|
puts "Graphiti Request: #{path}"
|
|
14
14
|
json = helpers.make_request(path, debug)
|
|
15
15
|
pp json
|
|
@@ -20,7 +20,7 @@ namespace :graphiti do
|
|
|
20
20
|
desc "Write the schema file. Refuses backwards-incompatible changes unless FORCE_SCHEMA=true. Takes an optional path, defaulting to Graphiti.config.schema_path."
|
|
21
21
|
task :generate, [:path] => [:environment] do |_, args|
|
|
22
22
|
check = Graphiti::Schema.check(path: helpers.schema_path(args[:path]))
|
|
23
|
-
abort check.message unless check.compatible? ||
|
|
23
|
+
abort check.message unless check.compatible? || Graphiti::Schema.forced?
|
|
24
24
|
|
|
25
25
|
puts "Schema written: #{check.write!}"
|
|
26
26
|
end
|
|
@@ -49,12 +49,15 @@ namespace :graphiti do
|
|
|
49
49
|
|
|
50
50
|
desc "Execute benchmark without web server."
|
|
51
51
|
task :benchmark, [:path, :requests] => [:environment] do |_, args|
|
|
52
|
+
requests = args[:requests].to_i
|
|
53
|
+
abort "Needs a request count, as in graphiti:benchmark[/path,100]" unless requests > 0
|
|
54
|
+
|
|
52
55
|
helpers.setup_rails!
|
|
53
56
|
took = Benchmark.ms {
|
|
54
|
-
|
|
57
|
+
requests.times do
|
|
55
58
|
helpers.make_request(args[:path])
|
|
56
59
|
end
|
|
57
60
|
}
|
|
58
|
-
puts "Took: #{(took /
|
|
61
|
+
puts "Took: #{(took / requests).round(2)}ms"
|
|
59
62
|
end
|
|
60
63
|
end
|