graphiti 2.0.0.beta.7 → 2.0.0.beta.9

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +56 -4
  4. data/.github/workflows/docs.yml +10 -7
  5. data/.github/workflows/notify-example-app.yml +18 -0
  6. data/.github/workflows/release.yml +1 -1
  7. data/.gitignore +2 -0
  8. data/.standard.yml +6 -0
  9. data/CHANGELOG.md +66 -0
  10. data/CONTRIBUTING.md +3 -1
  11. data/README.md +13 -206
  12. data/Rakefile +34 -3
  13. data/UPGRADING.md +2 -2
  14. data/graphiti.gemspec +8 -4
  15. data/lib/generators/graphiti/generator_mixin.rb +1 -1
  16. data/lib/generators/graphiti/install_generator.rb +1 -1
  17. data/lib/generators/graphiti/resource_generator.rb +6 -3
  18. data/lib/generators/graphiti/templates/application_resource.rb.erb +19 -0
  19. data/lib/generators/graphiti/templates/controller.rb.erb +26 -8
  20. data/lib/graphiti/audit/report.rb +6 -6
  21. data/lib/graphiti/audit.rb +3 -4
  22. data/lib/graphiti/configuration.rb +11 -4
  23. data/lib/graphiti/debugger.rb +18 -6
  24. data/lib/graphiti/delegates/pagination.rb +2 -2
  25. data/lib/graphiti/deserializer.rb +3 -3
  26. data/lib/graphiti/error_serializers/deprecated_constants.rb +23 -0
  27. data/lib/graphiti/error_serializers/invalid_request.rb +6 -0
  28. data/lib/graphiti/errors.rb +22 -6
  29. data/lib/graphiti/extensions/temp_id.rb +1 -1
  30. data/lib/graphiti/query.rb +5 -4
  31. data/lib/graphiti/rails/context.rb +2 -1
  32. data/lib/graphiti/rails/controller.rb +3 -0
  33. data/lib/graphiti/rails/debugging.rb +1 -1
  34. data/lib/graphiti/rails/responders.rb +2 -1
  35. data/lib/graphiti/rails.rb +14 -0
  36. data/lib/graphiti/railtie.rb +8 -0
  37. data/lib/graphiti/resource/configuration.rb +1 -0
  38. data/lib/graphiti/resource/dsl.rb +2 -2
  39. data/lib/graphiti/resource/links.rb +1 -1
  40. data/lib/graphiti/resource/persistence.rb +1 -1
  41. data/lib/graphiti/resource/polymorphism.rb +1 -1
  42. data/lib/graphiti/resource.rb +2 -2
  43. data/lib/graphiti/resource_proxy.rb +0 -1
  44. data/lib/graphiti/scope.rb +68 -21
  45. data/lib/graphiti/scoping/filter.rb +5 -3
  46. data/lib/graphiti/scoping/paginate.rb +1 -1
  47. data/lib/graphiti/scoping/sort.rb +1 -1
  48. data/lib/graphiti/sideload/has_many.rb +1 -1
  49. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +11 -4
  50. data/lib/graphiti/sideload.rb +8 -6
  51. data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
  52. data/lib/graphiti/spec_helpers/matchers.rb +1 -1
  53. data/lib/graphiti/types.rb +5 -1
  54. data/lib/graphiti/util/cache_debug.rb +3 -3
  55. data/lib/graphiti/util/hash.rb +1 -1
  56. data/lib/graphiti/util/persistence.rb +6 -6
  57. data/lib/graphiti/util/serializer_relationships.rb +11 -2
  58. data/lib/graphiti/version.rb +1 -1
  59. data/lib/graphiti.rb +1 -1
  60. data/lib/tasks/graphiti.rake +1 -1
  61. data/package-lock.json +114 -0
  62. data/package.json +16 -3
  63. metadata +33 -198
  64. data/docs/concepts/backends-and-models.md +0 -122
  65. data/docs/concepts/endpoints.md +0 -183
  66. data/docs/concepts/links.md +0 -212
  67. data/docs/concepts/overview.md +0 -80
  68. data/docs/concepts/persisting.md +0 -376
  69. data/docs/concepts/relationships.md +0 -627
  70. data/docs/concepts/resources.md +0 -677
  71. data/docs/getting-started/first-api.md +0 -289
  72. data/docs/getting-started/installation.md +0 -185
  73. data/docs/intro.md +0 -307
  74. data/docs/js/authentication.md +0 -63
  75. data/docs/js/ddau.md +0 -20
  76. data/docs/js/extra-params.md +0 -41
  77. data/docs/js/index.md +0 -112
  78. data/docs/js/installation.md +0 -120
  79. data/docs/js/middleware.md +0 -72
  80. data/docs/js/models.md +0 -202
  81. data/docs/js/reads.md +0 -494
  82. data/docs/js/state-syncing.md +0 -100
  83. data/docs/js/writes.md +0 -373
  84. data/docs/reference/vandal.md +0 -63
  85. data/docs/reference/why.md +0 -13
  86. data/docs/topics/authorization.md +0 -155
  87. data/docs/topics/caching.md +0 -55
  88. data/docs/topics/customizing-sideloads.md +0 -156
  89. data/docs/topics/debugging.md +0 -242
  90. data/docs/topics/error-handling.md +0 -210
  91. data/docs/topics/etags.md +0 -46
  92. data/docs/topics/hopping-relationships.md +0 -149
  93. data/docs/topics/json-attributes.md +0 -77
  94. data/docs/topics/openstruct-models.md +0 -50
  95. data/docs/topics/remote-resources.md +0 -291
  96. data/docs/topics/testing.md +0 -916
  97. data/docs/topics/without-activerecord.md +0 -324
  98. data/docs/tutorial/index.md +0 -58
  99. data/docs/tutorial/step_0.md +0 -107
  100. data/docs/tutorial/step_1.md +0 -199
  101. data/docs/tutorial/step_2.md +0 -312
  102. data/docs/tutorial/step_3.md +0 -142
  103. data/docs/tutorial/step_4.md +0 -135
  104. data/docs/tutorial/step_5.md +0 -69
  105. data/docs/tutorial/step_6.md +0 -82
  106. data/docs/tutorial/step_7.md +0 -205
  107. data/docs/tutorial/step_8.md +0 -128
  108. data/docs/tutorial/step_9.md +0 -171
  109. data/docs/upgrading.md +0 -267
  110. data/website/.gitignore +0 -20
  111. data/website/README.md +0 -43
  112. data/website/docusaurus.config.js +0 -197
  113. data/website/package-lock.json +0 -19474
  114. data/website/package.json +0 -46
  115. data/website/sidebars.js +0 -82
  116. data/website/src/css/custom.css +0 -85
  117. data/website/src/pages/markdown-page.mdx +0 -7
  118. data/website/static/.nojekyll +0 -0
  119. data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
  120. data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
  121. data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
  122. data/website/static/1.13/2019/10/14/tutorial.html +0 -198
  123. data/website/static/1.13/CNAME +0 -1
  124. data/website/static/1.13/README.md +0 -16
  125. data/website/static/1.13/assets/css/syntax.css +0 -60
  126. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  127. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  128. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  129. data/website/static/1.13/assets/favicons/browserconfig.xml +0 -9
  130. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  131. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  132. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  133. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  134. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +0 -1
  135. data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
  136. data/website/static/1.13/assets/img/backend.gif +0 -0
  137. data/website/static/1.13/assets/img/conformity.png +0 -0
  138. data/website/static/1.13/assets/img/error_payload.png +0 -0
  139. data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
  140. data/website/static/1.13/assets/img/gh.png +0 -0
  141. data/website/static/1.13/assets/img/home-bg.jpg +0 -0
  142. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  143. data/website/static/1.13/assets/img/logo-500.png +0 -0
  144. data/website/static/1.13/assets/img/logo.png +0 -0
  145. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  146. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  147. data/website/static/1.13/assets/img/persist.jpg +0 -0
  148. data/website/static/1.13/assets/img/resource.gif +0 -0
  149. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  150. data/website/static/1.13/assets/img/rest1.gif +0 -0
  151. data/website/static/1.13/assets/img/rest2.gif +0 -0
  152. data/website/static/1.13/assets/img/rest3.gif +0 -0
  153. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  154. data/website/static/1.13/assets/img/sunrise.png +0 -0
  155. data/website/static/1.13/assets/img/why.png +0 -0
  156. data/website/static/1.13/assets/js/highlight.pack.js +0 -2
  157. data/website/static/1.13/assets/main.css +0 -15518
  158. data/website/static/1.13/assets/main.css.map +0 -1
  159. data/website/static/1.13/bin/bundle +0 -109
  160. data/website/static/1.13/bin/jekyll +0 -27
  161. data/website/static/1.13/bin/kramdown +0 -27
  162. data/website/static/1.13/bin/listen +0 -27
  163. data/website/static/1.13/bin/rake +0 -27
  164. data/website/static/1.13/bin/rougify +0 -27
  165. data/website/static/1.13/bin/safe_yaml +0 -27
  166. data/website/static/1.13/bin/sass +0 -27
  167. data/website/static/1.13/bin/sass-convert +0 -27
  168. data/website/static/1.13/bin/scss +0 -27
  169. data/website/static/1.13/blog.html +0 -259
  170. data/website/static/1.13/cheatsheet.html +0 -316
  171. data/website/static/1.13/cookbooks/authorization.md +0 -0
  172. data/website/static/1.13/cookbooks/caching.md +0 -0
  173. data/website/static/1.13/cookbooks/customizing-sideloads.html +0 -325
  174. data/website/static/1.13/cookbooks/etags.md +0 -0
  175. data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
  176. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  177. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  178. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  179. data/website/static/1.13/cookbooks/without-activerecord.html +0 -510
  180. data/website/static/1.13/features.html +0 -249
  181. data/website/static/1.13/feed.xml +0 -106
  182. data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
  183. data/website/static/1.13/guides/concepts/debugging.html +0 -440
  184. data/website/static/1.13/guides/concepts/endpoints.html +0 -432
  185. data/website/static/1.13/guides/concepts/error-handling.html +0 -396
  186. data/website/static/1.13/guides/concepts/links.html +0 -501
  187. data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
  188. data/website/static/1.13/guides/concepts/resources.html +0 -2176
  189. data/website/static/1.13/guides/concepts/testing.html +0 -1469
  190. data/website/static/1.13/guides/getting-started/installation.html +0 -420
  191. data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
  192. data/website/static/1.13/guides/index.html +0 -269
  193. data/website/static/1.13/guides/overview.html +0 -325
  194. data/website/static/1.13/guides/upgrading-2-0.html +0 -193
  195. data/website/static/1.13/guides/upgrading.html +0 -314
  196. data/website/static/1.13/guides/vandal.html +0 -282
  197. data/website/static/1.13/guides/why.html +0 -1121
  198. data/website/static/1.13/index.html +0 -72
  199. data/website/static/1.13/js/authentication.html +0 -295
  200. data/website/static/1.13/js/ddau.html +0 -238
  201. data/website/static/1.13/js/extra-params.html +0 -270
  202. data/website/static/1.13/js/index.html +0 -321
  203. data/website/static/1.13/js/installation.html +0 -637
  204. data/website/static/1.13/js/introduction.html +0 -257
  205. data/website/static/1.13/js/middleware.html +0 -318
  206. data/website/static/1.13/js/reads/fieldsets.html +0 -271
  207. data/website/static/1.13/js/reads/filtering.html +0 -289
  208. data/website/static/1.13/js/reads/includes.html +0 -260
  209. data/website/static/1.13/js/reads/index.html +0 -497
  210. data/website/static/1.13/js/reads/nested-queries.html +0 -353
  211. data/website/static/1.13/js/reads/pagination.html +0 -260
  212. data/website/static/1.13/js/reads/sorting.html +0 -265
  213. data/website/static/1.13/js/reads/statistics.html +0 -289
  214. data/website/static/1.13/js/state-syncing.html +0 -340
  215. data/website/static/1.13/js/writes/deferred.html +0 -296
  216. data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
  217. data/website/static/1.13/js/writes/index.html +0 -391
  218. data/website/static/1.13/js/writes/nested.html +0 -330
  219. data/website/static/1.13/js/writes/validations.html +0 -272
  220. data/website/static/1.13/quickstart.html +0 -660
  221. data/website/static/1.13/template +0 -161
  222. data/website/static/1.13/tutorial/index.html +0 -250
  223. data/website/static/1.13/tutorial/step_0.html +0 -292
  224. data/website/static/1.13/tutorial/step_1.html +0 -517
  225. data/website/static/1.13/tutorial/step_2.html +0 -481
  226. data/website/static/1.13/tutorial/step_3.html +0 -323
  227. data/website/static/1.13/tutorial/step_4.html +0 -318
  228. data/website/static/1.13/tutorial/step_5.html +0 -265
  229. data/website/static/1.13/tutorial/step_6.html +0 -276
  230. data/website/static/1.13/tutorial/step_7.html +0 -390
  231. data/website/static/1.13/tutorial/step_8.html +0 -316
  232. data/website/static/1.13/tutorial/step_9.html +0 -365
  233. data/website/static/CNAME +0 -1
  234. data/website/static/assets/img/error_payload.png +0 -0
  235. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  236. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  237. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  239. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  240. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  242. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  243. data/website/static/assets/img/meta_total_count.png +0 -0
  244. data/website/static/img/docusaurus-social-card.jpg +0 -0
  245. data/website/static/img/docusaurus.png +0 -0
  246. data/website/static/img/favicon.ico +0 -0
  247. data/website/static/img/logo.png +0 -0
  248. data/website/static/img/logo.svg +0 -1
  249. data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
  250. data/website/static/img/undraw_docusaurus_react.svg +0 -170
  251. data/website/static/img/undraw_docusaurus_tree.svg +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acced292af02bc2826f89f6e1d9388bcc7fdedabada42e4cc65d1bfaf9f713f5
