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.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -86
  3. data/.github/workflows/docs.yml +60 -0
  4. data/.github/workflows/release.yml +8 -8
  5. data/.gitignore +7 -0
  6. data/.npmrc +9 -0
  7. data/.standard.yml +4 -1
  8. data/Appraisals +33 -32
  9. data/CHANGELOG.md +34 -0
  10. data/README.md +13 -2
  11. data/UPGRADING.md +2 -58
  12. data/docs/concepts/backends-and-models.md +122 -0
  13. data/docs/concepts/endpoints.md +183 -0
  14. data/docs/concepts/links.md +212 -0
  15. data/docs/concepts/overview.md +80 -0
  16. data/docs/concepts/persisting.md +376 -0
  17. data/docs/concepts/relationships.md +527 -0
  18. data/docs/concepts/resources.md +677 -0
  19. data/docs/getting-started/first-api.md +289 -0
  20. data/docs/getting-started/installation.md +185 -0
  21. data/docs/intro.md +307 -0
  22. data/docs/js/authentication.md +63 -0
  23. data/docs/js/ddau.md +20 -0
  24. data/docs/js/extra-params.md +41 -0
  25. data/docs/js/index.md +112 -0
  26. data/docs/js/installation.md +120 -0
  27. data/docs/js/middleware.md +72 -0
  28. data/docs/js/models.md +202 -0
  29. data/docs/js/reads.md +494 -0
  30. data/docs/js/state-syncing.md +100 -0
  31. data/docs/js/writes.md +373 -0
  32. data/docs/reference/vandal.md +63 -0
  33. data/docs/reference/why.md +13 -0
  34. data/docs/topics/authorization.md +155 -0
  35. data/docs/topics/caching.md +55 -0
  36. data/docs/topics/customizing-sideloads.md +156 -0
  37. data/docs/topics/debugging.md +216 -0
  38. data/docs/topics/error-handling.md +210 -0
  39. data/docs/topics/etags.md +46 -0
  40. data/docs/topics/hopping-relationships.md +149 -0
  41. data/docs/topics/json-attributes.md +77 -0
  42. data/docs/topics/openstruct-models.md +50 -0
  43. data/docs/topics/remote-resources.md +291 -0
  44. data/docs/topics/testing.md +894 -0
  45. data/docs/topics/without-activerecord.md +324 -0
  46. data/docs/tutorial/index.md +58 -0
  47. data/docs/tutorial/step_0.md +107 -0
  48. data/docs/tutorial/step_1.md +199 -0
  49. data/docs/tutorial/step_2.md +312 -0
  50. data/docs/tutorial/step_3.md +142 -0
  51. data/docs/tutorial/step_4.md +135 -0
  52. data/docs/tutorial/step_5.md +69 -0
  53. data/docs/tutorial/step_6.md +82 -0
  54. data/docs/tutorial/step_7.md +205 -0
  55. data/docs/tutorial/step_8.md +128 -0
  56. data/docs/tutorial/step_9.md +171 -0
  57. data/docs/upgrading.md +265 -0
  58. data/gemfiles/rails_7_1.gemfile +4 -3
  59. data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
  60. data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
  61. data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
  62. data/graphiti.gemspec +7 -5
  63. data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
  64. data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
  65. data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
  66. data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
  67. data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
  68. data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
  69. data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
  70. data/lib/graphiti/configuration.rb +2 -2
  71. data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
  72. data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
  73. data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
  74. data/lib/graphiti/error_serializers/validation.rb +143 -0
  75. data/lib/graphiti/errors.rb +4 -4
  76. data/lib/graphiti/query.rb +1 -1
  77. data/lib/graphiti/rails/context.rb +33 -0
  78. data/lib/graphiti/rails/controller.rb +41 -0
  79. data/lib/graphiti/rails/debugging.rb +18 -0
  80. data/lib/graphiti/rails/exception_handlers.rb +77 -0
  81. data/lib/graphiti/rails/railtie.rb +139 -0
  82. data/lib/graphiti/rails/responders.rb +21 -0
  83. data/lib/graphiti/rails/test_helpers.rb +22 -0
  84. data/lib/graphiti/rails.rb +47 -29
  85. data/lib/graphiti/resource/configuration.rb +1 -0
  86. data/lib/graphiti/resource/interface.rb +2 -2
  87. data/lib/graphiti/resource/remote.rb +2 -2
  88. data/lib/graphiti/resource/sideloading.rb +1 -1
  89. data/lib/graphiti/resource.rb +13 -1
  90. data/lib/graphiti/responders.rb +7 -20
  91. data/lib/graphiti/schema.rb +5 -1
  92. data/lib/graphiti/schema_diff.rb +4 -0
  93. data/lib/graphiti/scope.rb +45 -37
  94. data/lib/graphiti/serializer.rb +6 -0
  95. data/lib/graphiti/sideload/belongs_to.rb +38 -5
  96. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
  97. data/lib/graphiti/sideload.rb +54 -35
  98. data/lib/graphiti/spec_helpers/errors.rb +73 -0
  99. data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
  100. data/lib/graphiti/spec_helpers/helpers.rb +107 -0
  101. data/lib/graphiti/spec_helpers/node.rb +88 -0
  102. data/lib/graphiti/spec_helpers/rspec.rb +147 -0
  103. data/lib/graphiti/spec_helpers.rb +53 -0
  104. data/lib/graphiti/util/include_params.rb +2 -2
  105. data/lib/graphiti/util/serializer_relationships.rb +41 -5
  106. data/lib/graphiti/version.rb +1 -1
  107. data/lib/graphiti-rails.rb +11 -0
  108. data/lib/graphiti.rb +34 -10
  109. data/lib/graphiti_errors.rb +11 -0
  110. data/lib/graphiti_spec_helpers/rspec.rb +3 -0
  111. data/lib/graphiti_spec_helpers.rb +11 -0
  112. data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
  113. data/package-lock.json +6199 -0
  114. data/package.json +5 -4
  115. data/website/.gitignore +20 -0
  116. data/website/README.md +43 -0
  117. data/website/docusaurus.config.js +141 -0
  118. data/website/package-lock.json +19474 -0
  119. data/website/package.json +46 -0
  120. data/website/sidebars.js +82 -0
  121. data/website/src/css/custom.css +58 -0
  122. data/website/src/pages/markdown-page.mdx +7 -0
  123. data/website/static/.nojekyll +0 -0
  124. data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
  125. data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
  126. data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
  127. data/website/static/1.13/2019/10/14/tutorial.html +198 -0
  128. data/website/static/1.13/CNAME +1 -0
  129. data/website/static/1.13/README.md +16 -0
  130. data/website/static/1.13/assets/css/syntax.css +60 -0
  131. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  132. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  133. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  134. data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
  135. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  136. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  137. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  138. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  139. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
  140. data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
  141. data/website/static/1.13/assets/img/backend.gif +0 -0
  142. data/website/static/1.13/assets/img/conformity.png +0 -0
  143. data/website/static/1.13/assets/img/error_payload.png +0 -0
  144. data/website/static/1.13/assets/img/gh.png +0 -0
  145. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  146. data/website/static/1.13/assets/img/logo-500.png +0 -0
  147. data/website/static/1.13/assets/img/logo.png +0 -0
  148. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  149. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  150. data/website/static/1.13/assets/img/persist.jpg +0 -0
  151. data/website/static/1.13/assets/img/resource.gif +0 -0
  152. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  153. data/website/static/1.13/assets/img/rest1.gif +0 -0
  154. data/website/static/1.13/assets/img/rest2.gif +0 -0
  155. data/website/static/1.13/assets/img/rest3.gif +0 -0
  156. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  157. data/website/static/1.13/assets/img/why.png +0 -0
  158. data/website/static/1.13/assets/js/highlight.pack.js +2 -0
  159. data/website/static/1.13/assets/main.css +15518 -0
  160. data/website/static/1.13/assets/main.css.map +1 -0
  161. data/website/static/1.13/bin/bundle +109 -0
  162. data/website/static/1.13/bin/jekyll +27 -0
  163. data/website/static/1.13/bin/kramdown +27 -0
  164. data/website/static/1.13/bin/listen +27 -0
  165. data/website/static/1.13/bin/rake +27 -0
  166. data/website/static/1.13/bin/rougify +27 -0
  167. data/website/static/1.13/bin/safe_yaml +27 -0
  168. data/website/static/1.13/bin/sass +27 -0
  169. data/website/static/1.13/bin/sass-convert +27 -0
  170. data/website/static/1.13/bin/scss +27 -0
  171. data/website/static/1.13/blog.html +259 -0
  172. data/website/static/1.13/cheatsheet.html +316 -0
  173. data/website/static/1.13/cookbooks/authorization.md +0 -0
  174. data/website/static/1.13/cookbooks/caching.md +0 -0
  175. data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
  176. data/website/static/1.13/cookbooks/etags.md +0 -0
  177. data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
  178. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  179. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  180. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  181. data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
  182. data/website/static/1.13/features.html +249 -0
  183. data/website/static/1.13/feed.xml +106 -0
  184. data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
  185. data/website/static/1.13/guides/concepts/debugging.html +440 -0
  186. data/website/static/1.13/guides/concepts/endpoints.html +432 -0
  187. data/website/static/1.13/guides/concepts/error-handling.html +396 -0
  188. data/website/static/1.13/guides/concepts/links.html +501 -0
  189. data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
  190. data/website/static/1.13/guides/concepts/resources.html +2176 -0
  191. data/website/static/1.13/guides/concepts/testing.html +1469 -0
  192. data/website/static/1.13/guides/getting-started/installation.html +420 -0
  193. data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
  194. data/website/static/1.13/guides/index.html +269 -0
  195. data/website/static/1.13/guides/overview.html +325 -0
  196. data/website/static/1.13/guides/upgrading-2-0.html +193 -0
  197. data/website/static/1.13/guides/upgrading.html +314 -0
  198. data/website/static/1.13/guides/vandal.html +282 -0
  199. data/website/static/1.13/guides/why.html +1121 -0
  200. data/website/static/1.13/index.html +72 -0
  201. data/website/static/1.13/js/authentication.html +295 -0
  202. data/website/static/1.13/js/ddau.html +238 -0
  203. data/website/static/1.13/js/extra-params.html +270 -0
  204. data/website/static/1.13/js/index.html +321 -0
  205. data/website/static/1.13/js/installation.html +637 -0
  206. data/website/static/1.13/js/introduction.html +257 -0
  207. data/website/static/1.13/js/middleware.html +318 -0
  208. data/website/static/1.13/js/reads/fieldsets.html +271 -0
  209. data/website/static/1.13/js/reads/filtering.html +289 -0
  210. data/website/static/1.13/js/reads/includes.html +260 -0
  211. data/website/static/1.13/js/reads/index.html +497 -0
  212. data/website/static/1.13/js/reads/nested-queries.html +353 -0
  213. data/website/static/1.13/js/reads/pagination.html +260 -0
  214. data/website/static/1.13/js/reads/sorting.html +265 -0
  215. data/website/static/1.13/js/reads/statistics.html +289 -0
  216. data/website/static/1.13/js/state-syncing.html +340 -0
  217. data/website/static/1.13/js/writes/deferred.html +296 -0
  218. data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
  219. data/website/static/1.13/js/writes/index.html +391 -0
  220. data/website/static/1.13/js/writes/nested.html +330 -0
  221. data/website/static/1.13/js/writes/validations.html +272 -0
  222. data/website/static/1.13/quickstart.html +660 -0
  223. data/website/static/1.13/template +161 -0
  224. data/website/static/1.13/tutorial/index.html +250 -0
  225. data/website/static/1.13/tutorial/step_0.html +292 -0
  226. data/website/static/1.13/tutorial/step_1.html +517 -0
  227. data/website/static/1.13/tutorial/step_2.html +481 -0
  228. data/website/static/1.13/tutorial/step_3.html +323 -0
  229. data/website/static/1.13/tutorial/step_4.html +318 -0
  230. data/website/static/1.13/tutorial/step_5.html +265 -0
  231. data/website/static/1.13/tutorial/step_6.html +276 -0
  232. data/website/static/1.13/tutorial/step_7.html +390 -0
  233. data/website/static/1.13/tutorial/step_8.html +316 -0
  234. data/website/static/1.13/tutorial/step_9.html +365 -0
  235. data/website/static/assets/img/error_payload.png +0 -0
  236. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  237. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  239. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  240. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  242. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  243. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  244. data/website/static/assets/img/meta_total_count.png +0 -0
  245. data/website/static/img/docusaurus-social-card.jpg +0 -0
  246. data/website/static/img/docusaurus.png +0 -0
  247. data/website/static/img/favicon.ico +0 -0
  248. data/website/static/img/logo.png +0 -0
  249. data/website/static/img/logo.svg +1 -0
  250. data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
  251. data/website/static/img/undraw_docusaurus_react.svg +170 -0
  252. data/website/static/img/undraw_docusaurus_tree.svg +40 -0
  253. metadata +245 -46
  254. data/gemfiles/rails_6.gemfile +0 -18
  255. data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
  256. data/gemfiles/rails_7.gemfile +0 -18
  257. data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
  258. data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
  259. data/lib/graphiti/railtie.rb +0 -121
  260. /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
  261. /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
  262. /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
  263. /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
  264. /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
  265. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
  266. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
  267. /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: 69a8294c01d96b3ca136060036e2b8592f46464a775965f6f2e42129915854bd
