graphiti 2.0.0.beta.2 → 2.0.0.beta.4
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 +30 -86
- data/.github/workflows/docs.yml +60 -0
- data/.github/workflows/release.yml +8 -8
- data/.gitignore +7 -0
- data/.npmrc +9 -0
- data/.standard.yml +4 -1
- data/Appraisals +33 -32
- data/CHANGELOG.md +41 -0
- data/README.md +13 -2
- data/UPGRADING.md +2 -68
- data/docs/concepts/backends-and-models.md +122 -0
- data/docs/concepts/endpoints.md +183 -0
- data/docs/concepts/links.md +212 -0
- data/docs/concepts/overview.md +80 -0
- data/docs/concepts/persisting.md +376 -0
- data/docs/concepts/relationships.md +527 -0
- data/docs/concepts/resources.md +677 -0
- data/docs/getting-started/first-api.md +289 -0
- data/docs/getting-started/installation.md +185 -0
- data/docs/intro.md +307 -0
- data/docs/js/authentication.md +63 -0
- data/docs/js/ddau.md +20 -0
- data/docs/js/extra-params.md +41 -0
- data/docs/js/index.md +112 -0
- data/docs/js/installation.md +120 -0
- data/docs/js/middleware.md +72 -0
- data/docs/js/models.md +202 -0
- data/docs/js/reads.md +494 -0
- data/docs/js/state-syncing.md +100 -0
- data/docs/js/writes.md +373 -0
- data/docs/reference/vandal.md +63 -0
- data/docs/reference/why.md +13 -0
- data/docs/topics/authorization.md +155 -0
- data/docs/topics/caching.md +55 -0
- data/docs/topics/customizing-sideloads.md +156 -0
- data/docs/topics/debugging.md +216 -0
- data/docs/topics/error-handling.md +210 -0
- data/docs/topics/etags.md +46 -0
- data/docs/topics/hopping-relationships.md +149 -0
- data/docs/topics/json-attributes.md +77 -0
- data/docs/topics/openstruct-models.md +50 -0
- data/docs/topics/remote-resources.md +291 -0
- data/docs/topics/testing.md +894 -0
- data/docs/topics/without-activerecord.md +324 -0
- data/docs/tutorial/index.md +58 -0
- data/docs/tutorial/step_0.md +107 -0
- data/docs/tutorial/step_1.md +199 -0
- data/docs/tutorial/step_2.md +312 -0
- data/docs/tutorial/step_3.md +142 -0
- data/docs/tutorial/step_4.md +135 -0
- data/docs/tutorial/step_5.md +69 -0
- data/docs/tutorial/step_6.md +82 -0
- data/docs/tutorial/step_7.md +205 -0
- data/docs/tutorial/step_8.md +128 -0
- data/docs/tutorial/step_9.md +171 -0
- data/docs/upgrading.md +265 -0
- data/gemfiles/rails_7_1.gemfile +4 -3
- data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
- data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
- data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
- data/graphiti.gemspec +7 -5
- data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
- data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
- data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
- data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
- data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
- data/lib/graphiti/configuration.rb +2 -2
- data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
- data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
- data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
- data/lib/graphiti/error_serializers/validation.rb +143 -0
- data/lib/graphiti/errors.rb +4 -23
- data/lib/graphiti/query.rb +1 -1
- data/lib/graphiti/rails/context.rb +33 -0
- data/lib/graphiti/rails/controller.rb +41 -0
- data/lib/graphiti/rails/debugging.rb +18 -0
- data/lib/graphiti/rails/exception_handlers.rb +77 -0
- data/lib/graphiti/rails/railtie.rb +139 -0
- data/lib/graphiti/rails/responders.rb +21 -0
- data/lib/graphiti/rails/test_helpers.rb +22 -0
- data/lib/graphiti/rails.rb +47 -29
- data/lib/graphiti/resource/configuration.rb +1 -0
- data/lib/graphiti/resource/interface.rb +2 -2
- data/lib/graphiti/resource/persistence.rb +14 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +1 -1
- data/lib/graphiti/resource.rb +13 -1
- data/lib/graphiti/responders.rb +7 -20
- data/lib/graphiti/schema.rb +5 -1
- data/lib/graphiti/schema_diff.rb +4 -0
- data/lib/graphiti/scope.rb +45 -37
- data/lib/graphiti/serializer.rb +6 -0
- data/lib/graphiti/sideload/belongs_to.rb +38 -5
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
- data/lib/graphiti/sideload.rb +54 -35
- data/lib/graphiti/spec_helpers/errors.rb +73 -0
- data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
- data/lib/graphiti/spec_helpers/helpers.rb +107 -0
- data/lib/graphiti/spec_helpers/node.rb +88 -0
- data/lib/graphiti/spec_helpers/rspec.rb +147 -0
- data/lib/graphiti/spec_helpers.rb +53 -0
- data/lib/graphiti/util/include_params.rb +2 -2
- data/lib/graphiti/util/persistence.rb +10 -11
- data/lib/graphiti/util/serializer_relationships.rb +41 -5
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti-rails.rb +11 -0
- data/lib/graphiti.rb +34 -10
- data/lib/graphiti_errors.rb +11 -0
- data/lib/graphiti_spec_helpers/rspec.rb +3 -0
- data/lib/graphiti_spec_helpers.rb +11 -0
- data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
- data/package-lock.json +6199 -0
- data/package.json +5 -4
- data/website/.gitignore +20 -0
- data/website/README.md +43 -0
- data/website/docusaurus.config.js +141 -0
- data/website/package-lock.json +19474 -0
- data/website/package.json +46 -0
- data/website/sidebars.js +82 -0
- data/website/src/css/custom.css +58 -0
- data/website/src/pages/markdown-page.mdx +7 -0
- data/website/static/.nojekyll +0 -0
- data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
- data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
- data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
- data/website/static/1.13/2019/10/14/tutorial.html +198 -0
- data/website/static/1.13/CNAME +1 -0
- data/website/static/1.13/README.md +16 -0
- data/website/static/1.13/assets/css/syntax.css +60 -0
- data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
- data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
- data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
- data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
- data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
- data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
- data/website/static/1.13/assets/favicons/favicon.ico +0 -0
- data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
- data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
- data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
- data/website/static/1.13/assets/img/backend.gif +0 -0
- data/website/static/1.13/assets/img/conformity.png +0 -0
- data/website/static/1.13/assets/img/error_payload.png +0 -0
- data/website/static/1.13/assets/img/gh.png +0 -0
- data/website/static/1.13/assets/img/lifecycle.gif +0 -0
- data/website/static/1.13/assets/img/logo-500.png +0 -0
- data/website/static/1.13/assets/img/logo.png +0 -0
- data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/meta_total_count.png +0 -0
- data/website/static/1.13/assets/img/persist.jpg +0 -0
- data/website/static/1.13/assets/img/resource.gif +0 -0
- data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/rest1.gif +0 -0
- data/website/static/1.13/assets/img/rest2.gif +0 -0
- data/website/static/1.13/assets/img/rest3.gif +0 -0
- data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/why.png +0 -0
- data/website/static/1.13/assets/js/highlight.pack.js +2 -0
- data/website/static/1.13/assets/main.css +15518 -0
- data/website/static/1.13/assets/main.css.map +1 -0
- data/website/static/1.13/bin/bundle +109 -0
- data/website/static/1.13/bin/jekyll +27 -0
- data/website/static/1.13/bin/kramdown +27 -0
- data/website/static/1.13/bin/listen +27 -0
- data/website/static/1.13/bin/rake +27 -0
- data/website/static/1.13/bin/rougify +27 -0
- data/website/static/1.13/bin/safe_yaml +27 -0
- data/website/static/1.13/bin/sass +27 -0
- data/website/static/1.13/bin/sass-convert +27 -0
- data/website/static/1.13/bin/scss +27 -0
- data/website/static/1.13/blog.html +259 -0
- data/website/static/1.13/cheatsheet.html +316 -0
- data/website/static/1.13/cookbooks/authorization.md +0 -0
- data/website/static/1.13/cookbooks/caching.md +0 -0
- data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
- data/website/static/1.13/cookbooks/etags.md +0 -0
- data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
- data/website/static/1.13/cookbooks/json_attributes.md +0 -0
- data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
- data/website/static/1.13/cookbooks/remote-resources.md +0 -0
- data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
- data/website/static/1.13/features.html +249 -0
- data/website/static/1.13/feed.xml +106 -0
- data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
- data/website/static/1.13/guides/concepts/debugging.html +440 -0
- data/website/static/1.13/guides/concepts/endpoints.html +432 -0
- data/website/static/1.13/guides/concepts/error-handling.html +396 -0
- data/website/static/1.13/guides/concepts/links.html +501 -0
- data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
- data/website/static/1.13/guides/concepts/resources.html +2176 -0
- data/website/static/1.13/guides/concepts/testing.html +1469 -0
- data/website/static/1.13/guides/getting-started/installation.html +420 -0
- data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
- data/website/static/1.13/guides/index.html +269 -0
- data/website/static/1.13/guides/overview.html +325 -0
- data/website/static/1.13/guides/upgrading-2-0.html +193 -0
- data/website/static/1.13/guides/upgrading.html +314 -0
- data/website/static/1.13/guides/vandal.html +282 -0
- data/website/static/1.13/guides/why.html +1121 -0
- data/website/static/1.13/index.html +72 -0
- data/website/static/1.13/js/authentication.html +295 -0
- data/website/static/1.13/js/ddau.html +238 -0
- data/website/static/1.13/js/extra-params.html +270 -0
- data/website/static/1.13/js/index.html +321 -0
- data/website/static/1.13/js/installation.html +637 -0
- data/website/static/1.13/js/introduction.html +257 -0
- data/website/static/1.13/js/middleware.html +318 -0
- data/website/static/1.13/js/reads/fieldsets.html +271 -0
- data/website/static/1.13/js/reads/filtering.html +289 -0
- data/website/static/1.13/js/reads/includes.html +260 -0
- data/website/static/1.13/js/reads/index.html +497 -0
- data/website/static/1.13/js/reads/nested-queries.html +353 -0
- data/website/static/1.13/js/reads/pagination.html +260 -0
- data/website/static/1.13/js/reads/sorting.html +265 -0
- data/website/static/1.13/js/reads/statistics.html +289 -0
- data/website/static/1.13/js/state-syncing.html +340 -0
- data/website/static/1.13/js/writes/deferred.html +296 -0
- data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
- data/website/static/1.13/js/writes/index.html +391 -0
- data/website/static/1.13/js/writes/nested.html +330 -0
- data/website/static/1.13/js/writes/validations.html +272 -0
- data/website/static/1.13/quickstart.html +660 -0
- data/website/static/1.13/template +161 -0
- data/website/static/1.13/tutorial/index.html +250 -0
- data/website/static/1.13/tutorial/step_0.html +292 -0
- data/website/static/1.13/tutorial/step_1.html +517 -0
- data/website/static/1.13/tutorial/step_2.html +481 -0
- data/website/static/1.13/tutorial/step_3.html +323 -0
- data/website/static/1.13/tutorial/step_4.html +318 -0
- data/website/static/1.13/tutorial/step_5.html +265 -0
- data/website/static/1.13/tutorial/step_6.html +276 -0
- data/website/static/1.13/tutorial/step_7.html +390 -0
- data/website/static/1.13/tutorial/step_8.html +316 -0
- data/website/static/1.13/tutorial/step_9.html +365 -0
- data/website/static/assets/img/error_payload.png +0 -0
- data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
- data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
- data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
- data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
- data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
- data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
- data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
- data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
- data/website/static/assets/img/meta_total_count.png +0 -0
- data/website/static/img/docusaurus-social-card.jpg +0 -0
- data/website/static/img/docusaurus.png +0 -0
- data/website/static/img/favicon.ico +0 -0
- data/website/static/img/logo.png +0 -0
- data/website/static/img/logo.svg +1 -0
- data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
- data/website/static/img/undraw_docusaurus_react.svg +170 -0
- data/website/static/img/undraw_docusaurus_tree.svg +40 -0
- metadata +245 -46
- data/gemfiles/rails_6.gemfile +0 -18
- data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7.gemfile +0 -18
- data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
- data/lib/graphiti/railtie.rb +0 -121
- /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/update_request_spec.rb.erb +0 -0
data/lib/graphiti/rails.rb
CHANGED
|
@@ -1,38 +1,56 @@
|
|
|
1
|
+
require "rescue_registry"
|
|
2
|
+
require "graphiti"
|
|
3
|
+
require "rails"
|
|
4
|
+
|
|
1
5
|
module Graphiti
|
|
2
|
-
# Rails
|
|
3
|
-
#
|
|
4
|
-
# * Mixes in Base
|
|
5
|
-
# * Adds a global around_action (see Base#wrap_context)
|
|
6
|
-
#
|
|
7
|
-
# @see Base#render_jsonapi
|
|
8
|
-
# @see Base#wrap_context
|
|
6
|
+
# Rails integration for Graphiti. See {file:README.md} for more details.
|
|
9
7
|
module Rails
|
|
8
|
+
# Graphiti::Rails was a controller mixin before it became this namespace.
|
|
9
|
+
# Including a namespace is not an error, it just does nothing, so honour
|
|
10
|
+
# the old spelling rather than leave a controller silently without its
|
|
11
|
+
# context, debugger and exception handlers.
|
|
10
12
|
def self.included(klass)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
include Graphiti::Context
|
|
16
|
-
include GraphitiErrors
|
|
17
|
-
around_action :wrap_context
|
|
18
|
-
around_action :debug
|
|
19
|
-
end
|
|
20
|
-
end
|
|
13
|
+
DEPRECATOR.deprecation_warning(
|
|
14
|
+
"Including Graphiti::Rails",
|
|
15
|
+
"include Graphiti::Rails::Controller instead"
|
|
16
|
+
)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
Graphiti.with_context(jsonapi_context, action_name.to_sym) do
|
|
24
|
-
yield
|
|
25
|
-
end
|
|
18
|
+
klass.include(Controller)
|
|
26
19
|
end
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
yield
|
|
31
|
-
end
|
|
32
|
-
end
|
|
21
|
+
# graphiti-rails had its own, and apps that silenced it by name still resolve.
|
|
22
|
+
DEPRECATOR = Graphiti::DEPRECATOR
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
autoload :ConflictRequestHandler, "graphiti/rails/exception_handlers"
|
|
25
|
+
autoload :Context, "graphiti/rails/context"
|
|
26
|
+
autoload :Controller, "graphiti/rails/controller"
|
|
27
|
+
autoload :Debugging, "graphiti/rails/debugging"
|
|
28
|
+
autoload :ExceptionHandler, "graphiti/rails/exception_handlers"
|
|
29
|
+
autoload :FallbackHandler, "graphiti/rails/exception_handlers"
|
|
30
|
+
autoload :InvalidRequestHandler, "graphiti/rails/exception_handlers"
|
|
31
|
+
autoload :Responders, "graphiti/rails/responders"
|
|
32
|
+
autoload :TestHelpers, "graphiti/rails/test_helpers"
|
|
33
|
+
|
|
34
|
+
# @!attribute self.handled_exception_formats
|
|
35
|
+
# A list of formats as symbols whose exceptions will be handled by Graphiti. See {Railtie}.
|
|
36
|
+
cattr_accessor :handled_exception_formats, default: []
|
|
37
|
+
|
|
38
|
+
# @!attribute self.respond_to_formats
|
|
39
|
+
# A list of formats as symbols which will be available for Graphiti::Rails::Responders. See {Railtie}.
|
|
40
|
+
cattr_accessor :respond_to_formats, default: []
|
|
37
41
|
end
|
|
42
|
+
|
|
43
|
+
# Deprecated. Was core's own responders mixin, superseded by the one that came
|
|
44
|
+
# in with graphiti-rails. Remove in 3.0.
|
|
45
|
+
Responders = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
|
|
46
|
+
"Graphiti::Responders",
|
|
47
|
+
"Graphiti::Rails::Responders",
|
|
48
|
+
DEPRECATOR
|
|
49
|
+
)
|
|
38
50
|
end
|
|
51
|
+
|
|
52
|
+
ActiveSupport.on_load(:active_record) do
|
|
53
|
+
require "graphiti/adapters/active_record"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require "graphiti/rails/railtie"
|
|
@@ -90,6 +90,7 @@ module Graphiti
|
|
|
90
90
|
:attributes_schema_by_default,
|
|
91
91
|
:relationships_readable_by_default,
|
|
92
92
|
:relationships_writable_by_default,
|
|
93
|
+
:always_include_resource_ids_by_default,
|
|
93
94
|
:filters_accept_nil_by_default,
|
|
94
95
|
:filters_deny_empty_by_default,
|
|
95
96
|
:graphql_entrypoint,
|
|
@@ -84,8 +84,8 @@ module Graphiti
|
|
|
84
84
|
|
|
85
85
|
if context&.respond_to?(:request)
|
|
86
86
|
path = context.request.env["PATH_INFO"]
|
|
87
|
-
unless allow_request?(path, params,
|
|
88
|
-
raise Errors::InvalidEndpoint.new(self, path,
|
|
87
|
+
unless allow_request?(path, params, current_action)
|
|
88
|
+
raise Errors::InvalidEndpoint.new(self, path, current_action)
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -93,11 +93,23 @@ module Graphiti
|
|
|
93
93
|
model_instance
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
# The model built by a prior ResourceProxy#assign_attributes, present
|
|
97
|
+
# for the duration of the save that persists it
|
|
98
|
+
attr_reader :assigned_model
|
|
99
|
+
|
|
100
|
+
# @api private
|
|
101
|
+
def with_assigned_model(model)
|
|
102
|
+
@assigned_model = model
|
|
103
|
+
yield
|
|
104
|
+
ensure
|
|
105
|
+
@assigned_model = nil
|
|
106
|
+
end
|
|
107
|
+
|
|
96
108
|
# Attributes are assigned before the persistence callbacks fire, so
|
|
97
109
|
# around_persistence receives the assigned model - its pre-yield
|
|
98
110
|
# position is the last chance to touch the model before save, inside
|
|
99
111
|
# the transaction. Modify attributes in before_attributes instead.
|
|
100
|
-
def create(create_params, meta = nil
|
|
112
|
+
def create(create_params, meta = nil)
|
|
101
113
|
model_instance = assigned_model || assign(create_params, meta, :create)
|
|
102
114
|
|
|
103
115
|
run_callbacks :persistence, :create, model_instance, meta do
|
|
@@ -111,7 +123,7 @@ module Graphiti
|
|
|
111
123
|
model_instance
|
|
112
124
|
end
|
|
113
125
|
|
|
114
|
-
def update(update_params, meta = nil
|
|
126
|
+
def update(update_params, meta = nil)
|
|
115
127
|
model_instance = assigned_model || assign(update_params, meta, :update)
|
|
116
128
|
|
|
117
129
|
run_callbacks :persistence, :update, model_instance, meta do
|
|
@@ -38,8 +38,8 @@ module Graphiti
|
|
|
38
38
|
# Forward all headers
|
|
39
39
|
def request_headers
|
|
40
40
|
{}.tap do |headers|
|
|
41
|
-
# TODO: Maybe handle this in
|
|
42
|
-
if defined?(Rails) && context
|
|
41
|
+
# TODO: Maybe handle this in the Rails integration
|
|
42
|
+
if defined?(::Rails) && context
|
|
43
43
|
raw = context.request.headers.to_h
|
|
44
44
|
if (auth = raw["HTTP_AUTHORIZATION"])
|
|
45
45
|
headers["Authorization"] = auth
|
|
@@ -128,7 +128,7 @@ module Graphiti
|
|
|
128
128
|
# If eager loading, ensure routes are loaded first, then apply
|
|
129
129
|
# This happens in Railtie
|
|
130
130
|
def eagerly_apply_sideload?(sideload)
|
|
131
|
-
# TODO: Maybe handle this in
|
|
131
|
+
# TODO: Maybe handle this in the Rails integration
|
|
132
132
|
if defined?(::Rails) && (app = ::Rails.application)
|
|
133
133
|
app.config.eager_load ? false : true
|
|
134
134
|
else
|
data/lib/graphiti/resource.rb
CHANGED
|
@@ -51,10 +51,22 @@ module Graphiti
|
|
|
51
51
|
self.class.context
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
# Rails sets this from action_name, so it is whatever the controller action
|
|
55
|
+
# is called, not a fixed list. Persistence overrides it with :create/:update
|
|
56
|
+
# while saving, and :show while resolving sideloads afterwards.
|
|
57
|
+
def self.current_action
|
|
55
58
|
Graphiti.context[:namespace]
|
|
56
59
|
end
|
|
57
60
|
|
|
61
|
+
def current_action
|
|
62
|
+
self.class.current_action
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.context_namespace
|
|
66
|
+
Graphiti::DEPRECATOR.deprecation_warning(:context_namespace, "Use #current_action instead")
|
|
67
|
+
current_action
|
|
68
|
+
end
|
|
69
|
+
|
|
58
70
|
def context_namespace
|
|
59
71
|
self.class.context_namespace
|
|
60
72
|
end
|
data/lib/graphiti/responders.rb
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
extend ActiveSupport::Concern
|
|
1
|
+
# Deprecated entry point. graphiti required this path itself in 1.x, so an
|
|
2
|
+
# explicit require left behind would otherwise raise LoadError. Remove in 3.0.
|
|
3
|
+
require "graphiti/rails"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
respond_to(*Graphiti.config.respond_to)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Override to avoid location url generation (for now)
|
|
14
|
-
def respond_with(*args, &blk)
|
|
15
|
-
opts = args.extract_options!
|
|
16
|
-
opts[:location] = nil
|
|
17
|
-
args << opts
|
|
18
|
-
super(*args, &blk)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
5
|
+
Graphiti::DEPRECATOR.warn(
|
|
6
|
+
'require "graphiti/responders" is deprecated. Use "graphiti/rails" and include ' \
|
|
7
|
+
"Graphiti::Rails::Responders."
|
|
8
|
+
)
|
data/lib/graphiti/schema.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Graphiti
|
|
|
3
3
|
attr_reader :resources
|
|
4
4
|
|
|
5
5
|
def self.generate(resources = nil)
|
|
6
|
-
# TODO: Maybe handle this in
|
|
6
|
+
# TODO: Maybe handle this in the Rails integration
|
|
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? }
|
|
@@ -251,6 +251,10 @@ module Graphiti
|
|
|
251
251
|
schema[:guard] = true
|
|
252
252
|
end
|
|
253
253
|
|
|
254
|
+
if config.always_include_resource_ids?
|
|
255
|
+
schema[:linkage] = true
|
|
256
|
+
end
|
|
257
|
+
|
|
254
258
|
r[name] = schema
|
|
255
259
|
end
|
|
256
260
|
end
|
data/lib/graphiti/schema_diff.rb
CHANGED
|
@@ -106,6 +106,10 @@ module Graphiti
|
|
|
106
106
|
@errors << "#{old_resource[:name]}: relationship #{name.inspect} became guarded."
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
+
if old_rel[:linkage] && !new_rel[:linkage]
|
|
110
|
+
@errors << "#{old_resource[:name]}: relationship #{name.inspect} no longer includes resource linkage."
|
|
111
|
+
end
|
|
112
|
+
|
|
109
113
|
if new_rel[:resource] != old_rel[:resource]
|
|
110
114
|
@errors << "#{old_resource[:name]}: relationship #{name.inspect} changed resource from #{old_rel[:resource]} to #{new_rel[:resource]}."
|
|
111
115
|
end
|
data/lib/graphiti/scope.rb
CHANGED
|
@@ -48,29 +48,25 @@ module Graphiti
|
|
|
48
48
|
# Thread/Fiber storage snapshots, and Rails executor wrappers on every
|
|
49
49
|
# request purely to drive a thread pool that is intentionally synchronous.
|
|
50
50
|
# See https://github.com/graphiti-api/graphiti/issues/505
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if Graphiti.config.concurrency
|
|
52
|
+
future_resolve(&blk).value!
|
|
53
|
+
else
|
|
54
|
+
sync_resolve(&blk)
|
|
55
|
+
end
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def resolve_sideloads(results)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
if Graphiti.config.concurrency
|
|
60
|
+
future_resolve_sideloads(results).value!
|
|
61
|
+
else
|
|
62
|
+
sync_resolve_sideloads(results)
|
|
63
|
+
end
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
def future_resolve
|
|
66
|
+
def future_resolve(&blk)
|
|
63
67
|
return Concurrent::Promises.fulfilled_future([], self.class.global_thread_pool_executor) if @query.zero_results?
|
|
64
68
|
|
|
65
|
-
resolved =
|
|
66
|
-
@object = @resource.before_resolve(@object, @query)
|
|
67
|
-
payload[:results] = @resource.resolve(@object)
|
|
68
|
-
payload[:results]
|
|
69
|
-
}
|
|
70
|
-
resolved.compact!
|
|
71
|
-
assign_serializer(resolved)
|
|
72
|
-
yield resolved if block_given?
|
|
73
|
-
@opts[:after_resolve]&.call(resolved)
|
|
69
|
+
resolved = resolve_primary_data(&blk)
|
|
74
70
|
sideloaded = @query.parents.any?
|
|
75
71
|
close_adapter = Graphiti.config.concurrency && sideloaded
|
|
76
72
|
if close_adapter
|
|
@@ -125,9 +121,33 @@ module Graphiti
|
|
|
125
121
|
# Synchronous counterpart to #future_resolve, used when concurrency is off.
|
|
126
122
|
# Resolves the resource and its sideloads inline without any promise
|
|
127
123
|
# machinery. See #resolve.
|
|
128
|
-
def sync_resolve
|
|
124
|
+
def sync_resolve(&blk)
|
|
129
125
|
return [] if @query.zero_results?
|
|
130
126
|
|
|
127
|
+
resolved = resolve_primary_data(&blk)
|
|
128
|
+
sync_resolve_sideloads(resolved)
|
|
129
|
+
resolved
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Synchronous counterpart to #future_resolve_sideloads, used when
|
|
133
|
+
# concurrency is off. Resolves each sideload inline. See #resolve_sideloads.
|
|
134
|
+
def sync_resolve_sideloads(results)
|
|
135
|
+
return if results == []
|
|
136
|
+
|
|
137
|
+
each_applicable_sideload do |sideload, sideload_query|
|
|
138
|
+
Graphiti.config.before_sideload&.call(Graphiti.context)
|
|
139
|
+
sideload.resolve(results, sideload_query, @resource)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Match pre-1.8 semantics: the non-concurrent resolve_sideloads returned
|
|
143
|
+
# nil (not the sideloads Hash). Callers don't rely on the return value.
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Resolve this scope's own data: run hooks, resolve the resource, and
|
|
148
|
+
# decorate the results. Shared by the sync and future paths — everything
|
|
149
|
+
# here runs inline on the calling thread in both modes.
|
|
150
|
+
def resolve_primary_data
|
|
131
151
|
resolved = broadcast_data { |payload|
|
|
132
152
|
@object = @resource.before_resolve(@object, @query)
|
|
133
153
|
payload[:results] = @resource.resolve(@object)
|
|
@@ -137,40 +157,28 @@ module Graphiti
|
|
|
137
157
|
assign_serializer(resolved)
|
|
138
158
|
yield resolved if block_given?
|
|
139
159
|
@opts[:after_resolve]&.call(resolved)
|
|
140
|
-
sync_resolve_sideloads(resolved) unless @query.sideloads.empty?
|
|
141
160
|
resolved
|
|
142
161
|
end
|
|
143
162
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def sync_resolve_sideloads(results)
|
|
147
|
-
return if results == []
|
|
148
|
-
|
|
149
|
-
@query.sideloads.each_pair do |name, q|
|
|
163
|
+
def each_applicable_sideload
|
|
164
|
+
@query.sideloads.each_pair do |name, sideload_query|
|
|
150
165
|
sideload = @resource.class.sideload(name)
|
|
151
166
|
next if sideload.nil? || sideload.shared_remote?
|
|
152
167
|
|
|
153
|
-
|
|
154
|
-
sideload.resolve(results, q, @resource)
|
|
168
|
+
yield sideload, sideload_query
|
|
155
169
|
end
|
|
156
|
-
|
|
157
|
-
# Match pre-1.8 semantics: the non-concurrent resolve_sideloads returned
|
|
158
|
-
# nil (not the sideloads Hash). Callers don't rely on the return value.
|
|
159
|
-
nil
|
|
160
170
|
end
|
|
161
171
|
|
|
162
172
|
def future_resolve_sideloads(results)
|
|
163
173
|
return Concurrent::Promises.fulfilled_future(nil, self.class.global_thread_pool_executor) if results == []
|
|
164
174
|
|
|
165
|
-
sideload_promises =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
p = future_with_context(results, q, @resource) do |parent_results, sideload_query, parent_resource|
|
|
175
|
+
sideload_promises = []
|
|
176
|
+
each_applicable_sideload do |sideload, sideload_query|
|
|
177
|
+
promise = future_with_context(results, sideload_query, @resource) do |parent_results, future_query, parent_resource|
|
|
170
178
|
Graphiti.config.before_sideload&.call(Graphiti.context)
|
|
171
|
-
sideload.future_resolve(parent_results,
|
|
179
|
+
sideload.future_resolve(parent_results, future_query, parent_resource)
|
|
172
180
|
end
|
|
173
|
-
|
|
181
|
+
sideload_promises << promise.flat
|
|
174
182
|
end
|
|
175
183
|
|
|
176
184
|
Concurrent::Promises.zip_futures_on(self.class.global_thread_pool_executor, *sideload_promises)
|
data/lib/graphiti/serializer.rb
CHANGED
|
@@ -13,10 +13,16 @@ module Graphiti
|
|
|
13
13
|
class_attribute :attributes_applied_via_resource
|
|
14
14
|
class_attribute :extra_attributes_applied_via_resource
|
|
15
15
|
class_attribute :relationship_condition_blocks
|
|
16
|
+
# Which sideload each generated relationship block was built for. A
|
|
17
|
+
# subclass inherits its parent's blocks, so this is how a redeclared
|
|
18
|
+
# relationship is told apart from one already applied, and from one the
|
|
19
|
+
# application wrote by hand.
|
|
20
|
+
class_attribute :relationship_sideloads
|
|
16
21
|
self.attributes_applied_via_resource = []
|
|
17
22
|
self.extra_attributes_applied_via_resource = []
|
|
18
23
|
# See #requested_relationships
|
|
19
24
|
self.relationship_condition_blocks ||= {}
|
|
25
|
+
self.relationship_sideloads ||= {}
|
|
20
26
|
|
|
21
27
|
def self.inherited(klass)
|
|
22
28
|
super
|
|
@@ -1,13 +1,46 @@
|
|
|
1
1
|
class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
|
|
2
|
-
def initialize(name, opts)
|
|
3
|
-
opts = {always_include_resource_ids: false}.merge(opts)
|
|
4
|
-
super(name, opts)
|
|
5
|
-
end
|
|
6
|
-
|
|
7
2
|
def type
|
|
8
3
|
:belongs_to
|
|
9
4
|
end
|
|
10
5
|
|
|
6
|
+
def default_include_resource_ids?
|
|
7
|
+
linkage_from_foreign_key?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# The parent already carries the foreign key, and for a plain belongs_to
|
|
11
|
+
# that key *is* the related id, so linkage costs nothing. Anything that can
|
|
12
|
+
# change which record the relationship resolves to, or what type it carries,
|
|
13
|
+
# has to load the association instead:
|
|
14
|
+
#
|
|
15
|
+
# - a scope/params block or a base_scope can filter out the record the
|
|
16
|
+
# foreign key points at, so the key would claim a relationship the API
|
|
17
|
+
# would not actually return
|
|
18
|
+
# - a polymorphic target takes its type from the record, not from the
|
|
19
|
+
# relationship, so the key alone cannot say what type the id has
|
|
20
|
+
# - a remote resource has no local foreign key to read
|
|
21
|
+
# - a custom primary_key points the relationship at some other column, so
|
|
22
|
+
# the key holds that column's value rather than the related id
|
|
23
|
+
def linkage_from_foreign_key?
|
|
24
|
+
# Ask before resolving #resource: an unreadable relationship renders
|
|
25
|
+
# nothing, and its resource class may not even be inferrable.
|
|
26
|
+
return false unless readable?
|
|
27
|
+
return false unless foreign_key_is_related_id?
|
|
28
|
+
return false if polymorphic_child?
|
|
29
|
+
return false if self.class.scope_proc || self.class.params_proc
|
|
30
|
+
return false if @base_scope
|
|
31
|
+
return false if remote?
|
|
32
|
+
return false if resource.class.polymorphic.present?
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The foreign key can stand in for the related id only when the two hold the
|
|
38
|
+
# same value. base_filter matches the key against primary_key, so pointing
|
|
39
|
+
# that at another column means the key holds that column instead.
|
|
40
|
+
def foreign_key_is_related_id?
|
|
41
|
+
primary_key == :id
|
|
42
|
+
end
|
|
43
|
+
|
|
11
44
|
def load_params(parents, query)
|
|
12
45
|
query.hash.tap do |hash|
|
|
13
46
|
hash[:filter] ||= {}
|
|
@@ -108,40 +108,44 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def resolve(parents, query, graph_parent)
|
|
111
|
-
|
|
111
|
+
if Graphiti.config.concurrency
|
|
112
|
+
future_resolve(parents, query, graph_parent).value!
|
|
113
|
+
else
|
|
114
|
+
sync_resolve(parents, query, graph_parent)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
def future_resolve(parents, query, graph_parent)
|
|
119
|
+
promises = []
|
|
120
|
+
each_resolvable_group(parents, query) do |child, group, child_query|
|
|
121
|
+
promises << child.future_resolve(group, child_query, graph_parent)
|
|
122
|
+
end
|
|
123
|
+
Concurrent::Promises.zip(*promises)
|
|
124
|
+
end
|
|
115
125
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
err = ::Graphiti::Errors::PolymorphicSideloadChildNotFound
|
|
122
|
-
raise err.new(self, group_name)
|
|
123
|
-
end
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def sync_resolve(parents, query, graph_parent)
|
|
129
|
+
each_resolvable_group(parents, query) do |child, group, child_query|
|
|
130
|
+
child.resolve(group, child_query, graph_parent)
|
|
124
131
|
end
|
|
125
132
|
end
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
# Group parents by their polymorphic type and yield each group's child
|
|
135
|
+
# sideload alongside a query pruned to the sideloads that child supports.
|
|
136
|
+
def each_resolvable_group(parents, query)
|
|
137
|
+
parents.group_by(&grouper.field_name).each_pair do |group_name, group|
|
|
129
138
|
next if group_name.nil? || grouper.ignore?(group_name)
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
sideload.future_resolve(group, duped, graph_parent)
|
|
135
|
-
else
|
|
136
|
-
err = ::Graphiti::Errors::PolymorphicSideloadChildNotFound
|
|
137
|
-
raise err.new(self, group_name)
|
|
140
|
+
child = children.values.find { |candidate| candidate.group_name == group_name.to_sym }
|
|
141
|
+
unless child
|
|
142
|
+
raise ::Graphiti::Errors::PolymorphicSideloadChildNotFound.new(self, group_name)
|
|
138
143
|
end
|
|
144
|
+
|
|
145
|
+
yield child, group, remove_invalid_sideloads(child.resource, query)
|
|
139
146
|
end
|
|
140
|
-
Concurrent::Promises.zip(*promises)
|
|
141
147
|
end
|
|
142
148
|
|
|
143
|
-
private
|
|
144
|
-
|
|
145
149
|
# We may be requesting a relationship that some subclasses support,
|
|
146
150
|
# but not others. Remove anything we don't support.
|
|
147
151
|
# TODO: spec to ensure this dupe logic doesn't mutate the original
|
data/lib/graphiti/sideload.rb
CHANGED
|
@@ -122,8 +122,25 @@ module Graphiti
|
|
|
122
122
|
!!@polymorphic_as
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
+
# False everywhere but a plain belongs_to - see
|
|
126
|
+
# Sideload::BelongsTo#linkage_from_foreign_key?.
|
|
127
|
+
def linkage_from_foreign_key?
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# nil at either of the first two levels means "not specified" rather
|
|
132
|
+
# than "false".
|
|
125
133
|
def always_include_resource_ids?
|
|
126
|
-
!!@always_include_resource_ids
|
|
134
|
+
return !!@always_include_resource_ids unless @always_include_resource_ids.nil?
|
|
135
|
+
|
|
136
|
+
configured = parent_resource_class&.always_include_resource_ids_by_default
|
|
137
|
+
return !!configured unless configured.nil?
|
|
138
|
+
|
|
139
|
+
default_include_resource_ids?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def default_include_resource_ids?
|
|
143
|
+
false
|
|
127
144
|
end
|
|
128
145
|
|
|
129
146
|
def link?
|
|
@@ -292,48 +309,19 @@ module Graphiti
|
|
|
292
309
|
children.replace(associated) if track_associated
|
|
293
310
|
end
|
|
294
311
|
|
|
295
|
-
# Synchronous counterpart to #future_resolve, used when concurrency is off.
|
|
296
|
-
# Mirrors #future_resolve but resolves inline via the synchronous
|
|
297
|
-
# Scope#resolve / #load paths (no promises). See Scope#sync_resolve_sideloads.
|
|
298
312
|
def resolve(parents, query, graph_parent)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if single? && parents.length > 1
|
|
302
|
-
raise Errors::SingularSideload.new(self, parents.length)
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
if self.class.scope_proc
|
|
306
|
-
sideload_scope = fire_scope(parents)
|
|
307
|
-
sideload_scope = Scope.new sideload_scope,
|
|
308
|
-
resource,
|
|
309
|
-
query,
|
|
310
|
-
parent: graph_parent,
|
|
311
|
-
sideload: self,
|
|
312
|
-
sideload_parent_length: parents.length,
|
|
313
|
-
default_paginate: false
|
|
314
|
-
sideload_scope.resolve do |sideload_results|
|
|
315
|
-
fire_assign(parents, sideload_results)
|
|
316
|
-
end
|
|
313
|
+
if Graphiti.config.concurrency
|
|
314
|
+
future_resolve(parents, query, graph_parent).value!
|
|
317
315
|
else
|
|
318
|
-
|
|
316
|
+
sync_resolve(parents, query, graph_parent)
|
|
319
317
|
end
|
|
320
318
|
end
|
|
321
319
|
|
|
322
320
|
def future_resolve(parents, query, graph_parent)
|
|
323
|
-
|
|
324
|
-
raise Errors::SingularSideload.new(self, parents.length)
|
|
325
|
-
end
|
|
321
|
+
assert_singular!(parents)
|
|
326
322
|
|
|
327
323
|
if self.class.scope_proc
|
|
328
|
-
|
|
329
|
-
sideload_scope = Scope.new sideload_scope,
|
|
330
|
-
resource,
|
|
331
|
-
query,
|
|
332
|
-
parent: graph_parent,
|
|
333
|
-
sideload: self,
|
|
334
|
-
sideload_parent_length: parents.length,
|
|
335
|
-
default_paginate: false
|
|
336
|
-
sideload_scope.future_resolve do |sideload_results|
|
|
324
|
+
build_sideload_scope(parents, query, graph_parent).future_resolve do |sideload_results|
|
|
337
325
|
fire_assign(parents, sideload_results)
|
|
338
326
|
end
|
|
339
327
|
else
|
|
@@ -401,6 +389,37 @@ module Graphiti
|
|
|
401
389
|
|
|
402
390
|
private
|
|
403
391
|
|
|
392
|
+
# Synchronous counterpart to #future_resolve, used when concurrency is off.
|
|
393
|
+
# Resolves inline via the synchronous Scope#resolve / #load paths (no
|
|
394
|
+
# promises). See Scope#sync_resolve_sideloads.
|
|
395
|
+
def sync_resolve(parents, query, graph_parent)
|
|
396
|
+
assert_singular!(parents)
|
|
397
|
+
|
|
398
|
+
if self.class.scope_proc
|
|
399
|
+
build_sideload_scope(parents, query, graph_parent).resolve do |sideload_results|
|
|
400
|
+
fire_assign(parents, sideload_results)
|
|
401
|
+
end
|
|
402
|
+
else
|
|
403
|
+
load(parents, query, graph_parent)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def assert_singular!(parents)
|
|
408
|
+
if single? && parents.length > 1
|
|
409
|
+
raise Errors::SingularSideload.new(self, parents.length)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def build_sideload_scope(parents, query, graph_parent)
|
|
414
|
+
Scope.new fire_scope(parents),
|
|
415
|
+
resource,
|
|
416
|
+
query,
|
|
417
|
+
parent: graph_parent,
|
|
418
|
+
sideload: self,
|
|
419
|
+
sideload_parent_length: parents.length,
|
|
420
|
+
default_paginate: false
|
|
421
|
+
end
|
|
422
|
+
|
|
404
423
|
def future_load(parents, query, graph_parent)
|
|
405
424
|
proxy = build_resource_proxy(parents, query, graph_parent)
|
|
406
425
|
proxy.respond_to?(:future_resolve_data) ? proxy.future_resolve_data : Concurrent::Promises.fulfilled_future(proxy)
|