4
- data.tar.gz: 79f966dc31ccb9b3aee687e7dc31a92e943e17845b6017258e366bebee066446
3
+ metadata.gz: a61d00c6968d2dc24afb8986d7b3b6051394ea73ac5d5a8104f1296f650fef47
4
+ data.tar.gz: bf367c2e3ff33e4240a3f11e3fbae780c75b7f727863e61fc0c546ae9cdfb5fe
5
5
  SHA512:
6
- metadata.gz: 22584fd2a83e017219fb8202c965d3d295ac341fed01b2e543355c32722cae741866f3188970ee480018d6063c78b06281045f22934c42b9645de72c6cc9562f
7
- data.tar.gz: 502371f38ad71f5e901b57ba187be70bb4d7c85c94544324e825207bc03f120fa652d0859fe56744efbdbcf826376dad84a4917444c62a7e30193482bca47700
6
+ metadata.gz: 4bb22545b0a2b590fb07a237857407a5a50ac2423dd33a5621af164d93b4d36237f54339fb083895e6d349ae64da3cf9cc2e24c363f63478cea691622f130331
7
+ data.tar.gz: 9da999b97fa15b04ca97420d6a6942c5d32b70bd0e8f27e33e01b8861cdd0206342d6afd11db15eb0c1d445beda4ce1f6a5ff5cceffcb54d5917e3a185cdc16a
@@ -0,0 +1,2 @@
1
+ # standardrb reformat from the 1.4 -> 1.56 upgrade
2
+ 5fe4fe5188f75461be817528c8d911090ce94340
@@ -15,13 +15,17 @@ jobs:
15
15
  name: Lint
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
- - uses: actions/checkout@v3
18
+ - uses: actions/checkout@v4
19
19
  - uses: ruby/setup-ruby@v1