4
- data.tar.gz: e133c0cf444718997a718e16a957b6e11d86aec78696ac2cd4bb4fe8e3b907eb
3
+ metadata.gz: e8f1b385d0a7623788b2a492d98d2175d1964d320ea48f988f73bffb30ab9439
4
+ data.tar.gz: a95f11de0a07753144d5c4d057ae0b4bb90e2f61c5906996bd29f7fa09998657
5
5
  SHA512:
6
- metadata.gz: fb38ebe38f207b016b61a960f0b39890fe4cb3aa8e8bc70a50f5f82894513c9360fe37ce02de50a7de14718d5a0f5865a50d6defe1ce15e2730409bc3324401c
7
- data.tar.gz: cc5090ad354c15c4f7d5a2c9daa8aab78be9bc7b4f41c62569a5fa5edf7496dd79e2b0d86533027fd7989ed1f7295950fc325a1cf9475ae03992ddd6344a2e27
6
+ metadata.gz: 7f00afaae7e44289e89d5d29e56e3539953e2c13befcc6c2d5dbb95c9b8904ca6103c3651ebabb6227f9e43f50f22ee4b845d1d9235244e363a1c08089363c47
7
+ data.tar.gz: a6f82ed9556a22d2ddd76710877af8f39cfca470cad5a12a05d99ef5d54fc6d03a5c0e549026307cd71b6a6323c51e4889164acd94a38a0d371e0c74fe54760a
@@ -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.0"
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 }} | Appraisal: ${{ matrix.appraisal }}"
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
- - ruby: ruby-head
52
- gemfile: gemfiles/rails_7_1.gemfile
53
- appraisal: true
54
- - ruby: ruby-head
55
- gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
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 8.1 needs ruby 3.2 +
88
- - gemfile: gemfiles/rails_8_1_graphiti_rails.gemfile
89
- ruby: 3.0
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 8 needs ruby 3.2 +
94
- - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
95
- ruby: 3.0
96
- - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
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 7.2 needs ruby 3.1 +
100
- - gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
101
- ruby: 3.0
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 7.1 needs ruby 3.0 +
104
- - gemfile: gemfiles/rails_7_1_graphiti_rails.gemfile
105
- ruby: 3.0
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@v3
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.1"
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@v2
25
+ uses: actions/setup-node@v4
28
26
  with:
