graphiti 2.0.0.beta.3 → 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 +34 -0
- data/README.md +13 -2
- data/UPGRADING.md +2 -58
- 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 -4
- 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/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/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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8f1b385d0a7623788b2a492d98d2175d1964d320ea48f988f73bffb30ab9439
|
|
4
|
+
data.tar.gz: a95f11de0a07753144d5c4d057ae0b4bb90e2f61c5906996bd29f7fa09998657
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f00afaae7e44289e89d5d29e56e3539953e2c13befcc6c2d5dbb95c9b8904ca6103c3651ebabb6227f9e43f50f22ee4b845d1d9235244e363a1c08089363c47
|
|
7
|
+
data.tar.gz: a6f82ed9556a22d2ddd76710877af8f39cfca470cad5a12a05d99ef5d54fc6d03a5c0e549026307cd71b6a6323c51e4889164acd94a38a0d371e0c74fe54760a
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -18,105 +18,49 @@ jobs:
|
|
|
18
18
|
- uses: actions/checkout@v3
|
|
19
19
|
- uses: ruby/setup-ruby@v1
|
|
20
20
|
with:
|
|
21
|
-
ruby-version: "3.
|
|
21
|
+
ruby-version: "3.2"
|
|
22
22
|
bundler-cache: true
|
|
23
23
|
- name: Run standardrb
|
|
24
24
|
run: bundle exec standardrb --no-fix --format progress
|
|
25
25
|
test:
|
|
26
|
-
name: "Tests - ${{ matrix.ruby }} | ${{ matrix.gemfile }}
|
|
26
|
+
name: "Tests - ${{ matrix.ruby }} | ${{ matrix.gemfile }}"
|
|
27
27
|
runs-on: ubuntu-latest
|
|
28
28
|
strategy:
|
|
29
29
|
fail-fast: false
|
|
30
|
+
# Every cell is listed explicitly rather than built from a cross product
|
|
31
|
+
# plus `exclude:`. An exclude row naming a gemfile that does not exist is
|
|
32
|
+
# silently ignored, which is how this matrix carried four dead rows for a
|
|
33
|
+
# "rails_7_0_graphiti_rails.gemfile" that was never generated. A bad
|
|
34
|
+
# gemfile path in an include row fails the job loudly instead.
|
|
35
|
+
#
|
|
36
|
+
# Ruby coverage per Rails version tracks that version's own supported
|
|
37
|
+
# range; see Appraisals for the matching sqlite3 constraints.
|
|
30
38
|
matrix:
|
|
31
|
-
ruby:
|
|
32
|
-
- "3.0"
|
|
33
|
-
- "3.1"
|
|
34
|
-
- "3.2"
|
|
35
|
-
- "3.3"
|
|
36
|
-
- "3.4"
|
|
37
|
-
gemfile:
|
|
38
|
-
- Gemfile
|
|
39
|
-
- gemfiles/rails_6.gemfile
|
|
40
|
-
- gemfiles/rails_7.gemfile
|
|
41
|
-
- gemfiles/rails_6_graphiti_rails.gemfile
|
|
42
|
-
- gemfiles/rails_7_graphiti_rails.gemfile
|
|
43
|
-
- gemfiles/rails_7_1_graphiti_rails.gemfile
|
|
44
|
-
- gemfiles/rails_7_2_graphiti_rails.gemfile
|
|
45
|
-
- gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
46
|
-
- gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
47
|
-
appraisal:
|
|
48
|
-
- true
|
|
49
|
-
- false
|
|
50
39
|
include:
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- ruby:
|
|
55
|
-
|
|
56
|
-
appraisal: true
|
|
57
|
-
- ruby: ruby-head
|
|
58
|
-
gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
59
|
-
appraisal: true
|
|
60
|
-
- ruby: ruby-head
|
|
61
|
-
gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
62
|
-
appraisal: true
|
|
63
|
-
- ruby: ruby-head
|
|
64
|
-
gemfile: Gemfile
|
|
65
|
-
appraisal: false
|
|
66
|
-
exclude:
|
|
67
|
-
# Skip some extra variants
|
|
68
|
-
- gemfile: Gemfile
|
|
69
|
-
appraisal: true
|
|
70
|
-
- gemfile: gemfiles/rails_6.gemfile
|
|
71
|
-
appraisal: false
|
|
72
|
-
- gemfile: gemfiles/rails_7.gemfile
|
|
73
|
-
appraisal: false
|
|
74
|
-
- gemfile: gemfiles/rails_6_graphiti_rails.gemfile
|
|
75
|
-
appraisal: false
|
|
76
|
-
- gemfile: gemfiles/rails_7_graphiti_rails.gemfile
|
|
77
|
-
appraisal: false
|
|
78
|
-
- gemfile: gemfiles/rails_7_1_graphiti_rails.gemfile
|
|
79
|
-
appraisal: false
|
|
80
|
-
- gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
|
|
81
|
-
appraisal: false
|
|
82
|
-
- gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
|
|
83
|
-
appraisal: false
|
|
84
|
-
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
85
|
-
appraisal: false
|
|
40
|
+
# No Rails - the library on its own, across every supported Ruby
|
|
41
|
+
- { ruby: "3.2", gemfile: Gemfile, appraisal: false }
|
|
42
|
+
- { ruby: "3.3", gemfile: Gemfile, appraisal: false }
|
|
43
|
+
- { ruby: "3.4", gemfile: Gemfile, appraisal: false }
|
|
44
|
+
- { ruby: ruby-head, gemfile: Gemfile, appraisal: false }
|
|
86
45
|
|
|
87
|
-
# Rails
|
|
88
|
-
- gemfile: gemfiles/
|
|
89
|
-
|
|
90
|
-
- gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
|
|
91
|
-
ruby: 3.1
|
|
46
|
+
# Rails 7.1 - the floor
|
|
47
|
+
- { ruby: "3.2", gemfile: gemfiles/rails_7_1.gemfile, appraisal: true }
|
|
48
|
+
- { ruby: "3.4", gemfile: gemfiles/rails_7_1.gemfile, appraisal: true }
|
|
92
49
|
|
|
93
|
-
# Rails
|
|
94
|
-
- gemfile: gemfiles/
|
|
95
|
-
|
|
96
|
-
- gemfile: gemfiles/
|
|
97
|
-
ruby: 3.1
|
|
50
|
+
# Rails 7.2
|
|
51
|
+
- { ruby: "3.2", gemfile: gemfiles/rails_7_2.gemfile, appraisal: true }
|
|
52
|
+
- { ruby: "3.4", gemfile: gemfiles/rails_7_2.gemfile, appraisal: true }
|
|
53
|
+
- { ruby: ruby-head, gemfile: gemfiles/rails_7_2.gemfile, appraisal: true }
|
|
98
54
|
|
|
99
|
-
# Rails
|
|
100
|
-
- gemfile: gemfiles/
|
|
101
|
-
|
|
55
|
+
# Rails 8.0
|
|
56
|
+
- { ruby: "3.2", gemfile: gemfiles/rails_8_0.gemfile, appraisal: true }
|
|
57
|
+
- { ruby: "3.4", gemfile: gemfiles/rails_8_0.gemfile, appraisal: true }
|
|
58
|
+
- { ruby: ruby-head, gemfile: gemfiles/rails_8_0.gemfile, appraisal: true }
|
|
102
59
|
|
|
103
|
-
# Rails
|
|
104
|
-
- gemfile: gemfiles/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# Rails 7.0 needs ruby 3.1 +
|
|
108
|
-
- gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
|
|
109
|
-
ruby: 3.2
|
|
110
|
-
- gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
|
|
111
|
-
ruby: 3.3
|
|
112
|
-
- gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
|
|
113
|
-
ruby: 3.4
|
|
114
|
-
|
|
115
|
-
# Rails 6 needs < ruby 3.4
|
|
116
|
-
- gemfile: gemfiles/rails_6.gemfile
|
|
117
|
-
ruby: 3.4
|
|
118
|
-
- gemfile: gemfiles/rails_6_graphiti_rails.gemfile
|
|
119
|
-
ruby: 3.4
|
|
60
|
+
# Rails 8.1
|
|
61
|
+
- { ruby: "3.2", gemfile: gemfiles/rails_8_1.gemfile, appraisal: true }
|
|
62
|
+
- { ruby: "3.4", gemfile: gemfiles/rails_8_1.gemfile, appraisal: true }
|
|
63
|
+
- { ruby: ruby-head, gemfile: gemfiles/rails_8_1.gemfile, appraisal: true }
|
|
120
64
|
|
|
121
65
|
continue-on-error: ${{ matrix.ruby == 'ruby-head' }}
|
|
122
66
|
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
# Publishes the documentation site from beta so the deploy is exercised well
|
|
4
|
+
# before it matters. Pages serves this repo under /graphiti/, and the root of
|
|
5
|
+
# graphiti.dev comes from graphiti-api.github.io, so nothing here can affect
|
|
6
|
+
# the live 1.x site. When the domain moves to this repo, drop DOCS_BASE_URL so
|
|
7
|
+
# baseUrl returns to "/" and add website/static/CNAME.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [beta]
|
|
11
|
+
paths:
|
|
12
|
+
- 'docs/**'
|
|
13
|
+
- 'website/**'
|
|
14
|
+
- '.github/workflows/docs.yml'
|
|
15
|
+
workflow_dispatch: {}
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: docs-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
deploy:
|
|
26
|
+
name: Build and publish
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v3
|
|
30
|
+
- uses: actions/setup-node@v4
|
|
31
|
+
with:
|
|
32
|
+
node-version: '20'
|
|
33
|
+
cache: npm
|
|
34
|
+
cache-dependency-path: website/package-lock.json
|
|
35
|
+
|
|
36
|
+
- name: Install
|
|
37
|
+
working-directory: website
|
|
38
|
+
run: npm ci
|
|
39
|
+
|
|
40
|
+
- name: Build
|
|
41
|
+
working-directory: website
|
|
42
|
+
env:
|
|
43
|
+
DOCS_BASE_URL: /graphiti/
|
|
44
|
+
run: npm run build
|
|
45
|
+
|
|
46
|
+
- name: Publish to gh-pages
|
|
47
|
+
working-directory: website/build
|
|
48
|
+
run: |
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
# Pages skips directories beginning with an underscore unless Jekyll
|
|
51
|
+
# is switched off, and Docusaurus emits some.
|
|
52
|
+
touch .nojekyll
|
|
53
|
+
git init --quiet --initial-branch=gh-pages
|
|
54
|
+
git config user.name "github-actions[bot]"
|
|
55
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
56
|
+
git add --all
|
|
57
|
+
git commit --quiet -m "docs: build ${GITHUB_SHA::7} from ${GITHUB_REF_NAME}"
|
|
58
|
+
git push --force --quiet \
|
|
59
|
+
"https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
|
|
60
|
+
gh-pages
|
|
@@ -10,27 +10,27 @@ jobs:
|
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
steps:
|
|
12
12
|
- name: Checkout
|
|
13
|
-
uses: actions/checkout@
|
|
13
|
+
uses: actions/checkout@v5
|
|
14
14
|
with:
|
|
15
|
-
submodules: true
|
|
16
15
|
persist-credentials: false
|
|
17
16
|
- name: Set up Ruby
|
|
18
17
|
uses: ruby/setup-ruby@v1
|
|
19
18
|
with:
|
|
20
|
-
ruby-version: "3.
|
|
19
|
+
ruby-version: "3.4"
|
|
21
20
|
- name: Build
|
|
22
21
|
run: |
|
|
23
22
|
gem install bundler
|
|
24
|
-
git submodule update --init --recursive
|
|
25
23
|
bundle install --jobs 4 --retry 3
|
|
26
24
|
- name: Setup Node.js
|
|
27
|
-
uses: actions/setup-node@
|
|
25
|
+
uses: actions/setup-node@v4
|
|
28
26
|
with:
|
|
29
|
-
|
|
27
|
+
# semantic-release 25 and its plugins require ^22.22.2 || >=24.15
|
|
28
|
+
node-version: "24"
|
|
29
|
+
cache: npm
|
|
30
30
|
- name: Install Dependencies
|
|
31
|
-
run:
|
|
31
|
+
run: npm ci
|
|
32
32
|
- name: Release
|
|
33
33
|
env:
|
|
34
34
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
35
|
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
|
|
36
|
-
run:
|
|
36
|
+
run: npx semantic-release
|
data/.gitignore
CHANGED
data/.npmrc
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# semantic-release-rubygem@1.2.0 declares `peerDependencies: { semantic-release:
|
|
2
|
+
# ">=16.0.0 <18.0.0" }`, but it was last published in July 2020 and that range has
|
|
3
|
+
# simply not been updated since. This project has been releasing against
|
|
4
|
+
# semantic-release 19 for years, and the plugin loads cleanly under 25 exposing
|
|
5
|
+
# its verifyConditions/prepare/publish hooks - it only touches `env` and `cwd` on
|
|
6
|
+
# the plugin context, both stable across semantic-release majors.
|
|
7
|
+
#
|
|
8
|
+
# Without this, `npm ci` fails the install with ERESOLVE.
|
|
9
|
+
legacy-peer-deps=true
|
data/.standard.yml
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
fix: true
|
|
2
2
|
parallel: true
|
|
3
|
-
ruby_version: 3.
|
|
3
|
+
ruby_version: 3.2
|
|
4
4
|
|
|
5
5
|
ignore:
|
|
6
6
|
- 'vendor/**/*'
|
|
7
|
+
# The docs site, including the frozen 1.x archive under static/. None of it
|
|
8
|
+
# is ours to restyle, and some of it is generated.
|
|
9
|
+
- 'website/**/*'
|
|
7
10
|
- 'spec/**/*':
|
|
8
11
|
# There are some false hits when doing repeated
|
|
9
12
|
# Class.new blocks in different test cases
|
data/Appraisals
CHANGED
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
# Graphiti's Rails integration ships in the gem itself as of 2.0, so there is
|
|
2
|
+
# one appraisal per Rails version rather than a with/without graphiti-rails pair.
|
|
3
|
+
#
|
|
4
|
+
# Rails 7.1 is the floor: 2.0 requires Ruby >= 3.2, and Rails 6.1 and 7.0 do not
|
|
5
|
+
# support it. Apps on older Rails stay on the graphiti 1.x branch.
|
|
6
|
+
#
|
|
7
|
+
# Every Rails requirement pins the minor ("~> 7.1.0", not "~> 7.1"). The looser
|
|
8
|
+
# form lets a newer minor satisfy it, which is how rails-7-1 and rails-8-0 ended
|
|
9
|
+
# up resolving to 7.2 and 8.1 - two appraisals apiece testing the same Rails and
|
|
10
|
+
# neither testing the version it was named for.
|
|
11
|
+
#
|
|
12
|
+
# sqlite3 pins follow what each Rails version's adapter demands at require time
|
|
13
|
+
# (activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb):
|
|
14
|
+
# Rails 7.1 -> gem "sqlite3", "~> 1.4" (2.x raises Gem::LoadError)
|
|
15
|
+
# Rails 7.2 -> gem "sqlite3", ">= 1.4"
|
|
16
|
+
# Rails 8.0 / 8.1 -> gem "sqlite3", ">= 2.1"
|
|
17
|
+
# "~> 1.4" rather than "~> 1.4.0" is deliberate: it resolves to 1.7.x, which
|
|
18
|
+
# still builds on modern Rubies, where 1.4.x no longer does.
|
|
15
19
|
|
|
16
|
-
appraise "rails-7" do
|
|
17
|
-
gem "rails", "~> 7.0"
|
|
20
|
+
appraise "rails-7-1" do
|
|
21
|
+
gem "rails", "~> 7.1.0"
|
|
18
22
|
gem "rspec-rails"
|
|
19
|
-
gem "
|
|
20
|
-
gem "
|
|
23
|
+
gem "responders"
|
|
24
|
+
gem "sqlite3", "~> 1.4"
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
appraise "rails-7-
|
|
24
|
-
gem "rails", "~> 7.0"
|
|
27
|
+
appraise "rails-7-2" do
|
|
28
|
+
gem "rails", "~> 7.2.0"
|
|
25
29
|
gem "rspec-rails"
|
|
26
|
-
gem "
|
|
27
|
-
gem "
|
|
28
|
-
gem "graphiti-rails", "~> 0.4.0"
|
|
30
|
+
gem "responders"
|
|
31
|
+
gem "sqlite3", "~> 2.1"
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
appraise "rails-
|
|
32
|
-
gem "rails", "~>
|
|
34
|
+
appraise "rails-8-0" do
|
|
35
|
+
gem "rails", "~> 8.0.0"
|
|
33
36
|
gem "rspec-rails"
|
|
34
|
-
|
|
37
|
+
gem "responders"
|
|
35
38
|
gem "sqlite3", "~> 2.1"
|
|
36
|
-
gem "database_cleaner"
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
appraise "rails-
|
|
40
|
-
gem "rails", "~>
|
|
41
|
+
appraise "rails-8-1" do
|
|
42
|
+
gem "rails", "~> 8.1.0"
|
|
41
43
|
gem "rspec-rails"
|
|
42
|
-
gem "
|
|
43
|
-
gem "
|
|
44
|
-
gem "graphiti-rails", "~> 0.4.0"
|
|
44
|
+
gem "responders"
|
|
45
|
+
gem "sqlite3", "~> 2.1"
|
|
45
46
|
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
graphiti changelog
|
|
2
2
|
|
|
3
|
+
# [2.0.0-beta.4](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2026-08-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* a subclass redeclaring a relationship reaches its serializer ([976dbc4](https://github.com/graphiti-api/graphiti/commit/976dbc47e7bbf951da925a13a34f1bc9679470f8))
|
|
9
|
+
* make each appraisal test the Rails version it is named for ([9e00652](https://github.com/graphiti-api/graphiti/commit/9e00652e124a411a2916d0fef52dc4fd2dc59020))
|
|
10
|
+
* require active_support so graphiti boots without Rails ([d431a03](https://github.com/graphiti-api/graphiti/commit/d431a0328fb4071429f39f4c69cda9c6cee308f2))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Code Refactoring
|
|
14
|
+
|
|
15
|
+
* bridge the remaining 1.x names ([3d80ef9](https://github.com/graphiti-api/graphiti/commit/3d80ef98cf765a152032b7fed89b3cdc10be23dc))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* belongs_to renders resource linkage by default ([024824d](https://github.com/graphiti-api/graphiti/commit/024824d319619811ade886c72ce345cfeb584dbc)), closes [#168](https://github.com/graphiti-api/graphiti/issues/168) [#185](https://github.com/graphiti-api/graphiti/issues/185) [#167](https://github.com/graphiti-api/graphiti/issues/167) [#167](https://github.com/graphiti-api/graphiti/issues/167)
|
|
21
|
+
* fold graphiti_spec_helpers into graphiti ([da955a1](https://github.com/graphiti-api/graphiti/commit/da955a1e090ba44be31bf043ed90baa6661a4772))
|
|
22
|
+
* fold graphiti-rails into graphiti ([7740f8a](https://github.com/graphiti-api/graphiti/commit/7740f8a5803f1fbf02ef4e27d8baa6e0809d2b43)), closes [graphiti-rails#52](https://github.com/graphiti-rails/issues/52)
|
|
23
|
+
* handle exceptions with rescue_registry, fold in graphiti_errors ([e48171c](https://github.com/graphiti-api/graphiti/commit/e48171c117e7b8141fb6f966a972b0c1cd50c5ab))
|
|
24
|
+
* require Ruby 3.2 and Rails 7.1 ([635b249](https://github.com/graphiti-api/graphiti/commit/635b249a99f7d1ef726ae2328707ee6210cc0608))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### BREAKING CHANGES
|
|
28
|
+
|
|
29
|
+
* nothing removed, everything warns and goes away in 3.0. Except `include GraphitiErrors`, which now raises, as rescue_registry replaced it, so there's nothing to point it at.
|
|
30
|
+
* graphiti_errors is no longer a dependency and must be removed from the Gemfile, along with any `include GraphitiErrors`.
|
|
31
|
+
|
|
32
|
+
GraphitiErrors::Validation::Serializer is now Graphiti::ErrorSerializers::Validation, and GraphitiErrors.enable!/disable! becomes handle_request_exceptions. 409 responses now report code "conflict" and title "Conflict Error".
|
|
33
|
+
* Ruby >= 3.2 and Rails >= 7.1 are now required.
|
|
34
|
+
* remove graphiti-rails from your Gemfile. Controllers serving Graphiti resources must `include Graphiti::Rails::Controller` — previously every controller received it whether it wanted it or not. Graphiti::Responders is now Graphiti::Rails::Responders.
|
|
35
|
+
* remove graphiti_spec_helpers from your Gemfile. Prefer Graphiti::SpecHelpers and "graphiti/spec_helpers/rspec"; the old namespace and require paths still resolve, warn, and are removed in 3.0.
|
|
36
|
+
|
|
3
37
|
# [2.0.0-beta.3](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2026-07-31)
|
|
4
38
|
|
|
5
39
|
|
data/README.md
CHANGED
|
@@ -55,7 +55,18 @@ class EmployeeResource < ApplicationResource
|
|
|
55
55
|
end
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
In Rails, controllers serving resources include `Graphiti::Rails::Controller`. Putting it in `ApplicationController` covers the whole app; putting it in an API base class scopes it there and leaves the rest alone.
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
class ApplicationController < ActionController::Base
|
|
62
|
+
include Graphiti::Rails::Controller
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That gives those controllers the Graphiti context, the debugger, and JSON:API rendering for Graphiti's exceptions plus anything else raised. Registering your own exceptions does not require it — `register_exception` is available on every controller — but only controllers with the include render *unregistered* exceptions as JSON:API rather than falling through to Rails.
|
|
67
|
+
|
|
68
|
+
A pretty boilerplate controller that just interfaces with the resource. `respond_with` comes from the [`responders`](https://github.com/heartcombo/responders) gem; with it in your Gemfile, add `include Graphiti::Rails::Responders` as well. Otherwise use `render jsonapi:` as the write actions below do.
|
|
69
|
+
|
|
59
70
|
```ruby
|
|
60
71
|
class EmployeesController < ApplicationController
|
|
61
72
|
def index
|
|
@@ -219,7 +230,7 @@ Request:
|
|
|
219
230
|
|
|
220
231
|
|
|
221
232
|
|
|
222
|
-
[Graphiti
|
|
233
|
+
[Graphiti Documentation](https://www.graphiti.dev/)
|
|
223
234
|
|
|
224
235
|
[Join the Discord](https://discord.gg/wgqkMBsSRV)
|
|
225
236
|
|
data/UPGRADING.md
CHANGED
|
@@ -1,61 +1,5 @@
|
|
|
1
1
|
# Upgrading Graphiti
|
|
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://www.graphiti.dev/upgrading**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Graphiti 2.0 requires Ruby 3.0+ and Rails 6.0+ (when using Rails).
|
|
8
|
-
|
|
9
|
-
### The model you inspect is the model that saves
|
|
10
|
-
|
|
11
|
-
Proxies returned by `build` and `find` now apply the request payload lazily, and expose the resulting model before anything is written:
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
# Creates
|
|
15
|
-
resource = MyResource.build(params)
|
|
16
|
-
resource.data # unsaved model, attributes applied
|
|
17
|
-
resource.data.valid? # inspect before committing to anything
|
|
18
|
-
resource.save # persists that same instance
|
|
19
|
-
|
|
20
|
-
# Updates
|
|
21
|
-
resource = MyResource.find(params)
|
|
22
|
-
resource.assign_attributes(params)
|
|
23
|
-
resource.data.changed # dirty tracking works
|
|
24
|
-
resource.update
|
|
25
|
-
|
|
26
|
-
# or assign and save in one call, Rails-style
|
|
27
|
-
resource = MyResource.find(params)
|
|
28
|
-
resource.update(params)
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
`assign_attributes` is idempotent per payload, validation still runs before assignment, and the instance you inspect is the instance that saves.
|
|
32
|
-
|
|
33
|
-
### Breaking: around_persistence receives the model, not the attributes hash
|
|
34
|
-
|
|
35
|
-
To make the above hold, attributes are assigned to the model once, before the persistence hooks fire. `around_persistence` now wraps the save of an already-assigned model and receives that model:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
# 1.x
|
|
39
|
-
def do_around_persistence(attributes)
|
|
40
|
-
attributes[:tenant_id] = current_tenant.id
|
|
41
|
-
model = yield
|
|
42
|
-
model.log_saved!
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# 2.0
|
|
46
|
-
def do_around_persistence(model)
|
|
47
|
-
model.tenant_id = current_tenant.id # last chance to touch the model before save, inside the transaction
|
|
48
|
-
saved = yield
|
|
49
|
-
saved.log_saved!
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
To migrate, move attribute-hash modifications to `before_attributes` (which still receives the mutable hash, before assignment), or set the value on the model as above. Hooks that only wrap their yield - transactions, timing, post-save side effects - need no changes. Graphiti 1.x releases warn at runtime when a hook would be affected.
|
|
54
|
-
|
|
55
|
-
`before/around/after_attributes` and `before/around/after_save` are unchanged.
|
|
56
|
-
|
|
57
|
-
### Fine print
|
|
58
|
-
|
|
59
|
-
- If you inspect the model before saving, the attributes callbacks run at inspection time (in your controller, outside the save transaction). On the plain `save` path they run inside the transaction, at the same point as 1.x.
|
|
60
|
-
- Writable guards judge persisted state: a guard asking for the model gets a fresh build/find, never the current request's unsaved changes. A payload cannot influence its own authorization.
|
|
61
|
-
- Sideposted child models are still built and assigned during save; `data` exposes the pre-assigned root model only.
|
|
5
|
+
In this repository it is [docs/upgrading/](docs/upgrading/index.md).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Backends and Models'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Backends and Models
|
|
6
|
+
|
|
7
|
+
A Resource queries a **Backend** and returns **Models** from what comes back. Graphiti serializes the Models.
|
|
8
|
+
|
|
9
|
+
With ActiveRecord those are the same object. `Employee` is both the thing you query and the thing you render, and you can skip most of this page. It matters when they're separate: a search index, an HTTP service, a document store. Then the Backend is whatever you query, and the Model is whatever you hand back.
|
|
10
|
+
|
|
11
|
+
## Scopes {#scopes}
|
|
12
|
+
|
|
13
|
+
A **scope** is whatever your backend needs to run a query. Graphiti doesn't care what it is. For ActiveRecord it's an `ActiveRecord::Relation`. Here it's a plain hash:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
class EmployeeResource < ApplicationResource
|
|
17
|
+
self.adapter = Graphiti::Adapters::Null
|
|
18
|
+
|
|
19
|
+
attribute :name, :string
|
|
20
|
+
|
|
21
|
+
def base_scope
|
|
22
|
+
{ conditions: {}, sort: {} }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
filter :name do
|
|
26
|
+
eq do |scope, value|
|
|
27
|
+
scope[:conditions].merge!(value)
|
|
28
|
+
scope
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sort :name do |scope, direction|
|
|
33
|
+
scope[:sort] = { name: direction }
|
|
34
|
+
scope
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolve(scope)
|
|
38
|
+
results = Backend.query(scope)
|
|
39
|
+
results.map { |result| Employee.new(result) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`base_scope` is the starting point, each `filter` and `sort` block mutates it based on request params, and `resolve` runs the query and returns Models.
|
|
45
|
+
|
|
46
|
+
**Every block must return the scope.** Returning the result of `merge!` or an assignment instead of the scope itself is the most common way to break this.
|
|
47
|
+
|
|
48
|
+
Writing that per Resource gets old. Once the pattern stabilizes, move it into an [Adapter](/topics/without-activerecord#adapters) and Resources go back to being declarative:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class EmployeeResource < ApplicationResource
|
|
52
|
+
self.adapter = BackendAdapter
|
|
53
|
+
attribute :name, :string
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What a Model has to do {#model-requirements}
|
|
58
|
+
|
|
59
|
+
**Respond to `id`, uniquely.** Graphiti uses `model.id` to tell records apart when rendering. Duplicate ids produce wrong output, not an error.
|
|
60
|
+
|
|
61
|
+
If the underlying record has no id, generate one:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
def id
|
|
65
|
+
@id ||= SecureRandom.uuid
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Respond to its readable attributes.** `attribute :name, :string` calls `model.name`. If your Model doesn't have that method, pass a block instead:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
attribute :name, :string do
|
|
73
|
+
@object.full_name
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Include `ActiveModel::Validations` if you want validation errors.** Graphiti checks models on write requests and renders a [JSON:API errors payload](http://jsonapi.org/format/#errors) from `model.errors`. Without it, an invalid model saves silently:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class Employee
|
|
81
|
+
include ActiveModel::Validations
|
|
82
|
+
|
|
83
|
+
validates :name, presence: true
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Writing a Model {#model-implementations}
|
|
88
|
+
|
|
89
|
+
Graphiti has no opinion here. A plain class works:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
class Employee
|
|
93
|
+
attr_accessor :id, :first_name, :last_name, :age
|
|
94
|
+
|
|
95
|
+
def initialize(attrs = {})
|
|
96
|
+
attrs.each_pair { |key, value| send(:"#{key}=", value) }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
[ActiveModel::Model](https://api.rubyonrails.org/classes/ActiveModel/Model.html) gives you the constructor and validations for free:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class Employee
|
|
105
|
+
include ActiveModel::Model
|
|
106
|
+
|
|
107
|
+
attr_accessor :id, :first_name, :last_name, :age
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
[Dry::Struct](https://dry-rb.org/gems/dry-struct) adds type enforcement, and dry-types is already a Graphiti dependency:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
class Employee < Dry::Struct
|
|
115
|
+
attribute :id, Types::Integer
|
|
116
|
+
attribute :first_name, Types::String
|
|
117
|
+
attribute :last_name, Types::String
|
|
118
|
+
attribute :age, Types::Integer
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`OpenStruct` also works and is what Graphiti uses internally for remote resources, but it fails quietly in ways the others don't. See [OpenStruct Models](/topics/openstruct-models) before reaching for it.
|