20
20
  with:
21
21
  ruby-version: "3.2"
22
22
  bundler-cache: true
23
23
  - name: Run standardrb
24
- run: bundle exec standardrb --no-fix --format progress
24
+ run: |
25
+ bundle exec standardrb --no-fix --format progress || {
26
+ echo "::error::Lint failed. Almost everything standard flags is auto-fixable — run 'bundle exec standardrb --fix' locally and push the result."
27
+ exit 1
28
+ }
25
29
  test:
26
30
  name: "Tests - ${{ matrix.ruby }} | ${{ matrix.gemfile }}"
27
31
  runs-on: ubuntu-latest
@@ -74,20 +78,68 @@ jobs:
74
78
  - name: Set up Appraisal
75
79
  if: matrix.appraisal
76
80
  run: echo "APPRAISAL_INITIALIZED=true" >> $GITHUB_ENV
77
- - uses: actions/checkout@v3
81
+ - uses: actions/checkout@v4
78
82
  - uses: ruby/setup-ruby@v1
79
83
  with:
80
84
  ruby-version: ${{ matrix.ruby }}
81
85
  bundler-cache: true
82
86
  - name: Run tests
83
87
  run: bundle exec rspec
88
+ example-app:
89
+ name: "Example app (employee_directory)"
90
+ runs-on: ubuntu-latest
91
+ # The example app lives in its own repo and can lag behind; it is an
92
+ # integration canary, not a merge gate.
93
+ continue-on-error: true
94
+ env:
95
+ GRAPHITI_PATH: ${{ github.workspace }}/graphiti
96
+ BUNDLE_FROZEN: "false" # the path override rewrites the checked-in lockfile
97
+ defaults:
98
+ run:
99
+ working-directory: employee_directory
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+ with:
103
+ path: graphiti
104
+ - uses: actions/checkout@v4
105
+ with:
106
+ repository: graphiti-api/employee_directory
107
+ path: employee_directory
108
+ - uses: ruby/setup-ruby@v1
109
+ with:
110
+ ruby-version: "3.3"
111
+ - name: Bundle against this graphiti
112
+ run: bundle install
113
+ - name: Run the example app suite
114
+ run: |
115
+ bundle exec rails db:test:prepare
116
+ bundle exec rspec
117
+ examples:
118
+ name: "Examples (plain ruby, sinatra)"
119
+ runs-on: ubuntu-latest
120
+ # In-tree examples run against the working copy via their path: Gemfiles.
121
+ steps:
122
+ - uses: actions/checkout@v4
123
+ - uses: ruby/setup-ruby@v1
124
+ with:
125
+ ruby-version: "3.3"
126
+ - name: Run the plain ruby example
127
+ working-directory: examples/plain_ruby
128
+ run: |
129
+ bundle install
130
+ bundle exec ruby index.rb
131
+ - name: Run the sinatra example
132
+ working-directory: examples/sinatra
133
+ run: |
134
+ bundle install
135
+ bundle exec ruby smoke.rb
84
136
  publish:
