graphiti 2.0.0.beta.6 → 2.0.0.beta.8
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/.git-blame-ignore-revs +2 -0
- data/.github/workflows/ci.yml +55 -3
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/notify-example-app.yml +18 -0
- data/.gitignore +2 -0
- data/.standard.yml +6 -0
- data/CHANGELOG.md +54 -0
- data/CONTRIBUTING.md +3 -1
- data/README.md +13 -206
- data/Rakefile +6 -3
- data/UPGRADING.md +1 -1
- data/graphiti.gemspec +8 -4
- data/lib/generators/graphiti/generator_mixin.rb +1 -1
- data/lib/generators/graphiti/install_generator.rb +1 -2
- data/lib/generators/graphiti/resource_generator.rb +6 -3
- data/lib/generators/graphiti/templates/application_resource.rb.erb +19 -0
- data/lib/generators/graphiti/templates/controller.rb.erb +26 -8
- data/lib/generators/graphiti/templates/index_request_spec.rb.erb +2 -2
- data/lib/generators/graphiti/templates/resource_reads_spec.rb.erb +4 -4
- data/lib/generators/graphiti/templates/show_request_spec.rb.erb +2 -2
- data/lib/graphiti/audit/report.rb +6 -6
- data/lib/graphiti/audit.rb +3 -4
- data/lib/graphiti/delegates/pagination.rb +2 -2
- data/lib/graphiti/deserializer.rb +3 -3
- data/lib/graphiti/errors.rb +6 -6
- data/lib/graphiti/extensions/temp_id.rb +1 -1
- data/lib/graphiti/query.rb +27 -8
- data/lib/graphiti/rails/context.rb +2 -1
- data/lib/graphiti/rails/controller.rb +3 -0
- data/lib/graphiti/rails/debugging.rb +1 -1
- data/lib/graphiti/rails/responders.rb +2 -1
- data/lib/graphiti/resource/configuration.rb +1 -0
- data/lib/graphiti/resource/dsl.rb +2 -2
- data/lib/graphiti/resource/interface.rb +3 -3
- data/lib/graphiti/resource/links.rb +1 -1
- data/lib/graphiti/resource/persistence.rb +1 -1
- data/lib/graphiti/resource/polymorphism.rb +1 -1
- data/lib/graphiti/resource.rb +2 -2
- data/lib/graphiti/resource_proxy.rb +0 -1
- data/lib/graphiti/runner.rb +8 -3
- data/lib/graphiti/scope.rb +13 -0
- data/lib/graphiti/scoping/filter.rb +5 -3
- data/lib/graphiti/scoping/paginate.rb +1 -1
- data/lib/graphiti/scoping/sort.rb +1 -1
- data/lib/graphiti/sideload/has_many.rb +1 -1
- data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
- data/lib/graphiti/spec_helpers/matchers.rb +1 -1
- data/lib/graphiti/spec_helpers.rb +5 -0
- data/lib/graphiti/types.rb +5 -1
- data/lib/graphiti/util/cache_debug.rb +3 -3
- data/lib/graphiti/util/hash.rb +1 -1
- data/lib/graphiti/util/persistence.rb +6 -6
- data/lib/graphiti/util/serializer_relationships.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- data/lib/tasks/graphiti.rake +1 -1
- data/package.json +1 -0
- metadata +31 -197
- data/docs/concepts/backends-and-models.md +0 -122
- data/docs/concepts/endpoints.md +0 -183
- data/docs/concepts/links.md +0 -212
- data/docs/concepts/overview.md +0 -80
- data/docs/concepts/persisting.md +0 -376
- data/docs/concepts/relationships.md +0 -627
- data/docs/concepts/resources.md +0 -677
- data/docs/getting-started/first-api.md +0 -289
- data/docs/getting-started/installation.md +0 -185
- data/docs/intro.md +0 -307
- data/docs/js/authentication.md +0 -63
- data/docs/js/ddau.md +0 -20
- data/docs/js/extra-params.md +0 -41
- data/docs/js/index.md +0 -112
- data/docs/js/installation.md +0 -120
- data/docs/js/middleware.md +0 -72
- data/docs/js/models.md +0 -202
- data/docs/js/reads.md +0 -494
- data/docs/js/state-syncing.md +0 -100
- data/docs/js/writes.md +0 -373
- data/docs/reference/vandal.md +0 -63
- data/docs/reference/why.md +0 -13
- data/docs/topics/authorization.md +0 -155
- data/docs/topics/caching.md +0 -55
- data/docs/topics/customizing-sideloads.md +0 -156
- data/docs/topics/debugging.md +0 -242
- data/docs/topics/error-handling.md +0 -210
- data/docs/topics/etags.md +0 -46
- data/docs/topics/hopping-relationships.md +0 -149
- data/docs/topics/json-attributes.md +0 -77
- data/docs/topics/openstruct-models.md +0 -50
- data/docs/topics/remote-resources.md +0 -291
- data/docs/topics/testing.md +0 -894
- data/docs/topics/without-activerecord.md +0 -324
- data/docs/tutorial/index.md +0 -58
- data/docs/tutorial/step_0.md +0 -107
- data/docs/tutorial/step_1.md +0 -199
- data/docs/tutorial/step_2.md +0 -312
- data/docs/tutorial/step_3.md +0 -142
- data/docs/tutorial/step_4.md +0 -135
- data/docs/tutorial/step_5.md +0 -69
- data/docs/tutorial/step_6.md +0 -82
- data/docs/tutorial/step_7.md +0 -205
- data/docs/tutorial/step_8.md +0 -128
- data/docs/tutorial/step_9.md +0 -171
- data/docs/upgrading.md +0 -266
- data/website/.gitignore +0 -20
- data/website/README.md +0 -43
- data/website/docusaurus.config.js +0 -197
- data/website/package-lock.json +0 -19474
- data/website/package.json +0 -46
- data/website/sidebars.js +0 -82
- data/website/src/css/custom.css +0 -85
- data/website/src/pages/markdown-page.mdx +0 -7
- data/website/static/.nojekyll +0 -0
- data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
- data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
- data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
- data/website/static/1.13/2019/10/14/tutorial.html +0 -198
- data/website/static/1.13/CNAME +0 -1
- data/website/static/1.13/README.md +0 -16
- data/website/static/1.13/assets/css/syntax.css +0 -60
- 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 +0 -9
- 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 +0 -1
- data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
- 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/fancy-cushion.png +0 -0
- data/website/static/1.13/assets/img/gh.png +0 -0
- data/website/static/1.13/assets/img/home-bg.jpg +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/sunrise.png +0 -0
- data/website/static/1.13/assets/img/why.png +0 -0
- data/website/static/1.13/assets/js/highlight.pack.js +0 -2
- data/website/static/1.13/assets/main.css +0 -15518
- data/website/static/1.13/assets/main.css.map +0 -1
- data/website/static/1.13/bin/bundle +0 -109
- data/website/static/1.13/bin/jekyll +0 -27
- data/website/static/1.13/bin/kramdown +0 -27
- data/website/static/1.13/bin/listen +0 -27
- data/website/static/1.13/bin/rake +0 -27
- data/website/static/1.13/bin/rougify +0 -27
- data/website/static/1.13/bin/safe_yaml +0 -27
- data/website/static/1.13/bin/sass +0 -27
- data/website/static/1.13/bin/sass-convert +0 -27
- data/website/static/1.13/bin/scss +0 -27
- data/website/static/1.13/blog.html +0 -259
- data/website/static/1.13/cheatsheet.html +0 -316
- 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 +0 -325
- data/website/static/1.13/cookbooks/etags.md +0 -0
- data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
- 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 +0 -510
- data/website/static/1.13/features.html +0 -249
- data/website/static/1.13/feed.xml +0 -106
- data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
- data/website/static/1.13/guides/concepts/debugging.html +0 -440
- data/website/static/1.13/guides/concepts/endpoints.html +0 -432
- data/website/static/1.13/guides/concepts/error-handling.html +0 -396
- data/website/static/1.13/guides/concepts/links.html +0 -501
- data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
- data/website/static/1.13/guides/concepts/resources.html +0 -2176
- data/website/static/1.13/guides/concepts/testing.html +0 -1469
- data/website/static/1.13/guides/getting-started/installation.html +0 -420
- data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
- data/website/static/1.13/guides/index.html +0 -269
- data/website/static/1.13/guides/overview.html +0 -325
- data/website/static/1.13/guides/upgrading-2-0.html +0 -193
- data/website/static/1.13/guides/upgrading.html +0 -314
- data/website/static/1.13/guides/vandal.html +0 -282
- data/website/static/1.13/guides/why.html +0 -1121
- data/website/static/1.13/index.html +0 -72
- data/website/static/1.13/js/authentication.html +0 -295
- data/website/static/1.13/js/ddau.html +0 -238
- data/website/static/1.13/js/extra-params.html +0 -270
- data/website/static/1.13/js/index.html +0 -321
- data/website/static/1.13/js/installation.html +0 -637
- data/website/static/1.13/js/introduction.html +0 -257
- data/website/static/1.13/js/middleware.html +0 -318
- data/website/static/1.13/js/reads/fieldsets.html +0 -271
- data/website/static/1.13/js/reads/filtering.html +0 -289
- data/website/static/1.13/js/reads/includes.html +0 -260
- data/website/static/1.13/js/reads/index.html +0 -497
- data/website/static/1.13/js/reads/nested-queries.html +0 -353
- data/website/static/1.13/js/reads/pagination.html +0 -260
- data/website/static/1.13/js/reads/sorting.html +0 -265
- data/website/static/1.13/js/reads/statistics.html +0 -289
- data/website/static/1.13/js/state-syncing.html +0 -340
- data/website/static/1.13/js/writes/deferred.html +0 -296
- data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
- data/website/static/1.13/js/writes/index.html +0 -391
- data/website/static/1.13/js/writes/nested.html +0 -330
- data/website/static/1.13/js/writes/validations.html +0 -272
- data/website/static/1.13/quickstart.html +0 -660
- data/website/static/1.13/template +0 -161
- data/website/static/1.13/tutorial/index.html +0 -250
- data/website/static/1.13/tutorial/step_0.html +0 -292
- data/website/static/1.13/tutorial/step_1.html +0 -517
- data/website/static/1.13/tutorial/step_2.html +0 -481
- data/website/static/1.13/tutorial/step_3.html +0 -323
- data/website/static/1.13/tutorial/step_4.html +0 -318
- data/website/static/1.13/tutorial/step_5.html +0 -265
- data/website/static/1.13/tutorial/step_6.html +0 -276
- data/website/static/1.13/tutorial/step_7.html +0 -390
- data/website/static/1.13/tutorial/step_8.html +0 -316
- data/website/static/1.13/tutorial/step_9.html +0 -365
- data/website/static/CNAME +0 -1
- 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 +0 -1
- data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
- data/website/static/img/undraw_docusaurus_react.svg +0 -170
- data/website/static/img/undraw_docusaurus_tree.svg +0 -40
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
<% module_namespacing do -%>
|
|
2
2
|
class <%= controller_class_name %> < ApplicationController
|
|
3
3
|
<%- if actions?('index') -%>
|
|
4
|
+
<%- if responders? -%>
|
|
4
5
|
def index
|
|
5
6
|
<%= file_name.pluralize %> = <%= resource_klass %>.all(params)
|
|
6
|
-
<%- if responders? -%>
|
|
7
7
|
respond_with(<%= file_name.pluralize %>)
|
|
8
|
-
<%- else -%>
|
|
9
|
-
render jsonapi: <%= file_name.pluralize %>
|
|
10
|
-
<%- end -%>
|
|
11
8
|
end
|
|
9
|
+
<%- else -%>
|
|
10
|
+
<%- unless omit_comments? -%>
|
|
11
|
+
# With Graphiti::Rails::Responders included, these blocks collapse to
|
|
12
|
+
# respond_with(<%= file_name.pluralize %>). See https://graphiti.dev/getting-started/installation#responders
|
|
13
|
+
<%- end -%>
|
|
14
|
+
def index
|
|
15
|
+
<%= file_name.pluralize %> = <%= resource_klass %>.all(params)
|
|
16
|
+
|
|
17
|
+
respond_to do |format|
|
|
18
|
+
format.jsonapi { render jsonapi: <%= file_name.pluralize %> }
|
|
19
|
+
format.json { render json: <%= file_name.pluralize %> }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
<%- end -%>
|
|
12
23
|
<%- end -%>
|
|
13
24
|
<%- if actions?('show') -%>
|
|
14
25
|
|
|
26
|
+
<%- if responders? -%>
|
|
15
27
|
def show
|
|
16
28
|
<%= file_name %> = <%= resource_klass %>.find(params)
|
|
17
|
-
<%- if responders? -%>
|
|
18
29
|
respond_with(<%= file_name %>)
|
|
19
|
-
<%- else -%>
|
|
20
|
-
render jsonapi: <%= file_name %>
|
|
21
|
-
<%- end -%>
|
|
22
30
|
end
|
|
31
|
+
<%- else -%>
|
|
32
|
+
def show
|
|
33
|
+
<%= file_name %> = <%= resource_klass %>.find(params)
|
|
34
|
+
|
|
35
|
+
respond_to do |format|
|
|
36
|
+
format.jsonapi { render jsonapi: <%= file_name %> }
|
|
37
|
+
format.json { render json: <%= file_name %> }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
<%- end -%>
|
|
23
41
|
<%- end -%>
|
|
24
42
|
<%- if actions?('create') -%>
|
|
25
43
|
|
|
@@ -15,8 +15,8 @@ RSpec.describe "<%= type %>#index", type: :request do
|
|
|
15
15
|
expect(<%= resource_class %>).to receive(:all).and_call_original
|
|
16
16
|
make_request
|
|
17
17
|
expect(response.status).to eq(200), response.body
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
18
|
+
expect(jsonapi_data.map(&:jsonapi_type).uniq).to match_array(['<%= type %>'])
|
|
19
|
+
expect(jsonapi_data.map(&:<%= id_or_rawid %>)).to match_array([<%= var %>1.id, <%= var %>2.id])
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -11,7 +11,7 @@ RSpec.describe <%= resource_class %>, type: :resource do
|
|
|
11
11
|
expect(data.jsonapi_type).to eq('<%= type %>')
|
|
12
12
|
<%- attributes.each do |a| -%>
|
|
13
13
|
<%- if [:created_at, :updated_at].include?(a.name.to_sym) -%>
|
|
14
|
-
expect(data.<%= a.name %>).to eq(
|
|
14
|
+
expect(data.<%= a.name %>).to eq(json_datetime(<%= file_name %>.<%= a.name %>))
|
|
15
15
|
<%- else -%>
|
|
16
16
|
expect(data.<%= a.name %>).to eq(<%= file_name %>.<%= a.name %>)
|
|
17
17
|
<%- end -%>
|
|
@@ -31,7 +31,7 @@ RSpec.describe <%= resource_class %>, type: :resource do
|
|
|
31
31
|
|
|
32
32
|
it 'works' do
|
|
33
33
|
render
|
|
34
|
-
expect(
|
|
34
|
+
expect(jsonapi_data.map(&:<%= id_or_rawid %>)).to eq([<%= var %>2.id])
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -48,7 +48,7 @@ RSpec.describe <%= resource_class %>, type: :resource do
|
|
|
48
48
|
|
|
49
49
|
it 'works' do
|
|
50
50
|
render
|
|
51
|
-
expect(
|
|
51
|
+
expect(jsonapi_data.map(&:<%= id_or_rawid %>)).to eq([
|
|
52
52
|
<%= var %>1.id,
|
|
53
53
|
<%= var %>2.id
|
|
54
54
|
]<%= sort_raw_ids %>)
|
|
@@ -62,7 +62,7 @@ RSpec.describe <%= resource_class %>, type: :resource do
|
|
|
62
62
|
|
|
63
63
|
it 'works' do
|
|
64
64
|
render
|
|
65
|
-
expect(
|
|
65
|
+
expect(jsonapi_data.map(&:<%= id_or_rawid %>)).to eq([
|
|
66
66
|
<%= var %>2.id,
|
|
67
67
|
<%= var %>1.id
|
|
68
68
|
]<%= sort_raw_ids_descending %>)
|
|
@@ -14,8 +14,8 @@ RSpec.describe "<%= type %>#show", type: :request do
|
|
|
14
14
|
expect(<%= resource_class %>).to receive(:find).and_call_original
|
|
15
15
|
make_request
|
|
16
16
|
expect(response.status).to eq(200)
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
17
|
+
expect(jsonapi_data.jsonapi_type).to eq('<%= type %>')
|
|
18
|
+
expect(jsonapi_data.<%= id_or_rawid %>).to eq(<%= var %>.id)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -60,7 +60,7 @@ module Graphiti
|
|
|
60
60
|
|
|
61
61
|
section_heading(label, color, ISSUE_HEADINGS.fetch(check, check.to_s.tr("_", " "))) +
|
|
62
62
|
lines_grouped_by_resource(group.map { |row, finding| [row, finding.message] }) +
|
|
63
|
-
(fixes.size == 1 ? [fix_line(fixes.first), ""] : [])
|
|
63
|
+
((fixes.size == 1) ? [fix_line(fixes.first), ""] : [])
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def loading_section
|
|
@@ -71,7 +71,7 @@ module Graphiti
|
|
|
71
71
|
lines_grouped_by_resource(rows.map { |row| [row, nil] }) +
|
|
72
72
|
[" #{paint("That is a query per record rendered.", :dim)}",
|
|
73
73
|
fix_line("preload the association in base_scope, or drop `resource_ids`"),
|
|
74
|
-
" #{paint("See
|
|
74
|
+
" #{paint("See graphiti.dev/concepts/relationships#customizing-relationships", :dim)}",
|
|
75
75
|
""]
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -83,7 +83,7 @@ module Graphiti
|
|
|
83
83
|
rows = @rows.select(&:would_start_loading?)
|
|
84
84
|
return [] if rows.empty?
|
|
85
85
|
|
|
86
|
-
verb = rows.size == 1 ? "renders" : "render"
|
|
86
|
+
verb = (rows.size == 1) ? "renders" : "render"
|
|
87
87
|
|
|
88
88
|
section_heading("FYI", :blue, "#{count(rows.size, "belongs_to relationship")} #{verb} no resource ids") +
|
|
89
89
|
lines_grouped_by_resource(rows.map { |row| [row, nil] }) +
|
|
@@ -95,7 +95,7 @@ module Graphiti
|
|
|
95
95
|
["",
|
|
96
96
|
" #{paint("Either would load the association on every render. That potential performance cost is why ids are opt-in.", :dim)}",
|
|
97
97
|
" #{paint("Preloading the association in base_scope keeps that load cheap.", :dim)}",
|
|
98
|
-
" #{paint("See
|
|
98
|
+
" #{paint("See graphiti.dev/concepts/relationships#belongs-to-resource-ids", :dim)}",
|
|
99
99
|
""]
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -189,7 +189,7 @@ module Graphiti
|
|
|
189
189
|
|
|
190
190
|
rows.map do |cells|
|
|
191
191
|
line = cells.each_with_index.map { |cell, index|
|
|
192
|
-
index == cells.size - 1 ? cell.painted : cell.pad_to(widths[index])
|
|
192
|
+
(index == cells.size - 1) ? cell.painted : cell.pad_to(widths[index])
|
|
193
193
|
}.join(" ")
|
|
194
194
|
indent + line
|
|
195
195
|
end
|
|
@@ -203,7 +203,7 @@ module Graphiti
|
|
|
203
203
|
counts = [
|
|
204
204
|
count(@rows.group_by(&:resource).size, "resource"),
|
|
205
205
|
count(@rows.size, "relationship"),
|
|
206
|
-
paint(count(errors, "error"), errors > 0 ? :red : nil)
|
|
206
|
+
paint(count(errors, "error"), (errors > 0) ? :red : nil)
|
|
207
207
|
]
|
|
208
208
|
counts << paint("#{loading} loading ids without preloading", :yellow) if loading > 0
|
|
209
209
|
counts << "#{no_ids} without resource ids" if no_ids > 0
|
data/lib/graphiti/audit.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Graphiti
|
|
2
2
|
# Resources register themselves as they load, so eager load before auditing.
|
|
3
3
|
class Audit
|
|
4
|
-
Finding = Struct.new(:severity, :check, :message, :remedy
|
|
4
|
+
Finding = Struct.new(:severity, :check, :message, :remedy) do
|
|
5
5
|
def error?
|
|
6
6
|
severity == :error
|
|
7
7
|
end
|
|
@@ -18,8 +18,7 @@ module Graphiti
|
|
|
18
18
|
:resource_ids_blocker,
|
|
19
19
|
:preloaded,
|
|
20
20
|
:options,
|
|
21
|
-
:findings
|
|
22
|
-
keyword_init: true
|
|
21
|
+
:findings
|
|
23
22
|
) do
|
|
24
23
|
def would_start_loading?
|
|
25
24
|
return false if resource_ids_default == :never
|
|
@@ -75,7 +74,7 @@ module Graphiti
|
|
|
75
74
|
resource_ids_source_if_always: resource_ids_source_if_always(resource_class, sideload),
|
|
76
75
|
resource_ids_default: resource_class.belongs_to_resource_ids_by_default,
|
|
77
76
|
resource_ids_blocker: sideload.resource_ids_blocker,
|
|
78
|
-
preloaded: source == :load ? association_preloaded?(resource_class, sideload) : nil,
|
|
77
|
+
preloaded: (source == :load) ? association_preloaded?(resource_class, sideload) : nil,
|
|
79
78
|
options: declaration_options(sideload),
|
|
80
79
|
findings: [
|
|
81
80
|
missing_association_method(sideload, model),
|
|
@@ -32,7 +32,7 @@ module Graphiti
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def pagination_params
|
|
35
|
-
@pagination_params ||= @proxy.query.params.
|
|
35
|
+
@pagination_params ||= @proxy.query.params.except(:action, :controller, :format)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def pagination_link(page)
|
|
@@ -109,7 +109,7 @@ module Graphiti
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def page_param
|
|
112
|
-
@page_param ||=
|
|
112
|
+
@page_param ||= @proxy.query.hash[:page] || {}
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
end
|
|
@@ -85,7 +85,7 @@ class Graphiti::Deserializer
|
|
|
85
85
|
def meta(action: :update)
|
|
86
86
|
{
|
|
87
87
|
type: data[:type],
|
|
88
|
-
temp_id: data[:
|
|
88
|
+
temp_id: data[:"temp-id"],
|
|
89
89
|
method: action,
|
|
90
90
|
payload_path: ["data"]
|
|
91
91
|
}
|
|
@@ -162,12 +162,12 @@ class Graphiti::Deserializer
|
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
def process_relationship_datum(datum)
|
|
165
|
-
temp_id = datum[:
|
|
165
|
+
temp_id = datum[:"temp-id"]
|
|
166
166
|
included_object = included.find { |i|
|
|
167
167
|
next unless i[:type] == datum[:type]
|
|
168
168
|
|
|
169
169
|
(i[:id] && i[:id] == datum[:id]) ||
|
|
170
|
-
(i[:
|
|
170
|
+
(i[:"temp-id"] && i[:"temp-id"] == temp_id)
|
|
171
171
|
}
|
|
172
172
|
included_idx = included.index(included_object)
|
|
173
173
|
|
data/lib/graphiti/errors.rb
CHANGED
|
@@ -270,7 +270,7 @@ module Graphiti
|
|
|
270
270
|
#{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
|
|
271
271
|
|
|
272
272
|
The error was raised while attempting to build query parameters for the associated Resource.
|
|
273
|
-
Read more about sideload scoping here:
|
|
273
|
+
Read more about sideload scoping here: graphiti.dev/concepts/relationships#customizing-scope
|
|
274
274
|
|
|
275
275
|
A good way to debug is to put a debugger within the 'params' block.
|
|
276
276
|
|
|
@@ -294,7 +294,7 @@ module Graphiti
|
|
|
294
294
|
|
|
295
295
|
The error was raised while attempting to build the scope for the associated Resource.
|
|
296
296
|
|
|
297
|
-
Read more about sideload scoping here:
|
|
297
|
+
Read more about sideload scoping here: graphiti.dev/concepts/relationships#customizing-scope
|
|
298
298
|
|
|
299
299
|
Here's the original, underlying error:
|
|
300
300
|
|
|
@@ -315,7 +315,7 @@ module Graphiti
|
|
|
315
315
|
#{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
|
|
316
316
|
|
|
317
317
|
The error was raised while attempting to assign relevant model instances. Read
|
|
318
|
-
more about sideload assignment here:
|
|
318
|
+
more about sideload assignment here: graphiti.dev/concepts/relationships#customizing-assignment
|
|
319
319
|
|
|
320
320
|
A good way to debug is to put a debugger within the 'assign' block.
|
|
321
321
|
|
|
@@ -449,7 +449,7 @@ module Graphiti
|
|
|
449
449
|
|
|
450
450
|
self.validate_endpoints = false
|
|
451
451
|
|
|
452
|
-
See https://
|
|
452
|
+
See https://graphiti.dev/concepts/links for more information.
|
|
453
453
|
|
|
454
454
|
The current endpoints allowed for this resource are: #{@resource_class.endpoints.inspect}
|
|
455
455
|
MSG
|
|
@@ -731,7 +731,7 @@ module Graphiti
|
|
|
731
731
|
|
|
732
732
|
<<~MSG
|
|
733
733
|
|
|
734
|
-
resource_ids is set on this relationship, so every render reads the association, not just requests that include it. See
|
|
734
|
+
resource_ids is set on this relationship, so every render reads the association, not just requests that include it. See graphiti.dev/concepts/relationships#customizing-relationships.
|
|
735
735
|
MSG
|
|
736
736
|
end
|
|
737
737
|
end
|
|
@@ -909,7 +909,7 @@ module Graphiti
|
|
|
909
909
|
def initialize(resource, filter_names, required)
|
|
910
910
|
@resource = resource
|
|
911
911
|
@filter_names = filter_names
|
|
912
|
-
@required_label = required == :all ? "All" : "One"
|
|
912
|
+
@required_label = (required == :all) ? "All" : "One"
|
|
913
913
|
end
|
|
914
914
|
|
|
915
915
|
def message
|
data/lib/graphiti/query.rb
CHANGED
|
@@ -4,8 +4,15 @@ module Graphiti
|
|
|
4
4
|
class Query
|
|
5
5
|
attr_reader :resource, :association_name, :params, :action
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
def initialize(resource, params, *positional, association_name: nil, nested_include: nil, parents: [], action: nil)
|
|
8
|
+
if positional.any?
|
|
9
|
+
Graphiti::DEPRECATOR.warn("Passing Query.new trailing arguments positionally is deprecated. Use association_name:/nested_include:/parents:/action: keywords.")
|
|
10
|
+
association_name ||= positional[0]
|
|
11
|
+
nested_include ||= positional[1]
|
|
12
|
+
parents = positional[2] if positional.length > 2
|
|
13
|
+
action ||= positional[3]
|
|
14
|
+
end
|
|
15
|
+
|
|
9
16
|
@resource = resource
|
|
10
17
|
@association_name = association_name
|
|
11
18
|
@params = params
|
|
@@ -21,6 +28,13 @@ module Graphiti
|
|
|
21
28
|
!!@association_name
|
|
22
29
|
end
|
|
23
30
|
|
|
31
|
+
# Concurrent::Map because sideload scopes resolve on pool threads
|
|
32
|
+
def entity_map
|
|
33
|
+
return root.entity_map unless root == self
|
|
34
|
+
|
|
35
|
+
@entity_map ||= Concurrent::Map.new
|
|
36
|
+
end
|
|
37
|
+
|
|
24
38
|
def top_level?
|
|
25
39
|
!association?
|
|
26
40
|
end
|
|
@@ -110,9 +124,10 @@ module Graphiti
|
|
|
110
124
|
relationship_name = sideload ? sideload.name : key
|
|
111
125
|
hash[relationship_name] = Query.new sl_resource,
|
|
112
126
|
@params,
|
|
113
|
-
key,
|
|
114
|
-
sub_hash,
|
|
115
|
-
query_parents,
|
|
127
|
+
association_name: key,
|
|
128
|
+
nested_include: sub_hash,
|
|
129
|
+
parents: query_parents,
|
|
130
|
+
action: :all
|
|
116
131
|
else
|
|
117
132
|
handle_missing_sideload(key)
|
|
118
133
|
end
|
|
@@ -124,6 +139,10 @@ module Graphiti
|
|
|
124
139
|
@parents ||= []
|
|
125
140
|
end
|
|
126
141
|
|
|
142
|
+
def root
|
|
143
|
+
parents.first || self
|
|
144
|
+
end
|
|
145
|
+
|
|
127
146
|
def fields
|
|
128
147
|
@fields ||= begin
|
|
129
148
|
hash = parse_fieldset(@params[:fields] || {})
|
|
@@ -216,7 +235,7 @@ module Graphiti
|
|
|
216
235
|
|
|
217
236
|
scrubbed.filter do |key, value|
|
|
218
237
|
sideload = @resource.class.sideload(key)
|
|
219
|
-
sideload.nil?
|
|
238
|
+
sideload.nil? || sideload.readable?
|
|
220
239
|
end
|
|
221
240
|
end
|
|
222
241
|
end
|
|
@@ -292,7 +311,7 @@ module Graphiti
|
|
|
292
311
|
return false unless association?
|
|
293
312
|
|
|
294
313
|
split = name.to_s.split(".")
|
|
295
|
-
query_names = split[0
|
|
314
|
+
query_names = split[0..-2].map(&:to_sym)
|
|
296
315
|
my_names = parents.map(&:association_name).compact + [association_name].compact
|
|
297
316
|
query_names == my_names
|
|
298
317
|
end
|
|
@@ -324,7 +343,7 @@ module Graphiti
|
|
|
324
343
|
end
|
|
325
344
|
|
|
326
345
|
def sort_hash(attr)
|
|
327
|
-
value = attr[0] == "-" ? :desc : :asc
|
|
346
|
+
value = (attr[0] == "-") ? :desc : :asc
|
|
328
347
|
key = attr.sub("-", "").to_sym
|
|
329
348
|
|
|
330
349
|
{key => value}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module Graphiti
|
|
2
2
|
module Rails
|
|
3
|
-
# Wraps controller actions in a [Graphiti Context](https://
|
|
3
|
+
# Wraps controller actions in a [Graphiti Context](https://graphiti.dev/concepts/resources#context) which points to the
|
|
4
4
|
# controller instance by default.
|
|
5
5
|
module Context
|
|
6
6
|
def self.included(klass)
|
|
7
7
|
klass.class_eval do
|
|
8
8
|
include Graphiti::Context
|
|
9
|
+
|
|
9
10
|
around_action :wrap_graphiti_context
|
|
10
11
|
end
|
|
11
12
|
end
|
|
@@ -17,6 +17,9 @@ module Graphiti
|
|
|
17
17
|
included do
|
|
18
18
|
include Graphiti::Rails::Context
|
|
19
19
|
include Graphiti::Rails::Debugging
|
|
20
|
+
# ActionController::API strips this out, but Graphiti serves multiple
|
|
21
|
+
# formats (jsonapi/json/xml), so respond_to has to work everywhere.
|
|
22
|
+
include ActionController::MimeResponds
|
|
20
23
|
|
|
21
24
|
# Broad, but the handler only renders formats listed in
|
|
22
25
|
# config.graphiti.handled_exception_formats and hands everything else
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Graphiti
|
|
2
2
|
module Rails
|
|
3
|
-
# Wraps controller actions in a [Graphiti Debugger](https://
|
|
3
|
+
# Wraps controller actions in a [Graphiti Debugger](https://graphiti.dev/topics/debugging#debugger).
|
|
4
4
|
module Debugging
|
|
5
5
|
def self.included(klass)
|
|
6
6
|
klass.around_action :debug_graphiti
|
|
@@ -6,6 +6,7 @@ module Graphiti
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
include ActionController::MimeResponds
|
|
9
|
+
|
|
9
10
|
respond_to(*Graphiti::Rails.respond_to_formats)
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -14,7 +15,7 @@ module Graphiti
|
|
|
14
15
|
opts = args.extract_options!
|
|
15
16
|
opts[:location] = nil
|
|
16
17
|
args << opts
|
|
17
|
-
super
|
|
18
|
+
super
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
|
@@ -9,7 +9,7 @@ module Graphiti
|
|
|
9
9
|
opts = args.extract_options!
|
|
10
10
|
type_override = args[0]
|
|
11
11
|
|
|
12
|
-
if (att =
|
|
12
|
+
if (att = attributes[name] || extra_attributes[name])
|
|
13
13
|
# We're opting in to filtering, so force this
|
|
14
14
|
# UNLESS the filter is guarded at the attribute level
|
|
15
15
|
att[:filterable] = true if att[:filterable] == false
|
|
@@ -27,7 +27,7 @@ module Graphiti
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
required = att[:filterable] == :required || !!opts[:required]
|
|
30
|
-
schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
|
|
30
|
+
schema = (!!opts[:via_attribute_dsl]) ? att[:schema] : opts[:schema] != false
|
|
31
31
|
|
|
32
32
|
config[:filters][name.to_sym] = {
|
|
33
33
|
aliases: aliases,
|
|
@@ -17,7 +17,7 @@ module Graphiti
|
|
|
17
17
|
|
|
18
18
|
# @api private
|
|
19
19
|
def _all(params, opts, base_scope)
|
|
20
|
-
runner = Runner.new(self, params, opts.delete(:query), :all)
|
|
20
|
+
runner = Runner.new(self, params, query: opts.delete(:query), action: :all)
|
|
21
21
|
opts[:params] = params
|
|
22
22
|
runner.proxy(base_scope, opts.merge(caching_options))
|
|
23
23
|
end
|
|
@@ -36,7 +36,7 @@ module Graphiti
|
|
|
36
36
|
params[:filter] ||= {}
|
|
37
37
|
params[:filter][:id] = id if id
|
|
38
38
|
|
|
39
|
-
runner = Runner.new(self, params,
|
|
39
|
+
runner = Runner.new(self, params, action: :find)
|
|
40
40
|
|
|
41
41
|
find_options = {
|
|
42
42
|
single: true,
|
|
@@ -56,7 +56,7 @@ module Graphiti
|
|
|
56
56
|
# Wrap models fetched outside graphiti so they render like any other proxy
|
|
57
57
|
def wrap(models, base_scope = nil)
|
|
58
58
|
validate_wrap_models!(models)
|
|
59
|
-
runner = Runner.new(self, {},
|
|
59
|
+
runner = Runner.new(self, {}, action: :find)
|
|
60
60
|
runner.proxy(base_scope, bypass_required_filters: true).tap do |proxy|
|
|
61
61
|
proxy.data = models
|
|
62
62
|
end
|
|
@@ -65,7 +65,7 @@ module Graphiti
|
|
|
65
65
|
def add_callback(kind, lifecycle, method, only, &blk)
|
|
66
66
|
config[:callbacks][kind] ||= {}
|
|
67
67
|
config[:callbacks][kind][lifecycle] ||= []
|
|
68
|
-
config[:callbacks][kind][lifecycle] << {callback:
|
|
68
|
+
config[:callbacks][kind][lifecycle] << {callback: method || blk, only: Array(only)}
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
data/lib/graphiti/resource.rb
CHANGED
|
@@ -137,14 +137,14 @@ module Graphiti
|
|
|
137
137
|
|
|
138
138
|
# TODO: make foreign_key a keyword once the satellite gems are rolled in - they call these positionally
|
|
139
139
|
def assign_with_relationships(meta, attributes, relationships, caller_model = nil, foreign_key = nil, model_instance: nil)
|
|
140
|
-
persistence = Graphiti::Util::Persistence
|
|
140
|
+
persistence = Graphiti::Util::Persistence
|
|
141
141
|
.new(self, meta, attributes, relationships, caller_model, foreign_key,
|
|
142
142
|
assigned_model: model_instance)
|
|
143
143
|
persistence.assign
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def persist_with_relationships(meta, attributes, relationships, caller_model = nil, foreign_key = nil, assigned_model: nil)
|
|
147
|
-
persistence = Graphiti::Util::Persistence
|
|
147
|
+
persistence = Graphiti::Util::Persistence
|
|
148
148
|
.new(self, meta, attributes, relationships, caller_model, foreign_key,
|
|
149
149
|
assigned_model: assigned_model)
|
|
150
150
|
persistence.run
|
data/lib/graphiti/runner.rb
CHANGED
|
@@ -3,8 +3,13 @@ module Graphiti
|
|
|
3
3
|
attr_reader :params
|
|
4
4
|
attr_reader :deserialized_payload
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
def initialize(resource_class, params, *positional, query: nil, action: nil)
|
|
7
|
+
if positional.any?
|
|
8
|
+
Graphiti::DEPRECATOR.warn("Passing query/action to Runner.new positionally is deprecated. Use query:/action: keywords.")
|
|
9
|
+
query ||= positional[0]
|
|
10
|
+
action ||= positional[1]
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
@resource_class = resource_class
|
|
9
14
|
@params = params
|
|
10
15
|
@query = query
|
|
@@ -30,7 +35,7 @@ module Graphiti
|
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
def query
|
|
33
|
-
@query ||= Query.new(jsonapi_resource, params,
|
|
38
|
+
@query ||= Query.new(jsonapi_resource, params, action: @action)
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
def query_hash
|
data/lib/graphiti/scope.rb
CHANGED
|
@@ -154,12 +154,25 @@ module Graphiti
|
|
|
154
154
|
payload[:results]
|
|
155
155
|
}
|
|
156
156
|
resolved.compact!
|
|
157
|
+
deduplicate_entities!(resolved)
|
|
157
158
|
assign_serializer(resolved)
|
|
158
159
|
yield resolved if block_given?
|
|
159
160
|
@opts[:after_resolve]&.call(resolved)
|
|
160
161
|
resolved
|
|
161
162
|
end
|
|
162
163
|
|
|
164
|
+
# Must run before sideloads assign, so every include path populates the
|
|
165
|
+
# canonical instance. The resource class in the key keeps two resources
|
|
166
|
+
# serving the same model from sharing an instance and a serializer.
|
|
167
|
+
def deduplicate_entities!(resolved)
|
|
168
|
+
resolved.map! do |record|
|
|
169
|
+
next record unless record.respond_to?(:id) && !record.id.nil?
|
|
170
|
+
|
|
171
|
+
key = [@resource.class, record.class, record.id]
|
|
172
|
+
@query.entity_map.compute_if_absent(key) { record }
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
163
176
|
def each_applicable_sideload
|
|
164
177
|
@query.sideloads.each_pair do |name, sideload_query|
|
|
165
178
|
sideload = @resource.class.sideload(name)
|
|
@@ -82,7 +82,7 @@ module Graphiti
|
|
|
82
82
|
if is_array
|
|
83
83
|
@resource.typecast(name, value, :filterable)
|
|
84
84
|
else
|
|
85
|
-
value = value.nil? || value.is_a?(Hash) ? [value] : Array(value)
|
|
85
|
+
value = (value.nil? || value.is_a?(Hash)) ? [value] : Array(value)
|
|
86
86
|
value.map { |v| @resource.typecast(name, v, :filterable) }
|
|
87
87
|
end
|
|
88
88
|
end
|
|
@@ -100,7 +100,7 @@ module Graphiti
|
|
|
100
100
|
|
|
101
101
|
param_value.map do |operator, value|
|
|
102
102
|
if type == :hash
|
|
103
|
-
value, operator =
|
|
103
|
+
value, operator =
|
|
104
104
|
parse_hash_value(filter, param_value, value, operator)
|
|
105
105
|
end
|
|
106
106
|
|
|
@@ -209,7 +209,9 @@ module Graphiti
|
|
|
209
209
|
end
|
|
210
210
|
|
|
211
211
|
def parse_string_null(filter, value)
|
|
212
|
-
return
|
|
212
|
+
return value unless filter[:allow_nil]
|
|
213
|
+
return value.map { |item| (item == "null") ? nil : item } if value.is_a?(Array)
|
|
214
|
+
return if value == "null"
|
|
213
215
|
|
|
214
216
|
value
|
|
215
217
|
end
|