29
- node-version: 14
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: yarn install --frozen-lockfile
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: node_modules/.bin/semantic-release
36
+ run: npx semantic-release
data/.gitignore CHANGED
@@ -15,3 +15,10 @@ spec/.rspec-examples
15
15
  spec/dummy/log/*
16
16
  *~
17
17
  .vscode/
18
+
19
+ # Docusaurus
20
+ website/build/
21
+ website/.docusaurus/
22
+
23
+ .DS_Store
24
+ .idea/
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.0
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
- appraise "rails-6" do
2
- gem "rails", "~> 6.0"
3
- gem "rspec-rails"
4
- gem "sqlite3", "~> 1.4.0"
5
- gem "database_cleaner"
6
- end
7
-
8
- appraise "rails-6-graphiti-rails" do
9
- gem "rails", "~> 6.0"
10
- gem "rspec-rails"
11
- gem "sqlite3", "~> 1.4.0"
12
- gem "database_cleaner"
13
- gem "graphiti-rails", "~> 0.4.0"
14
- end
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 "sqlite3", "~> 1.4.0"
20
- gem "database_cleaner"
23
+ gem "responders"
24
+ gem "sqlite3", "~> 1.4"
21
25
  end
22
26
 
23
- appraise "rails-7-graphiti-rails" do
24
- gem "rails", "~> 7.0"
27
+ appraise "rails-7-2" do
28
+ gem "rails", "~> 7.2.0"
25
29
  gem "rspec-rails"
26
- gem "sqlite3", "~> 1.4.0"
27
- gem "database_cleaner"
28
- gem "graphiti-rails", "~> 0.4.0"
30
+ gem "responders"
31
+ gem "sqlite3", "~> 2.1"
29
32
  end
30
33
 
31
- appraise "rails-7-1" do
32
- gem "rails", "~> 7.1"
34
+ appraise "rails-8-0" do
35
+ gem "rails", "~> 8.0.0"
33
36
  gem "rspec-rails"
34
- # This appraisal is the ruby-head job; sqlite3 1.4.x cannot build on Ruby 4.x
37
+ gem "responders"
35
38
  gem "sqlite3", "~> 2.1"
36
- gem "database_cleaner"
37
39
  end
38
40
 
39
- appraise "rails-7-1-graphiti-rails" do
40
- gem "rails", "~> 7.1"
41
+ appraise "rails-8-1" do
42
+ gem "rails", "~> 8.1.0"
41
43
  gem "rspec-rails"
42
- gem "sqlite3", "~> 1.4.0"
43
- gem "database_cleaner"
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
- A pretty boilerplate controller that just interfaces with the resource
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 Guides](https://www.graphiti.dev/guides/)
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
- ## 1.x to 2.0
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
- ### Requirements
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.