85
137
  name: Release
86
138
  runs-on: ubuntu-latest
87
139
  # Only main auto-releases. Beta releases are cut by manually running
88
140
  # "Generate New Release" with the beta branch selected.
89
141
  if: github.ref == 'refs/heads/main'
90
- needs: [test]
142
+ needs: [lint, test]
91
143
  steps:
92
144
  - name: Dispatch Release
93
145
  uses: benc-uk/workflow-dispatch@v1
@@ -1,20 +1,23 @@
1
1
  name: Docs
2
2
 
3
- # Publishes the documentation site from beta. graphiti.dev now points at this
4
- # repo via website/static/CNAME, so this deploy is the live site: the 1.x
5
- # Jekyll site is frozen under website/static/1.13 and ships with every build,
6
- # and graphiti-api.github.io no longer serves the domain.
3
+ # Publishes the documentation site from beta, and from main once 2.0 lands
4
+ # there. graphiti.dev now points at this repo via website/static/CNAME, so this
5
+ # deploy is the live site: the 1.x Jekyll site is frozen under
6
+ # website/static/1.13 and ships with every build, and graphiti-api.github.io no
7
+ # longer serves the domain.
7
8
  on:
8
9
  push:
9
- branches: [beta]
10
+ branches: [beta, main]
10
11
  paths:
11
12
  - 'docs/**'
12
13
  - 'website/**'
13
14
  - '.github/workflows/docs.yml'
14
15
  workflow_dispatch: {}
15
16
 
17
+ # One static group: deploys from different branches force-push the same
18
+ # gh-pages branch, so they must not interleave.
16
19
  concurrency:
17
- group: docs-${{ github.ref }}
20
+ group: docs
18
21
  cancel-in-progress: true
19
22
 
20
23
  permissions:
@@ -25,7 +28,7 @@ jobs:
25
28
  name: Build and publish
26
29
  runs-on: ubuntu-latest
27
30
  steps:
28
- - uses: actions/checkout@v3
31
+ - uses: actions/checkout@v4
29
32
  - uses: actions/setup-node@v4
30
33
  with:
31
34
  node-version: '20'
@@ -0,0 +1,18 @@
1
+ name: Notify example app
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ dispatch:
9
+ name: Dispatch employee_directory update
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Send repository_dispatch
13
+ run: |
14
+ gh api -X POST repos/graphiti-api/employee_directory/dispatches \
15
+ -f event_type=graphiti-release \
16
+ -f 'client_payload[version]=${{ github.event.release.tag_name }}'
17
+ env:
18
+ GH_TOKEN: ${{ secrets.EXAMPLE_APP_TOKEN }}
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Set up Ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: "3.4"
19
+ ruby-version: "4.0"
20
20
  - name: Build
21
21
  run: |
22
22
  gem install bundler
data/.gitignore CHANGED
@@ -22,3 +22,5 @@ website/.docusaurus/
22
22
 
23
23
  .DS_Store
24
24
  .idea/
25
+ examples/*/Gemfile.lock
26
+ examples/sinatra/*.sqlite3*
data/.standard.yml CHANGED
@@ -4,6 +4,12 @@ ruby_version: 3.2
4
4
 
5
5
  ignore:
6
6
  - 'vendor/**/*'
