graphiti 2.0.0.beta.8 → 2.0.0.beta.10
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 +1 -1
- data/.github/workflows/docs.yml +9 -6
- data/.github/workflows/release.yml +1 -1
- data/CHANGELOG.md +43 -0
- data/Rakefile +28 -0
- data/UPGRADING.md +1 -1
- data/lib/generators/graphiti/generator_mixin.rb +24 -0
- data/lib/generators/graphiti/templates/application_resource.rb.erb +9 -16
- data/lib/graphiti/configuration.rb +66 -28
- data/lib/graphiti/debugger.rb +18 -6
- data/lib/graphiti/delegates/pagination.rb +2 -2
- data/lib/graphiti/error_serializers/deprecated_constants.rb +23 -0
- data/lib/graphiti/error_serializers/invalid_request.rb +6 -0
- data/lib/graphiti/errors.rb +51 -3
- data/lib/graphiti/query.rb +36 -14
- data/lib/graphiti/rails/controller.rb +26 -6
- data/lib/graphiti/rails/rake_helpers.rb +17 -0
- data/lib/graphiti/rails.rb +14 -0
- data/lib/graphiti/railtie.rb +8 -0
- data/lib/graphiti/request_validators/update_validator.rb +1 -1
- data/lib/graphiti/request_validators/validator.rb +9 -0
- data/lib/graphiti/resource/configuration.rb +138 -40
- data/lib/graphiti/resource/dsl.rb +21 -2
- data/lib/graphiti/resource/interface.rb +1 -1
- data/lib/graphiti/resource/links.rb +36 -5
- data/lib/graphiti/resource/remote.rb +2 -1
- data/lib/graphiti/resource/sideloading.rb +2 -2
- data/lib/graphiti/schema.rb +2 -2
- data/lib/graphiti/scope.rb +115 -23
- data/lib/graphiti/scoping/filter.rb +4 -4
- data/lib/graphiti/scoping/paginate.rb +3 -3
- data/lib/graphiti/serializer.rb +12 -6
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +11 -4
- data/lib/graphiti/sideload.rb +22 -14
- data/lib/graphiti/spec_helpers/matchers.rb +2 -2
- data/lib/graphiti/spec_helpers/rspec.rb +6 -3
- data/lib/graphiti/util/link.rb +1 -1
- data/lib/graphiti/util/serializer_attributes.rb +5 -3
- data/lib/graphiti/util/serializer_relationships.rb +26 -16
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +6 -1
- data/lib/tasks/graphiti.rake +4 -0
- data/package-lock.json +114 -0
- data/package.json +16 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1e331ab2dea47987ea47260eb0890c9a953d3435bba2e7ef37cbfbbc83fe5ab
|
|
4
|
+
data.tar.gz: 13a7483ead9f4ad5ed69390ff6a4a122a819813c2147c0a56dceebdfebc57e17
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cca252389b52b5a2983407b4eff7cf7fe8b49759b478228f9d78303522bc40f2948a1784d6c863373f10521dc80b61d35f201009e830d8a968cc989b37d2ad81
|
|
7
|
+
data.tar.gz: 3fdfda722d09ddea6c366ca4102258bdea6d36c923be729ae141063e7e62b43ae0730f30a6422ae4448f858a5c1d4269f80180279d7939ce050c88008613cedb
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -139,7 +139,7 @@ jobs:
|
|
|
139
139
|
# Only main auto-releases. Beta releases are cut by manually running
|
|
140
140
|
# "Generate New Release" with the beta branch selected.
|
|
141
141
|
if: github.ref == 'refs/heads/main'
|
|
142
|
-
needs: [test]
|
|
142
|
+
needs: [lint, test]
|
|
143
143
|
steps:
|
|
144
144
|
- name: Dispatch Release
|
|
145
145
|
uses: benc-uk/workflow-dispatch@v1
|
data/.github/workflows/docs.yml
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
name: Docs
|
|
2
2
|
|
|
3
|
-
# Publishes the documentation site from beta
|
|
4
|
-
# repo via website/static/CNAME, so this
|
|
5
|
-
#
|
|
6
|
-
# and graphiti-api.github.io no
|
|
3
|
+
# Publishes the documentation site from beta, and from main once 2.0 lands
|
|
4
|
+
# there. graphiti.dev now points at this repo via website/static/CNAME, so this
|
|
5
|
+
# deploy is the live site: the 1.x Jekyll site is frozen under
|
|
6
|
+
# website/static/1.13 and ships with every build, and graphiti-api.github.io no
|
|
7
|
+
# longer serves the domain.
|
|
7
8
|
on:
|
|
8
9
|
push:
|
|
9
|
-
branches: [beta]
|
|
10
|
+
branches: [beta, main]
|
|
10
11
|
paths:
|
|
11
12
|
- 'docs/**'
|
|
12
13
|
- 'website/**'
|
|
13
14
|
- '.github/workflows/docs.yml'
|
|
14
15
|
workflow_dispatch: {}
|
|
15
16
|
|
|
17
|
+
# One static group: deploys from different branches force-push the same
|
|
18
|
+
# gh-pages branch, so they must not interleave.
|
|
16
19
|
concurrency:
|
|
17
|
-
group: docs
|
|
20
|
+
group: docs
|
|
18
21
|
cancel-in-progress: true
|
|
19
22
|
|
|
20
23
|
permissions:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
graphiti changelog
|
|
2
2
|
|
|
3
|
+
# [2.0.0-beta.10](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.9...v2.0.0-beta.10) (2026-08-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* apply sideloads at definition once setup! has run ([39ef393](https://github.com/graphiti-api/graphiti/commit/39ef39383520816319282854c0ae99677bcd5127)), closes [#524](https://github.com/graphiti-api/graphiti/issues/524)
|
|
9
|
+
* give client-caused errors real detail and stop 500ing on bad query params ([35acc3e](https://github.com/graphiti-api/graphiti/commit/35acc3e6c78f68eeeb05728ea16264b136455c30)), closes [#531](https://github.com/graphiti-api/graphiti/issues/531)
|
|
10
|
+
* let an abstract resource declare relationships ([6036392](https://github.com/graphiti-api/graphiti/commit/6036392ebd611c7ed492beb3fe5754433eac9ffd)), closes [#453](https://github.com/graphiti-api/graphiti/issues/453)
|
|
11
|
+
* reject a non-object data payload instead of raising TypeError ([51603b8](https://github.com/graphiti-api/graphiti/commit/51603b854270715b80a75d9aa05a4020e6bbdce5))
|
|
12
|
+
* Update some missing sections and terms after renames, register unsupported pagination error ([39cff19](https://github.com/graphiti-api/graphiti/commit/39cff19f6f1f73126514c8b18234a18ad5eb7379))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* carry CurrentAttributes into concurrent sideloads ([7092096](https://github.com/graphiti-api/graphiti/commit/7092096f6d99ad277cc56817c616d946f655d55b))
|
|
18
|
+
* fold allow_nil and deny_empty into one blanks: filter option ([a108403](https://github.com/graphiti-api/graphiti/commit/a108403e5cd87fd2fdf177da30c4c4f97b9fe750))
|
|
19
|
+
* move link rendering onto the resource as page_links and relationship_links ([57878d1](https://github.com/graphiti-api/graphiti/commit/57878d14fdfc4741e8d418c8edbc4a8687335843))
|
|
20
|
+
* move typecast_reads onto the resource and let the railtie own Rails config ([236e970](https://github.com/graphiti-api/graphiti/commit/236e9706d7d8f08e9640c5c22ac596e2f7466849))
|
|
21
|
+
* one link mode per relationship, cascading from relationship_links ([f0e1da1](https://github.com/graphiti-api/graphiti/commit/f0e1da1e13a8d12b47458495b378779f5687ffe2))
|
|
22
|
+
* point connection pool timeouts at the sizing formula and add an audit task to check ([6e017c6](https://github.com/graphiti-api/graphiti/commit/6e017c60fd45338e4e6c8527adebfd46be4bfd8d))
|
|
23
|
+
* split validate_endpoints into validate_requests and validate_links ([100a370](https://github.com/graphiti-api/graphiti/commit/100a370deec6a0e6ae165bdf77e50b6b14d068d3))
|
|
24
|
+
|
|
25
|
+
# [2.0.0-beta.9](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2026-08-22)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* bridge the last 1.x names that died with a bare NameError ([34e1dbd](https://github.com/graphiti-api/graphiti/commit/34e1dbd3e34bb8e6be56292c51fcb32674f75995))
|
|
31
|
+
* build the entity map when the root query is, not on first use ([134d18b](https://github.com/graphiti-api/graphiti/commit/134d18b12b9ebb6903cca813209f60e5626b25a6))
|
|
32
|
+
* coerce debug flags from env strings ([46be6aa](https://github.com/graphiti-api/graphiti/commit/46be6aa05d4e2a9e5d6b2562f223b4b05582bb39))
|
|
33
|
+
* keep a customized sideload's instances out of entity dedup ([b1beee3](https://github.com/graphiti-api/graphiti/commit/b1beee3288fae2b20b009983aaa2f6c71189e2ca))
|
|
34
|
+
* raise a more helpful named error when belongs_to linkage reads an unselected foreign key ([f59ea0b](https://github.com/graphiti-api/graphiti/commit/f59ea0bda94b388b1c2c0ae0e50b258f8ce0eb11))
|
|
35
|
+
* resolve inline when already on a sideload pool thread ([11c9de7](https://github.com/graphiti-api/graphiti/commit/11c9de7fb4bfc2d83b02aaba3e58c1f94c84cec4))
|
|
36
|
+
* scope debugger chunks to the request rather than the class ([9a77896](https://github.com/graphiti-api/graphiti/commit/9a77896c6e56d23b89d0a2aef358d8e8e0c28c54))
|
|
37
|
+
* surface the first child error from concurrent polymorphic sideloads ([5a08c0e](https://github.com/graphiti-api/graphiti/commit/5a08c0edab2bc1ee0c87c6abbe1067297569411e))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Performance Improvements
|
|
41
|
+
|
|
42
|
+
* nest the entity map instead of keying it by an array ([20bf625](https://github.com/graphiti-api/graphiti/commit/20bf6259d0347b02568b7d47054bb799e69629c3))
|
|
43
|
+
* skip the promise machinery when nothing is sideloaded ([d879923](https://github.com/graphiti-api/graphiti/commit/d879923070a35d1a0e908b2d04746aa81effb262))
|
|
44
|
+
* skip zipping a lone sideload promise ([7ef2cea](https://github.com/graphiti-api/graphiti/commit/7ef2cea654c48175504105f907b5b651c9fcdaf3))
|
|
45
|
+
|
|
3
46
|
# [2.0.0-beta.8](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2026-08-10)
|
|
4
47
|
|
|
5
48
|
|
data/Rakefile
CHANGED
|
@@ -11,6 +11,34 @@ RSpec::Core::RakeTask.new(:spec) do |t|
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
namespace :performance do
|
|
15
|
+
# Shelling out keeps a failure to the script's own message, with no rake backtrace on top.
|
|
16
|
+
def measure(*arguments)
|
|
17
|
+
exit(1) unless system("bundle", "exec", "ruby", "spec/performance/measure_releases.rb", *arguments)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Compare the working tree to the last release, and plot it"
|
|
21
|
+
task :current do
|
|
22
|
+
measure
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Plot the release history plus the working tree to tmp/performance.html and open it"
|
|
26
|
+
task :page do
|
|
27
|
+
exit(1) unless system("bundle", "exec", "ruby", "spec/performance/chart_page.rb")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "Measure one release and record it (TAG=v2.0.0-beta.9)"
|
|
31
|
+
task :record do
|
|
32
|
+
tag = ENV["TAG"] or abort "pass the release to record, e.g. rake performance:record TAG=v2.0.0-beta.9"
|
|
33
|
+
measure(tag)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Re-record every release for this ruby, replacing its rows"
|
|
37
|
+
task :record_all do
|
|
38
|
+
measure("--all")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
14
42
|
if ENV["APPRAISAL_INITIALIZED"]
|
|
15
43
|
task default: [:spec]
|
|
16
44
|
else
|
data/UPGRADING.md
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
The upgrade guide lives with the rest of the documentation, so it can link into the concept pages and offer the same version picker: **https://graphiti.dev/upgrading**
|
|
4
4
|
|
|
5
|
-
In this repository it is [docs/upgrading
|
|
5
|
+
In this repository it is [docs/upgrading.md](docs/upgrading.md).
|
|
@@ -33,6 +33,30 @@ module Graphiti
|
|
|
33
33
|
methods.any? { |m| actions.include?(m) }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def resource_setting_groups
|
|
37
|
+
name_width = Graphiti::Resource::SETTINGS.keys.map(&:length).max
|
|
38
|
+
assignments = Graphiti::Resource::SETTINGS.to_h do |name, setting|
|
|
39
|
+
value = setting[:format] || setting[:default].inspect
|
|
40
|
+
[name, "self.#{name.to_s.ljust(name_width)} = #{value}"]
|
|
41
|
+
end
|
|
42
|
+
hint_column = assignments.values.map(&:length).max + 2
|
|
43
|
+
|
|
44
|
+
Graphiti::Resource::SETTING_GROUPS.transform_values do |settings|
|
|
45
|
+
settings.map do |name, setting|
|
|
46
|
+
hint = resource_setting_hint(setting)
|
|
47
|
+
hint ? "#{assignments[name].ljust(hint_column)}# #{hint}" : assignments[name]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resource_setting_hint(setting)
|
|
53
|
+
return setting[:note] if setting[:note]
|
|
54
|
+
return unless setting[:values]
|
|
55
|
+
|
|
56
|
+
setting[:values].map(&:inspect)
|
|
57
|
+
.to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
|
|
58
|
+
end
|
|
59
|
+
|
|
36
60
|
def graphiti_config
|
|
37
61
|
File.exist?(".graphiticfg.yml") ? YAML.load_file(".graphiticfg.yml") : {}
|
|
38
62
|
end
|
|
@@ -14,21 +14,14 @@ class ApplicationResource < Graphiti::Resource
|
|
|
14
14
|
self.endpoint_namespace = '<%= api_namespace %>'
|
|
15
15
|
<%- unless omit_comments? -%>
|
|
16
16
|
|
|
17
|
-
# Defaults every Resource inherits.
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# self.relationships_readable_by_default = true
|
|
27
|
-
# self.relationships_writable_by_default = true
|
|
28
|
-
# self.belongs_to_resource_ids_by_default = :foreign_key
|
|
29
|
-
# self.filters_accept_nil_by_default = false
|
|
30
|
-
# self.filters_deny_empty_by_default = false
|
|
31
|
-
# self.default_page_size = 20
|
|
32
|
-
# self.max_page_size = 1_000
|
|
17
|
+
# Defaults every Resource inherits. Uncomment to change them API-wide, or set
|
|
18
|
+
# them on individual Resources. See https://graphiti.dev/concepts/resources
|
|
19
|
+
<%- resource_setting_groups.each_pair do |group, lines| -%>
|
|
20
|
+
|
|
21
|
+
# <%= group.to_s.humanize %>
|
|
22
|
+
<%- lines.each do |line| -%>
|
|
23
|
+
# <%= line %>
|
|
24
|
+
<%- end -%>
|
|
25
|
+
<%- end -%>
|
|
33
26
|
<%- end -%>
|
|
34
27
|
end
|
|
@@ -24,10 +24,6 @@ module Graphiti
|
|
|
24
24
|
|
|
25
25
|
attr_accessor :respond_to
|
|
26
26
|
attr_accessor :context_for_endpoint
|
|
27
|
-
attr_accessor :links_on_demand
|
|
28
|
-
attr_accessor :pagination_links_on_demand
|
|
29
|
-
attr_accessor :pagination_links
|
|
30
|
-
attr_accessor :typecast_reads
|
|
31
27
|
attr_accessor :raise_on_missing_sidepost
|
|
32
28
|
attr_accessor :before_sideload
|
|
33
29
|
|
|
@@ -44,32 +40,12 @@ module Graphiti
|
|
|
44
40
|
@concurrency = false
|
|
45
41
|
@concurrency_max_threads = 4
|
|
46
42
|
@respond_to = [:json, :jsonapi, :xml]
|
|
47
|
-
@links_on_demand = false
|
|
48
|
-
@pagination_links_on_demand = false
|
|
49
|
-
@pagination_links = false
|
|
50
|
-
@typecast_reads = true
|
|
51
43
|
@raise_on_missing_sidepost = true
|
|
52
44
|
@cache_rendering = false
|
|
53
45
|
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
|
|
54
46
|
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
|
|
55
47
|
|
|
56
48
|
@uri_decoder = infer_uri_decoder
|
|
57
|
-
|
|
58
|
-
# FIXME: Don't duplicate the Railtie's initializer
|
|
59
|
-
if defined?(::Rails.root) && (root = ::Rails.root)
|
|
60
|
-
config_file = root.join(".graphiticfg.yml")
|
|
61
|
-
if config_file.exist?
|
|
62
|
-
cfg = YAML.load_file(config_file)
|
|
63
|
-
@schema_path = root.join("public#{cfg["namespace"]}/schema.json")
|
|
64
|
-
else
|
|
65
|
-
@schema_path = root.join("public/schema.json")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
if (logger = ::Rails.logger)
|
|
69
|
-
self.debug = logger.debug? && debug
|
|
70
|
-
Graphiti.logger = logger
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
49
|
end
|
|
74
50
|
|
|
75
51
|
def cache_rendering?
|
|
@@ -87,13 +63,20 @@ module Graphiti
|
|
|
87
63
|
end
|
|
88
64
|
|
|
89
65
|
def debug=(val)
|
|
90
|
-
@debug = val
|
|
91
|
-
Debugger.enabled =
|
|
66
|
+
@debug = coerce_flag(val)
|
|
67
|
+
Debugger.enabled = @debug
|
|
92
68
|
end
|
|
93
69
|
|
|
94
70
|
def debug_models=(val)
|
|
95
|
-
@debug_models = val
|
|
96
|
-
Debugger.debug_models =
|
|
71
|
+
@debug_models = coerce_flag(val)
|
|
72
|
+
Debugger.debug_models = @debug_models
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# every value comes back from ENV as a string and "false" is truthy
|
|
76
|
+
def coerce_flag(val)
|
|
77
|
+
return false if ["false", "0", ""].include?(val.to_s.strip.downcase)
|
|
78
|
+
|
|
79
|
+
!!val
|
|
97
80
|
end
|
|
98
81
|
|
|
99
82
|
def with_option(key, value)
|
|
@@ -104,6 +87,48 @@ module Graphiti
|
|
|
104
87
|
send(:"#{key}=", original)
|
|
105
88
|
end
|
|
106
89
|
|
|
90
|
+
def typecast_reads
|
|
91
|
+
Resource.typecast_reads
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def typecast_reads=(val)
|
|
95
|
+
Resource.typecast_reads = val
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def links_on_demand
|
|
99
|
+
Resource.relationship_links == :on_demand
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def links_on_demand=(val)
|
|
103
|
+
if val
|
|
104
|
+
Resource.relationship_links = :on_demand
|
|
105
|
+
elsif Resource.relationship_links == :on_demand
|
|
106
|
+
Resource.relationship_links = true
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def pagination_links
|
|
111
|
+
Resource.page_links == true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def pagination_links=(val)
|
|
115
|
+
return if Resource.page_links == :on_demand
|
|
116
|
+
|
|
117
|
+
Resource.page_links = !!val
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def pagination_links_on_demand
|
|
121
|
+
Resource.page_links == :on_demand
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def pagination_links_on_demand=(val)
|
|
125
|
+
if val
|
|
126
|
+
Resource.page_links = :on_demand
|
|
127
|
+
elsif Resource.page_links == :on_demand
|
|
128
|
+
Resource.page_links = false
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
107
132
|
def uri_decoder=(decoder)
|
|
108
133
|
unless decoder.respond_to?(:call)
|
|
109
134
|
raise "uri_decoder must respond to `call`."
|
|
@@ -134,4 +159,17 @@ module Graphiti
|
|
|
134
159
|
|
|
135
160
|
msg = "Use `config.graphiti.respond_to_formats`"
|
|
136
161
|
DEPRECATOR.deprecate_methods(Configuration, respond_to: msg, "respond_to=": msg)
|
|
162
|
+
|
|
163
|
+
relationship_msg = "Set `self.relationship_links` (true, false, or :on_demand) on your resource"
|
|
164
|
+
pagination_msg = "Set `self.page_links` (true, false, or :on_demand) on your resource"
|
|
165
|
+
typecast_msg = "Set `self.typecast_reads` on your resource"
|
|
166
|
+
DEPRECATOR.deprecate_methods(Configuration,
|
|
167
|
+
typecast_reads: typecast_msg,
|
|
168
|
+
"typecast_reads=": typecast_msg,
|
|
169
|
+
links_on_demand: relationship_msg,
|
|
170
|
+
"links_on_demand=": relationship_msg,
|
|
171
|
+
pagination_links: pagination_msg,
|
|
172
|
+
"pagination_links=": pagination_msg,
|
|
173
|
+
pagination_links_on_demand: pagination_msg,
|
|
174
|
+
"pagination_links_on_demand=": pagination_msg)
|
|
137
175
|
end
|
data/lib/graphiti/debugger.rb
CHANGED
|
@@ -4,11 +4,22 @@
|
|
|
4
4
|
module Graphiti
|
|
5
5
|
class Debugger
|
|
6
6
|
class << self
|
|
7
|
-
attr_accessor :enabled, :
|
|
7
|
+
attr_accessor :enabled, :debug_models, :preserve, :pry
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
CHUNKS = :__graphiti_debugger_chunks
|
|
11
|
+
private_constant :CHUNKS
|
|
10
12
|
|
|
11
13
|
class << self
|
|
14
|
+
# Pool threads inherit this through the thread-local copy Scope#future_with_context makes, so their chunks reach the right request.
|
|
15
|
+
def chunks
|
|
16
|
+
Thread.current[CHUNKS] ||= Concurrent::Array.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def chunks=(value)
|
|
20
|
+
Thread.current[CHUNKS] = value
|
|
21
|
+
end
|
|
22
|
+
|
|
12
23
|
def on_data(name, start, stop, id, payload)
|
|
13
24
|
return [] unless enabled
|
|
14
25
|
|
|
@@ -179,14 +190,15 @@ module Graphiti
|
|
|
179
190
|
end
|
|
180
191
|
|
|
181
192
|
def graph_statements
|
|
182
|
-
|
|
193
|
+
statements = chunks
|
|
194
|
+
statements.each do |chunk|
|
|
183
195
|
if (parent = chunk[:parent])
|
|
184
|
-
relevant =
|
|
196
|
+
relevant = statements.find { |c| c[:resource] == parent }
|
|
185
197
|
relevant[:children].unshift(chunk) if relevant
|
|
186
198
|
end
|
|
187
199
|
end
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
statements.reject! { |c| !!c[:parent] }
|
|
201
|
+
statements
|
|
190
202
|
end
|
|
191
203
|
|
|
192
204
|
def chunk_to_hash(chunk)
|
|
@@ -6,7 +6,7 @@ module Graphiti
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def links?
|
|
9
|
-
@proxy.query.
|
|
9
|
+
@proxy.query.page_links? && @proxy.data.present?
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def links
|
|
@@ -104,7 +104,7 @@ module Graphiti
|
|
|
104
104
|
|
|
105
105
|
def page_size
|
|
106
106
|
@page_size ||= (page_param[:size] ||
|
|
107
|
-
@proxy.resource.
|
|
107
|
+
@proxy.resource.page_default_size ||
|
|
108
108
|
Graphiti::Scoping::Paginate::DEFAULT_PAGE_SIZE).to_i
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -23,6 +23,29 @@ module GraphitiErrors
|
|
|
23
23
|
MSG
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# The old global rendering toggle, most often flipped in specs.
|
|
27
|
+
class << self
|
|
28
|
+
def enable!
|
|
29
|
+
Graphiti::DEPRECATOR.deprecation_warning("GraphitiErrors.enable!", "wrap the request in Graphiti::Rails::TestHelpers#handle_request_exceptions instead")
|
|
30
|
+
test_helpers.handle_request_exceptions(true)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def disable!
|
|
34
|
+
Graphiti::DEPRECATOR.deprecation_warning("GraphitiErrors.disable!", "wrap the request in Graphiti::Rails::TestHelpers#handle_request_exceptions instead")
|
|
35
|
+
test_helpers.handle_request_exceptions(false)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def disabled?
|
|
39
|
+
!test_helpers.handle_request_exceptions?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def test_helpers
|
|
45
|
+
@test_helpers ||= Object.new.extend(Graphiti::Rails::TestHelpers)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
26
49
|
module Validation; end
|
|
27
50
|
|
|
28
51
|
module InvalidRequest; end
|
data/lib/graphiti/errors.rb
CHANGED
|
@@ -221,7 +221,7 @@ module Graphiti
|
|
|
221
221
|
|
|
222
222
|
Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
|
|
223
223
|
|
|
224
|
-
If you do not wish to generate a link, pass link: false or set self.
|
|
224
|
+
If you do not wish to generate a link, pass link: false or set self.relationship_links = false. To keep the link and stop checking it, set self.validate_links = false.
|
|
225
225
|
MSG
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -255,6 +255,8 @@ module Graphiti
|
|
|
255
255
|
Graphiti.config.context_for_endpoint must be set to enable link generation:
|
|
256
256
|
|
|
257
257
|
Graphiti.config.context_for_endpoint = ->(path, action) { ... }
|
|
258
|
+
|
|
259
|
+
Or set self.validate_links = false to render links without checking them.
|
|
258
260
|
MSG
|
|
259
261
|
end
|
|
260
262
|
end
|
|
@@ -445,9 +447,9 @@ module Graphiti
|
|
|
445
447
|
|
|
446
448
|
secondary_endpoint '/my_url', [:index, :update]
|
|
447
449
|
|
|
448
|
-
Or disable
|
|
450
|
+
Or disable request validation for this resource:
|
|
449
451
|
|
|
450
|
-
self.
|
|
452
|
+
self.validate_requests = false
|
|
451
453
|
|
|
452
454
|
See https://graphiti.dev/concepts/links for more information.
|
|
453
455
|
|
|
@@ -708,6 +710,52 @@ module Graphiti
|
|
|
708
710
|
end
|
|
709
711
|
end
|
|
710
712
|
|
|
713
|
+
class InvalidFilterBlanks < Base
|
|
714
|
+
def initialize(resource_class, attribute, value)
|
|
715
|
+
@resource_class = resource_class
|
|
716
|
+
@attribute = attribute
|
|
717
|
+
@value = value
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def message
|
|
721
|
+
<<~MSG
|
|
722
|
+
#{@resource_class.name}: #{@attribute} must be one of :as_literal, :as_nil, or :reject. Got #{@value.inspect}.
|
|
723
|
+
|
|
724
|
+
:as_literal - "null" and "" reach the filter as strings (default)
|
|
725
|
+
:as_nil - "null" becomes nil, so the filter can query for NULL
|
|
726
|
+
:reject - a blank value raises InvalidFilterValue
|
|
727
|
+
MSG
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
class InvalidLinkRendering < Base
|
|
732
|
+
def initialize(resource_class, attribute, value)
|
|
733
|
+
@resource_class = resource_class
|
|
734
|
+
@attribute = attribute
|
|
735
|
+
@value = value
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def message
|
|
739
|
+
"#{@resource_class.name}: #{@attribute} must be true, false, or :on_demand. Got #{@value.inspect}."
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
class UnselectedForeignKey < Base
|
|
744
|
+
def initialize(resource_class, sideload, model)
|
|
745
|
+
@resource_class = resource_class
|
|
746
|
+
@sideload = sideload
|
|
747
|
+
@model = model
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def message
|
|
751
|
+
<<~MSG
|
|
752
|
+
#{@resource_class.name}: rendering resource linkage for relationship #{@sideload.name.inspect} reads ##{@sideload.foreign_key} off #{@model.class.name}, but that attribute is not loaded.
|
|
753
|
+
|
|
754
|
+
Keep #{@sideload.foreign_key} in the selected columns, or turn linkage off with `resource_ids: false` on the relationship or `self.belongs_to_resource_ids_by_default = :never` on the resource.
|
|
755
|
+
MSG
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
|
|
711
759
|
class MissingRelationshipMethod < Base
|
|
712
760
|
def initialize(resource_class, sideload, model)
|
|
713
761
|
@resource_class = resource_class
|
data/lib/graphiti/query.rb
CHANGED
|
@@ -2,6 +2,9 @@ require "digest"
|
|
|
2
2
|
|
|
3
3
|
module Graphiti
|
|
4
4
|
class Query
|
|
5
|
+
TRUTHY_LINKS = [true, "true"].freeze
|
|
6
|
+
LINKLESS_FORMATS = [:json, :xml, "json", "xml"].freeze
|
|
7
|
+
|
|
5
8
|
attr_reader :resource, :association_name, :params, :action
|
|
6
9
|
|
|
7
10
|
def initialize(resource, params, *positional, association_name: nil, nested_include: nil, parents: [], action: nil)
|
|
@@ -22,6 +25,7 @@ module Graphiti
|
|
|
22
25
|
@include_param = nested_include || @params[:include]
|
|
23
26
|
@parents = parents
|
|
24
27
|
@action = parse_action(action)
|
|
28
|
+
@entity_map = Concurrent::Map.new if parents.empty?
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def association?
|
|
@@ -32,32 +36,46 @@ module Graphiti
|
|
|
32
36
|
def entity_map
|
|
33
37
|
return root.entity_map unless root == self
|
|
34
38
|
|
|
35
|
-
@entity_map
|
|
39
|
+
@entity_map
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def top_level?
|
|
39
43
|
!association?
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
def
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
def links_requested?
|
|
47
|
+
return @links_requested if defined?(@links_requested)
|
|
48
|
+
|
|
49
|
+
@links_requested = TRUTHY_LINKS.include?(@params[:links])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def suppress_links?
|
|
53
|
+
return @suppress_links if defined?(@suppress_links)
|
|
54
|
+
|
|
55
|
+
@suppress_links = LINKLESS_FORMATS.include?(params[:format])
|
|
49
56
|
end
|
|
50
57
|
|
|
51
|
-
def
|
|
58
|
+
def render_link?(mode)
|
|
59
|
+
return false if suppress_links?
|
|
60
|
+
|
|
61
|
+
(mode == :on_demand) ? links_requested? : mode == true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def page_links?
|
|
52
65
|
if action == :find
|
|
53
66
|
false
|
|
54
|
-
elsif
|
|
55
|
-
|
|
67
|
+
elsif @resource.page_links == :on_demand
|
|
68
|
+
page_links_requested?
|
|
56
69
|
else
|
|
57
|
-
|
|
70
|
+
@resource.page_links
|
|
58
71
|
end
|
|
59
72
|
end
|
|
60
73
|
|
|
74
|
+
def page_links_requested?
|
|
75
|
+
[@params[:page_links], @params[:pagination_links]]
|
|
76
|
+
.any? { |value| [true, "true"].include?(value) }
|
|
77
|
+
end
|
|
78
|
+
|
|
61
79
|
def debug_requested?
|
|
62
80
|
!!@params[:debug]
|
|
63
81
|
end
|
|
@@ -266,8 +284,8 @@ module Graphiti
|
|
|
266
284
|
def query_cache_key
|
|
267
285
|
attrs = {extra_fields: extra_fields,
|
|
268
286
|
fields: fields,
|
|
269
|
-
links:
|
|
270
|
-
|
|
287
|
+
links: suppress_links? ? :none : links_requested?,
|
|
288
|
+
page_links: page_links?,
|
|
271
289
|
format: params[:format]}
|
|
272
290
|
|
|
273
291
|
Digest::SHA1.hexdigest(attrs.to_s)
|
|
@@ -386,5 +404,9 @@ module Graphiti
|
|
|
386
404
|
action
|
|
387
405
|
end
|
|
388
406
|
end
|
|
407
|
+
|
|
408
|
+
alias_method :pagination_links?, :page_links?
|
|
409
|
+
DEPRECATOR.deprecate_methods(self,
|
|
410
|
+
pagination_links?: "Use `page_links?`")
|
|
389
411
|
end
|
|
390
412
|
end
|