7
+ # The Rails app template's heredocs are written into the user's files, so
8
+ # their literal indentation is load-bearing and the <<~ autofix would
9
+ # change what gets generated.
10
+ - 'templates/**/*':
11
+ - Layout/HeredocIndentation
12
+ - Layout/ClosingHeredocIndentation
7
13
  # The docs site, including the frozen 1.x archive under static/. None of it
8
14
  # is ours to restyle, and some of it is generated.
9
15
  - 'website/**/*'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  graphiti changelog
2
2
 
3
+ # [2.0.0-beta.9](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2026-08-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * bridge the last 1.x names that died with a bare NameError ([34e1dbd](https://github.com/graphiti-api/graphiti/commit/34e1dbd3e34bb8e6be56292c51fcb32674f75995))
9
+ * build the entity map when the root query is, not on first use ([134d18b](https://github.com/graphiti-api/graphiti/commit/134d18b12b9ebb6903cca813209f60e5626b25a6))
10
+ * coerce debug flags from env strings ([46be6aa](https://github.com/graphiti-api/graphiti/commit/46be6aa05d4e2a9e5d6b2562f223b4b05582bb39))
11
+ * keep a customized sideload's instances out of entity dedup ([b1beee3](https://github.com/graphiti-api/graphiti/commit/b1beee3288fae2b20b009983aaa2f6c71189e2ca))
12
+ * raise a more helpful named error when belongs_to linkage reads an unselected foreign key ([f59ea0b](https://github.com/graphiti-api/graphiti/commit/f59ea0bda94b388b1c2c0ae0e50b258f8ce0eb11))
13
+ * resolve inline when already on a sideload pool thread ([11c9de7](https://github.com/graphiti-api/graphiti/commit/11c9de7fb4bfc2d83b02aaba3e58c1f94c84cec4))
14
+ * scope debugger chunks to the request rather than the class ([9a77896](https://github.com/graphiti-api/graphiti/commit/9a77896c6e56d23b89d0a2aef358d8e8e0c28c54))
15
+ * surface the first child error from concurrent polymorphic sideloads ([5a08c0e](https://github.com/graphiti-api/graphiti/commit/5a08c0edab2bc1ee0c87c6abbe1067297569411e))
16
+
17
+
18
+ ### Performance Improvements
19
+
20
+ * nest the entity map instead of keying it by an array ([20bf625](https://github.com/graphiti-api/graphiti/commit/20bf6259d0347b02568b7d47054bb799e69629c3))
21
+ * skip the promise machinery when nothing is sideloaded ([d879923](https://github.com/graphiti-api/graphiti/commit/d879923070a35d1a0e908b2d04746aa81effb262))
22
+ * skip zipping a lone sideload promise ([7ef2cea](https://github.com/graphiti-api/graphiti/commit/7ef2cea654c48175504105f907b5b651c9fcdaf3))
23
+
24
+ # [2.0.0-beta.8](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2026-08-10)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * accept a single value for array filters ([#517](https://github.com/graphiti-api/graphiti/issues/517)) ([ee685b6](https://github.com/graphiti-api/graphiti/commit/ee685b625e967a8225d48b486bd093e3a6970176))
30
+ * accept a single value for array filters ([#517](https://github.com/graphiti-api/graphiti/issues/517)) ([23f3e80](https://github.com/graphiti-api/graphiti/commit/23f3e80b583b0bf0843d8bb9334970ae56d83299))
31
+ * drop the relationship guard install notice ([65b8be6](https://github.com/graphiti-api/graphiti/commit/65b8be65455a7d034c9dca082434c1dbfe610a7a))
32
+ * reject invalid page parameters ([#537](https://github.com/graphiti-api/graphiti/issues/537)) ([fbb5683](https://github.com/graphiti-api/graphiti/commit/fbb5683393301e17ffcea84990ac5a95bdd3e15f)), closes [#347](https://github.com/graphiti-api/graphiti/issues/347)
33
+ * treat empty polymorphic configuration as unset ([#538](https://github.com/graphiti-api/graphiti/issues/538)) ([a27eeea](https://github.com/graphiti-api/graphiti/commit/a27eeea6eeb72749376c1f7959cadc89012142b9)), closes [#199](https://github.com/graphiti-api/graphiti/issues/199)
34
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3756fd1](https://github.com/graphiti-api/graphiti/commit/3756fd12c48d2fb3cf5e2d40c38aab92f12218b0))
35
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3dcf4c8](https://github.com/graphiti-api/graphiti/commit/3dcf4c868abb12eb6b6d59cd0814b227d4838ece))
36
+
37
+
38
+ ### Features
39
+
40
+ * add full set of commented-out Resource defaults in ApplicationResource when using the generator ([367678e](https://github.com/graphiti-api/graphiti/commit/367678eb3c2c737331f8edb00e00e70483a778b4))
41
+ * **generators:** explicit rendering by default, respond_with only when Responders is included ([70741fe](https://github.com/graphiti-api/graphiti/commit/70741fef2a3a585bbef32c3e535027ff610fda77))
42
+ * in-tree plain ruby and sinatra examples ([b92c6d9](https://github.com/graphiti-api/graphiti/commit/b92c6d9af22f759cc1f1eaccc250ecd04901e2aa))
43
+ * ship the Rails application template in this repo ([76d5329](https://github.com/graphiti-api/graphiti/commit/76d53291f94c576792e3649044784f88fbaa47a7))
44
+
3
45
  # [2.0.0-beta.7](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2026-08-10)
4
46
 
5
47
 
@@ -101,6 +143,30 @@ GraphitiErrors::Validation::Serializer is now Graphiti::ErrorSerializers::Valida
101
143
  * around_persistence hooks receive the assigned model instead of the attributes hash. Move attribute-hash modifications to before_attributes, or set values on the model. Custom create/update overrides that should receive a pre-assigned model must accept an assigned_model: keyword. See UPGRADING.md
102
144
  * Ruby >= 3.0 / Rails >= 6 are now required.
103
145
 
146
+ ## [1.13.3](https://github.com/graphiti-api/graphiti/compare/v1.13.2...v1.13.3) (2026-08-09)
147
+
148
+
149
+ ### Bug Fixes
150
+
151
+ * reject invalid page parameters ([#537](https://github.com/graphiti-api/graphiti/issues/537)) ([fbb5683](https://github.com/graphiti-api/graphiti/commit/fbb5683393301e17ffcea84990ac5a95bdd3e15f)), closes [#347](https://github.com/graphiti-api/graphiti/issues/347)
152
+ * treat empty polymorphic configuration as unset ([#538](https://github.com/graphiti-api/graphiti/issues/538)) ([a27eeea](https://github.com/graphiti-api/graphiti/commit/a27eeea6eeb72749376c1f7959cadc89012142b9)), closes [#199](https://github.com/graphiti-api/graphiti/issues/199)
153
+
154
+ ## [1.13.2](https://github.com/graphiti-api/graphiti/compare/v1.13.1...v1.13.2) (2026-08-07)
155
+
156
+
157
+ ### Bug Fixes
158
+
159
+ * drop the relationship guard install notice ([65b8be6](https://github.com/graphiti-api/graphiti/commit/65b8be65455a7d034c9dca082434c1dbfe610a7a))
160
+
161
+ ## [1.13.1](https://github.com/graphiti-api/graphiti/compare/v1.13.0...v1.13.1) (2026-08-02)
162
+
163
+
164
+ ### Bug Fixes
165
+
166
+ * accept a single value for array filters ([#517](https://github.com/graphiti-api/graphiti/issues/517)) ([23f3e80](https://github.com/graphiti-api/graphiti/commit/23f3e80b583b0bf0843d8bb9334970ae56d83299))
167
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3dcf4c8](https://github.com/graphiti-api/graphiti/commit/3dcf4c868abb12eb6b6d59cd0814b227d4838ece))
168
+
169
+
104
170
  # [1.13.0](https://github.com/graphiti-api/graphiti/compare/v1.12.2...v1.13.0) (2026-07-30)
105
171
 
106
172
 
data/CONTRIBUTING.md CHANGED
@@ -65,7 +65,7 @@ The Rails integration specs are gated behind an environment variable and are
65
65
  covering none of the ActiveRecord adapter:
66
66
 
67
67
  ```sh
68
- BUNDLE_GEMFILE=gemfiles/rails_8_0_graphiti_rails.gemfile \
68
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile \
69
69
  APPRAISAL_INITIALIZED=1 \
70
70
  bundle exec rspec spec/integration/rails/
71
71
  ```
@@ -75,3 +75,5 @@ Lint with:
75
75
  ```sh
76
76
  bundle exec standardrb
77
77
  ```
78
+
79
+ CI fails PRs that don't pass lint, and almost everything standard flags is auto-fixable — run `bundle exec standardrb --fix` before pushing. (A bare `rake` also runs lint before the specs.)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- #### Graphiti
1
+ ### Graphiti
2
2
 
3
3
  [![CI](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml/badge.svg)](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphiti.svg)](https://badge.fury.io/rb/graphiti)
@@ -7,233 +7,40 @@
7
7
 
8
8
 
9
9
  [![discord](https://img.shields.io/badge/community-discord-8A2BE2?logo=discord)](https://discord.gg/wgqkMBsSRV)
10
- [![guides](https://img.shields.io/badge/guides-https://www.graphiti.dev-F565A5)](https://www.graphiti.dev)
10
+ [![guides](https://img.shields.io/badge/guides-https://graphiti.dev-F565A5)](https://graphiti.dev)
11
11
 
12
12
 
13
13
 
14
14
  <img align="right" src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" alt="Graphiti logo" width="150px" />
15
- Graphiti is a resource-oriented framework that sits on top of your models (usually ActiveRecord) and exposes them via a JSON:API-compliant interface. It abstracts common concerns like serialization, filtering, sorting, pagination, and sideloading relationships, so you can build powerful APIs with minimal boilerplate. By defining resources instead of controllers and serializers, Graphiti helps you keep your API logic organized, consistent, and easy to maintain.
16
15
 
16
+ Graphiti sits on top of your models (usually ActiveRecord) and exposes them over a JSON:API-compliant interface. You define Resources instead of controllers and serializers, and get filtering, sorting, pagination, sparse fieldsets, statistics, and nested reads and writes across relationships, all over one endpoint.
17
17
 
18
- #### Examples
19
- Here's an example resource from the [example app](https://github.com/graphiti-api/employee_directory/) just to give you a taste of the possibilities.
18
+ It's built on the [JSON:API](https://jsonapi.org) spec, which settles the decisions every API accumulates: response shapes, filtering, sorting, pagination, error formats, and how related data rides along. Your client layer speaks this protocol in return. It isn't complicated, so client logic can be hand-rolled or you can use one of the [many available libraries](https://jsonapi.org/implementations/#client-libraries) that work with the standard.
20
19
 
20
+ A Resource looks like this:
21
21
 
22
22
  ```ruby
23
23
  class EmployeeResource < ApplicationResource
24
24
  attribute :first_name, :string
25
25
  attribute :last_name, :string
26
26
  attribute :age, :integer
27
- attribute :created_at, :datetime, writable: false
28
- attribute :updated_at, :datetime, writable: false
29
- attribute :title, :string, only: [:filterable, :sortable]
30
27
 
31
28
  has_many :positions
32
- has_many :tasks
33
- many_to_many :teams
34
- polymorphic_has_many :notes, as: :notable
35
- has_one :current_position, resource: PositionResource do
36
- params do |hash|
37
- hash[:filter][:current] = true
38
- end
39
- end
40
-
41
- filter :title, only: [:eq] do
42
- eq do |scope, value|
43
- scope.joins(:current_position).merge(Position.where(title: value))
44
- end
45
- end
46
-
47
- sort :title do |scope, value|
48
- scope.joins(:current_position).merge(Position.order(title: value))
49
- end
50
-
51
- sort :department_name, :string do |scope, value|
52
- scope.joins(current_position: :department)
53
- .merge(Department.order(name: value))
54
- end
55
29
  end
56
30
  ```
57
31
 
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.
32
+ That Resource serves `?filter[age][gt]=30`, `?sort=-age`, `?page[size]=10`, `?include=positions` and more, without writing any of them. See [graphiti.dev](https://graphiti.dev/) for the whole loop, or the [example app](https://github.com/graphiti-api/employee_directory/) for a full working API.
59
33
 
60
- ```ruby
61
- class ApplicationController < ActionController::Base
62
- include Graphiti::Rails::Controller
63
- end
64
- ```
34
+ ### Documentation
65
35
 
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.
36
+ Docs live at [graphiti.dev](https://graphiti.dev/) and are rendered from this repo: the markdown is in [`/docs`](docs), and the Docusaurus site that serves it is in [`/website`](website). Fixes and improvements are welcome (every page has an "Edit this page" link at the bottom, which makes opening a PR with a change easy).
67
37
 
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.
38
+ To preview locally:
69
39
 
70
- ```ruby
71
- class EmployeesController < ApplicationController
72
- def index
73
- employees = EmployeeResource.all(params)
74
- respond_with(employees)
75
- end
76
-
77
- def show
78
- employee = EmployeeResource.find(params)
79
- respond_with(employee)
80
- end
81
-
82
- def create
83
- employee = EmployeeResource.build(params)
84
-
85
- if employee.save
86
- render jsonapi: employee, status: 201
87
- else
88
- render jsonapi_errors: employee
89
- end
90
- end
91
-
92
- def update
93
- employee = EmployeeResource.find(params)
94
-
95
- if employee.update_attributes
96
- render jsonapi: employee
97
- else
98
- render jsonapi_errors: employee
99
- end
100
- end
101
-
102
- def destroy
103
- employee = EmployeeResource.find(params)
104
-
105
- if employee.destroy
106
- render jsonapi: { meta: {} }, status: 200
107
- else
108
- render jsonapi_errors: employee
109
- end
110
- end
111
- end
112
- ```
113
-
114
- </details>
115
-
116
-
117
- Now you can query your endpoints simply and powerfully, like:
118
-
119
-
120
-
121
- Request:
122
- ```http://localhost:3000/api/v1/employees?filter[title][eq]=Future Government Administrator&filter[age][lt]=40```
123
-
124
- <details>
125
- <summary>JSON-API response</summary>
126
-
127
- ```json
128
- {
129
- "data": [
130
- {
131
- "id": "1",
132
- "type": "employees",
133
- "attributes": {
134
- "first_name": "Quinn",
135
- "last_name": "Homenick",
136
- "age": 36,
137
- "created_at": "2025-03-21T23:04:40+00:00",
138
- "updated_at": "2025-03-21T23:04:40+00:00"
139
- },
140
- "relationships": {
141
- "positions": {
142
- "links": {
143
- "related": "/api/v1/positions?filter[employee_id]=1"
144
- },
145
- "data": [
146
- {
147
- "type": "positions",
148
- "id": "1"
149
- },
150
- {
151
- "type": "positions",
152
- "id": "2"
153
- }
154
- ]
155
- },
156
- "tasks": {
157
- "links": {
158
- "related": "/api/v1/tasks?filter[employee_id]=1"
159
- }
160
- },
161
- "teams": {
162
- "links": {
163
- "related": "/api/v1/teams?filter[employee_id]=1"
164
- }
165
- },
166
- "notes": {
167
- "links": {
168
- "related": "/api/v1/notes?filter[notable_id]=1&filter[notable_type][eql]=Employee"
169
- }
170
- },
171
- "current_position": {
172
- "links": {
173
- "related": "/api/v1/positions?filter[current]=true&filter[employee_id]=1"
174
- },
175
- "data": {
176
- "type": "positions",
177
- "id": "1"
178
- }
179
- }
180
- }
181
- }
182
- ],
183
- "included": [
184
- {
185
- "id": "1",
186
- "type": "positions",
187
- "attributes": {
188
- "title": "Future Government Administrator",
189
- "active": true
190
- },
191
- "relationships": {
192
- "employee": {
193
- "links": {
194
- "related": "/api/v1/employees/1"
195
- }
196
- },
197
- "department": {
198
- "links": {
199
- "related": "/api/v1/departments/3"
200
- }
201
- }
202
- }
203
- },
204
- {
205
- "id": "2",
206
- "type": "positions",
207
- "attributes": {
208
- "title": "Manufacturing Specialist",
209
- "active": false
210
- },
211
- "relationships": {
212
- "employee": {
213
- "links": {
214
- "related": "/api/v1/employees/1"
215
- }
216
- },
217
- "department": {
218
- "links": {
219
- "related": "/api/v1/departments/2"
220
- }
221
- }
222
- }
223
- }
224
- ],
225
- "meta": {}
226
- }
40
+ ```bash
41
+ cd website
42
+ npm install
43
+ npm run start
227
44
  ```
228
45
 
229
- </details>
230
-
231
-
232
-
233
- [Graphiti Documentation](https://www.graphiti.dev/)
234
-
235
46
  [Join the Discord](https://discord.gg/wgqkMBsSRV)
236
-
237
-
238
-
239
-
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
  require "appraisal"
4
+ # Standard is silent on success; show the inspected-files summary.
5
+ ENV["STANDARDOPTS"] ||= "--format progress"
6
+ require "standard/rake"
4
7
 
5
8
  RSpec::Core::RakeTask.new(:spec) do |t|
6
9
  if ENV["APPRAISAL_INITIALIZED"]
@@ -8,8 +11,36 @@ RSpec::Core::RakeTask.new(:spec) do |t|
8
11
  end
9
12
  end
10
13
 
11
- if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
12
- task default: [:spec, :appraisal]
13
- else
14
+ namespace :performance do
15
+ # Shelling out keeps a failure to the script's own message, with no rake backtrace on top.
16
+ def measure(*arguments)
17
+ exit(1) unless system("bundle", "exec", "ruby", "spec/performance/measure_releases.rb", *arguments)
18
+ end
19
+
20
+ desc "Compare the working tree to the last release, and plot it"
21
+ task :current do
22
+ measure
23
+ end
24
+
25
+ desc "Plot the release history plus the working tree to tmp/performance.html and open it"
26
+ task :page do
27
+ exit(1) unless system("bundle", "exec", "ruby", "spec/performance/chart_page.rb")
28
+ end
29
+
30
+ desc "Measure one release and record it (TAG=v2.0.0-beta.9)"
31
+ task :record do
32
+ tag = ENV["TAG"] or abort "pass the release to record, e.g. rake performance:record TAG=v2.0.0-beta.9"
33
+ measure(tag)
34
+ end
35
+
36
+ desc "Re-record every release for this ruby, replacing its rows"
37
+ task :record_all do
38
+ measure("--all")
39
+ end
40
+ end
41
+
42
+ if ENV["APPRAISAL_INITIALIZED"]
14
43
  task default: [:spec]
44
+ else
45
+ task default: [:standard, :spec, :appraisal]
15
46
  end
data/UPGRADING.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Upgrading Graphiti
2
2
 
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**
3
+ The upgrade guide lives with the rest of the documentation, so it can link into the concept pages and offer the same version picker: **https://graphiti.dev/upgrading**
4
4
 
5
- In this repository it is [docs/upgrading/](docs/upgrading/index.md).
5
+ In this repository it is [docs/upgrading.md](docs/upgrading.md).
data/graphiti.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.homepage = "https://github.com/graphiti-api/graphiti"
13
13
  spec.license = "MIT"
14
14
 
15
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|templates|website|docs|examples)/}) }
16
16
  spec.bindir = "exe"
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ["lib"]
@@ -27,12 +27,16 @@ Gem::Specification.new do |spec|
27
27
  # Bundled (no longer default) as of Ruby 4.0; graphiti uses OpenStruct in lib/
28
28
  spec.add_dependency "ostruct", ">= 0.5"
29
29
 
30
- spec.add_development_dependency "faraday", "~> 0.15"
31
- spec.add_development_dependency "kaminari", "~> 0.17"
30
+ spec.add_development_dependency "faraday", ">= 2.0", "< 3"
31
+ spec.add_development_dependency "kaminari", "~> 1.2"
32
32
  spec.add_development_dependency "bundler"
33
33
  spec.add_development_dependency "rake", ">= 10.0"
34
- spec.add_development_dependency "standard", "~> 1.4.0"
34
+ spec.add_development_dependency "standard", "~> 1.56"
35
35
  spec.add_development_dependency "activemodel", ">= 7.1"
36
+ # Only the specs need this, for ActiveSupport's Hash.from_xml when asserting
37
+ # on to_xml output. It used to arrive transitively via rubocop; rubocop 1.88
38
+ # dropped it, and it is a bundled (not default) gem since Ruby 3.0.
39
+ spec.add_development_dependency "rexml"
36
40
  # Was reaching the suite transitively through graphiti_spec_helpers, which is
37
41
  # now part of this gem.
38
42
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -39,7 +39,7 @@ module Graphiti
39
39
 
40
40
  def update_config!(attrs)
41
41
  config = graphiti_config.merge(attrs)
42
- File.open(".graphiticfg.yml", "w") { |f| f.write(config.to_yaml) }
42
+ File.write(".graphiticfg.yml", config.to_yaml)
43
43
  end
44
44
 
45
45
  def id_or_rawid
@@ -6,7 +6,7 @@ module Graphiti
6
6
 
7
7
  source_root File.expand_path("templates", __dir__)
8
8
 
9
- class_option :'omit-comments',
9
+ class_option :"omit-comments",
10
10
  type: :boolean,
11
11
  default: false,
12
12
  aliases: ["-c"],