graphiti 2.0.0.beta.6 → 2.0.0.beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +55 -3
  4. data/.github/workflows/docs.yml +1 -1
  5. data/.github/workflows/notify-example-app.yml +18 -0
  6. data/.gitignore +2 -0
  7. data/.standard.yml +6 -0
  8. data/CHANGELOG.md +54 -0
  9. data/CONTRIBUTING.md +3 -1
  10. data/README.md +13 -206
  11. data/Rakefile +6 -3
  12. data/UPGRADING.md +1 -1
  13. data/graphiti.gemspec +8 -4
  14. data/lib/generators/graphiti/generator_mixin.rb +1 -1
  15. data/lib/generators/graphiti/install_generator.rb +1 -2
  16. data/lib/generators/graphiti/resource_generator.rb +6 -3
  17. data/lib/generators/graphiti/templates/application_resource.rb.erb +19 -0
  18. data/lib/generators/graphiti/templates/controller.rb.erb +26 -8
  19. data/lib/generators/graphiti/templates/index_request_spec.rb.erb +2 -2
  20. data/lib/generators/graphiti/templates/resource_reads_spec.rb.erb +4 -4
  21. data/lib/generators/graphiti/templates/show_request_spec.rb.erb +2 -2
  22. data/lib/graphiti/audit/report.rb +6 -6
  23. data/lib/graphiti/audit.rb +3 -4
  24. data/lib/graphiti/delegates/pagination.rb +2 -2
  25. data/lib/graphiti/deserializer.rb +3 -3
  26. data/lib/graphiti/errors.rb +6 -6
  27. data/lib/graphiti/extensions/temp_id.rb +1 -1
  28. data/lib/graphiti/query.rb +27 -8
  29. data/lib/graphiti/rails/context.rb +2 -1
  30. data/lib/graphiti/rails/controller.rb +3 -0
  31. data/lib/graphiti/rails/debugging.rb +1 -1
  32. data/lib/graphiti/rails/responders.rb +2 -1
  33. data/lib/graphiti/resource/configuration.rb +1 -0
  34. data/lib/graphiti/resource/dsl.rb +2 -2
  35. data/lib/graphiti/resource/interface.rb +3 -3
  36. data/lib/graphiti/resource/links.rb +1 -1
  37. data/lib/graphiti/resource/persistence.rb +1 -1
  38. data/lib/graphiti/resource/polymorphism.rb +1 -1
  39. data/lib/graphiti/resource.rb +2 -2
  40. data/lib/graphiti/resource_proxy.rb +0 -1
  41. data/lib/graphiti/runner.rb +8 -3
  42. data/lib/graphiti/scope.rb +13 -0
  43. data/lib/graphiti/scoping/filter.rb +5 -3
  44. data/lib/graphiti/scoping/paginate.rb +1 -1
  45. data/lib/graphiti/scoping/sort.rb +1 -1
  46. data/lib/graphiti/sideload/has_many.rb +1 -1
  47. data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
  48. data/lib/graphiti/spec_helpers/matchers.rb +1 -1
  49. data/lib/graphiti/spec_helpers.rb +5 -0
  50. data/lib/graphiti/types.rb +5 -1
  51. data/lib/graphiti/util/cache_debug.rb +3 -3
  52. data/lib/graphiti/util/hash.rb +1 -1
  53. data/lib/graphiti/util/persistence.rb +6 -6
  54. data/lib/graphiti/util/serializer_relationships.rb +1 -1
  55. data/lib/graphiti/version.rb +1 -1
  56. data/lib/tasks/graphiti.rake +1 -1
  57. data/package.json +1 -0
  58. metadata +31 -197
  59. data/docs/concepts/backends-and-models.md +0 -122
  60. data/docs/concepts/endpoints.md +0 -183
  61. data/docs/concepts/links.md +0 -212
  62. data/docs/concepts/overview.md +0 -80
  63. data/docs/concepts/persisting.md +0 -376
  64. data/docs/concepts/relationships.md +0 -627
  65. data/docs/concepts/resources.md +0 -677
  66. data/docs/getting-started/first-api.md +0 -289
  67. data/docs/getting-started/installation.md +0 -185
  68. data/docs/intro.md +0 -307
  69. data/docs/js/authentication.md +0 -63
  70. data/docs/js/ddau.md +0 -20
  71. data/docs/js/extra-params.md +0 -41
  72. data/docs/js/index.md +0 -112
  73. data/docs/js/installation.md +0 -120
  74. data/docs/js/middleware.md +0 -72
  75. data/docs/js/models.md +0 -202
  76. data/docs/js/reads.md +0 -494
  77. data/docs/js/state-syncing.md +0 -100
  78. data/docs/js/writes.md +0 -373
  79. data/docs/reference/vandal.md +0 -63
  80. data/docs/reference/why.md +0 -13
  81. data/docs/topics/authorization.md +0 -155
  82. data/docs/topics/caching.md +0 -55
  83. data/docs/topics/customizing-sideloads.md +0 -156
  84. data/docs/topics/debugging.md +0 -242
  85. data/docs/topics/error-handling.md +0 -210
  86. data/docs/topics/etags.md +0 -46
  87. data/docs/topics/hopping-relationships.md +0 -149
  88. data/docs/topics/json-attributes.md +0 -77
  89. data/docs/topics/openstruct-models.md +0 -50
  90. data/docs/topics/remote-resources.md +0 -291
  91. data/docs/topics/testing.md +0 -894
  92. data/docs/topics/without-activerecord.md +0 -324
  93. data/docs/tutorial/index.md +0 -58
  94. data/docs/tutorial/step_0.md +0 -107
  95. data/docs/tutorial/step_1.md +0 -199
  96. data/docs/tutorial/step_2.md +0 -312
  97. data/docs/tutorial/step_3.md +0 -142
  98. data/docs/tutorial/step_4.md +0 -135
  99. data/docs/tutorial/step_5.md +0 -69
  100. data/docs/tutorial/step_6.md +0 -82
  101. data/docs/tutorial/step_7.md +0 -205
  102. data/docs/tutorial/step_8.md +0 -128
  103. data/docs/tutorial/step_9.md +0 -171
  104. data/docs/upgrading.md +0 -266
  105. data/website/.gitignore +0 -20
  106. data/website/README.md +0 -43
  107. data/website/docusaurus.config.js +0 -197
  108. data/website/package-lock.json +0 -19474
  109. data/website/package.json +0 -46
  110. data/website/sidebars.js +0 -82
  111. data/website/src/css/custom.css +0 -85
  112. data/website/src/pages/markdown-page.mdx +0 -7
  113. data/website/static/.nojekyll +0 -0
  114. data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
  115. data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
  116. data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
  117. data/website/static/1.13/2019/10/14/tutorial.html +0 -198
  118. data/website/static/1.13/CNAME +0 -1
  119. data/website/static/1.13/README.md +0 -16
  120. data/website/static/1.13/assets/css/syntax.css +0 -60
  121. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  122. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  123. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  124. data/website/static/1.13/assets/favicons/browserconfig.xml +0 -9
  125. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  126. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  127. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  128. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  129. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +0 -1
  130. data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
  131. data/website/static/1.13/assets/img/backend.gif +0 -0
  132. data/website/static/1.13/assets/img/conformity.png +0 -0
  133. data/website/static/1.13/assets/img/error_payload.png +0 -0
  134. data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
  135. data/website/static/1.13/assets/img/gh.png +0 -0
  136. data/website/static/1.13/assets/img/home-bg.jpg +0 -0
  137. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  138. data/website/static/1.13/assets/img/logo-500.png +0 -0
  139. data/website/static/1.13/assets/img/logo.png +0 -0
  140. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  141. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  142. data/website/static/1.13/assets/img/persist.jpg +0 -0
  143. data/website/static/1.13/assets/img/resource.gif +0 -0
  144. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  145. data/website/static/1.13/assets/img/rest1.gif +0 -0
  146. data/website/static/1.13/assets/img/rest2.gif +0 -0
  147. data/website/static/1.13/assets/img/rest3.gif +0 -0
  148. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  149. data/website/static/1.13/assets/img/sunrise.png +0 -0
  150. data/website/static/1.13/assets/img/why.png +0 -0
  151. data/website/static/1.13/assets/js/highlight.pack.js +0 -2
  152. data/website/static/1.13/assets/main.css +0 -15518
  153. data/website/static/1.13/assets/main.css.map +0 -1
  154. data/website/static/1.13/bin/bundle +0 -109
  155. data/website/static/1.13/bin/jekyll +0 -27
  156. data/website/static/1.13/bin/kramdown +0 -27
  157. data/website/static/1.13/bin/listen +0 -27
  158. data/website/static/1.13/bin/rake +0 -27
  159. data/website/static/1.13/bin/rougify +0 -27
  160. data/website/static/1.13/bin/safe_yaml +0 -27
  161. data/website/static/1.13/bin/sass +0 -27
  162. data/website/static/1.13/bin/sass-convert +0 -27
  163. data/website/static/1.13/bin/scss +0 -27
  164. data/website/static/1.13/blog.html +0 -259
  165. data/website/static/1.13/cheatsheet.html +0 -316
  166. data/website/static/1.13/cookbooks/authorization.md +0 -0
  167. data/website/static/1.13/cookbooks/caching.md +0 -0
  168. data/website/static/1.13/cookbooks/customizing-sideloads.html +0 -325
  169. data/website/static/1.13/cookbooks/etags.md +0 -0
  170. data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
  171. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  172. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  173. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  174. data/website/static/1.13/cookbooks/without-activerecord.html +0 -510
  175. data/website/static/1.13/features.html +0 -249
  176. data/website/static/1.13/feed.xml +0 -106
  177. data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
  178. data/website/static/1.13/guides/concepts/debugging.html +0 -440
  179. data/website/static/1.13/guides/concepts/endpoints.html +0 -432
  180. data/website/static/1.13/guides/concepts/error-handling.html +0 -396
  181. data/website/static/1.13/guides/concepts/links.html +0 -501
  182. data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
  183. data/website/static/1.13/guides/concepts/resources.html +0 -2176
  184. data/website/static/1.13/guides/concepts/testing.html +0 -1469
  185. data/website/static/1.13/guides/getting-started/installation.html +0 -420
  186. data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
  187. data/website/static/1.13/guides/index.html +0 -269
  188. data/website/static/1.13/guides/overview.html +0 -325
  189. data/website/static/1.13/guides/upgrading-2-0.html +0 -193
  190. data/website/static/1.13/guides/upgrading.html +0 -314
  191. data/website/static/1.13/guides/vandal.html +0 -282
  192. data/website/static/1.13/guides/why.html +0 -1121
  193. data/website/static/1.13/index.html +0 -72
  194. data/website/static/1.13/js/authentication.html +0 -295
  195. data/website/static/1.13/js/ddau.html +0 -238
  196. data/website/static/1.13/js/extra-params.html +0 -270
  197. data/website/static/1.13/js/index.html +0 -321
  198. data/website/static/1.13/js/installation.html +0 -637
  199. data/website/static/1.13/js/introduction.html +0 -257
  200. data/website/static/1.13/js/middleware.html +0 -318
  201. data/website/static/1.13/js/reads/fieldsets.html +0 -271
  202. data/website/static/1.13/js/reads/filtering.html +0 -289
  203. data/website/static/1.13/js/reads/includes.html +0 -260
  204. data/website/static/1.13/js/reads/index.html +0 -497
  205. data/website/static/1.13/js/reads/nested-queries.html +0 -353
  206. data/website/static/1.13/js/reads/pagination.html +0 -260
  207. data/website/static/1.13/js/reads/sorting.html +0 -265
  208. data/website/static/1.13/js/reads/statistics.html +0 -289
  209. data/website/static/1.13/js/state-syncing.html +0 -340
  210. data/website/static/1.13/js/writes/deferred.html +0 -296
  211. data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
  212. data/website/static/1.13/js/writes/index.html +0 -391
  213. data/website/static/1.13/js/writes/nested.html +0 -330
  214. data/website/static/1.13/js/writes/validations.html +0 -272
  215. data/website/static/1.13/quickstart.html +0 -660
  216. data/website/static/1.13/template +0 -161
  217. data/website/static/1.13/tutorial/index.html +0 -250
  218. data/website/static/1.13/tutorial/step_0.html +0 -292
  219. data/website/static/1.13/tutorial/step_1.html +0 -517
  220. data/website/static/1.13/tutorial/step_2.html +0 -481
  221. data/website/static/1.13/tutorial/step_3.html +0 -323
  222. data/website/static/1.13/tutorial/step_4.html +0 -318
  223. data/website/static/1.13/tutorial/step_5.html +0 -265
  224. data/website/static/1.13/tutorial/step_6.html +0 -276
  225. data/website/static/1.13/tutorial/step_7.html +0 -390
  226. data/website/static/1.13/tutorial/step_8.html +0 -316
  227. data/website/static/1.13/tutorial/step_9.html +0 -365
  228. data/website/static/CNAME +0 -1
  229. data/website/static/assets/img/error_payload.png +0 -0
  230. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  231. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  232. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  233. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  234. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  235. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  236. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  237. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  238. data/website/static/assets/img/meta_total_count.png +0 -0
  239. data/website/static/img/docusaurus-social-card.jpg +0 -0
  240. data/website/static/img/docusaurus.png +0 -0
  241. data/website/static/img/favicon.ico +0 -0
  242. data/website/static/img/logo.png +0 -0
  243. data/website/static/img/logo.svg +0 -1
  244. data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
  245. data/website/static/img/undraw_docusaurus_react.svg +0 -170
  246. 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: e108e5f71103c3fb7f38f5328a890ff131b264201c583509081cfd3106a7b255
4
- data.tar.gz: 18edd07b0ab429ec89309ec94164a2324a21bbbfbd6dd37f6e49bb6f9707f1c9
3
+ metadata.gz: b5605ecf7bc316cef1342565d7cd54cf7136a98915d669905550dcce32452d7c
4
+ data.tar.gz: 71be03dd2e18fed5485fe154957e8d5366ec21d648cfaf023281277e7d7c5e5a
5
5
  SHA512:
6
- metadata.gz: f2affdd800c2966ef15c5d1765b9d1acc48a032c3ee143b9283d69b9545290e227454d77559e25c94b4cd7b0131d0cdc2addbb031a4177155d8085086d818b3b
7
- data.tar.gz: 79c294b775b8be269194cae7e61cf179a16ec8dd01e8b0b24e7a141d3333683bfbd54032e4c07623f6d2dc3fbe3ce6c721884658da89cbe24bfd9dac935f8e07
6
+ metadata.gz: facebd92bdb5be565324696a089595e2b224cbf79872e60cc92dc4422ded817ad22937a3e9b9008744140f55b1519b97f00cb0e7708a92a7bfda88d5339a18a9
7
+ data.tar.gz: cb80adbf97d38e470c1a6b56bb0cbe49b59abfbe4a81cc0438696cc6452b2677a6f4d2d43a859c6eb4e06815ac4912ae3aa4c52085fbff89f8f05b1f5c9c32eb
@@ -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,13 +78,61 @@ 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
@@ -25,7 +25,7 @@ jobs:
25
25
  name: Build and publish
26
26
  runs-on: ubuntu-latest
27
27
  steps:
28
- - uses: actions/checkout@v3
28
+ - uses: actions/checkout@v4
29
29
  - uses: actions/setup-node@v4
30
30
  with:
31
31
  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 }}
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,35 @@
1
1
  graphiti changelog
2
2
 
3
+ # [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)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * 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))
9
+ * 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))
10
+ * drop the relationship guard install notice ([65b8be6](https://github.com/graphiti-api/graphiti/commit/65b8be65455a7d034c9dca082434c1dbfe610a7a))
11
+ * 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)
12
+ * 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)
13
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3756fd1](https://github.com/graphiti-api/graphiti/commit/3756fd12c48d2fb3cf5e2d40c38aab92f12218b0))
14
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3dcf4c8](https://github.com/graphiti-api/graphiti/commit/3dcf4c868abb12eb6b6d59cd0814b227d4838ece))
15
+
16
+
17
+ ### Features
18
+
19
+ * add full set of commented-out Resource defaults in ApplicationResource when using the generator ([367678e](https://github.com/graphiti-api/graphiti/commit/367678eb3c2c737331f8edb00e00e70483a778b4))
20
+ * **generators:** explicit rendering by default, respond_with only when Responders is included ([70741fe](https://github.com/graphiti-api/graphiti/commit/70741fef2a3a585bbef32c3e535027ff610fda77))
21
+ * in-tree plain ruby and sinatra examples ([b92c6d9](https://github.com/graphiti-api/graphiti/commit/b92c6d9af22f759cc1f1eaccc250ecd04901e2aa))
22
+ * ship the Rails application template in this repo ([76d5329](https://github.com/graphiti-api/graphiti/commit/76d53291f94c576792e3649044784f88fbaa47a7))
23
+
24
+ # [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)
25
+
26
+
27
+ ### Features
28
+
29
+ * dedupe under concurrency and across distinct resources ([800b1a2](https://github.com/graphiti-api/graphiti/commit/800b1a2bf9bb59702efd00afe8e4f54ae06b500e))
30
+ * deduplicate sideloaded entities across include paths ([0c2829a](https://github.com/graphiti-api/graphiti/commit/0c2829a634e9b96e2403ff168be9a48c6a663c78))
31
+ * deprecate SpecHelpers::Sugar in favor of the full helper names ([b155849](https://github.com/graphiti-api/graphiti/commit/b155849f55fe84b2d0911fa4681d82682dfa39ae))
32
+
3
33
  # [2.0.0-beta.6](https://github.com/graphiti-api/graphiti/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2026-08-09)
4
34
 
5
35
 
@@ -92,6 +122,30 @@ GraphitiErrors::Validation::Serializer is now Graphiti::ErrorSerializers::Valida
92
122
  * 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
93
123
  * Ruby >= 3.0 / Rails >= 6 are now required.
94
124
 
125
+ ## [1.13.3](https://github.com/graphiti-api/graphiti/compare/v1.13.2...v1.13.3) (2026-08-09)
126
+
127
+
128
+ ### Bug Fixes
129
+
130
+ * 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)
131
+ * 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)
132
+
133
+ ## [1.13.2](https://github.com/graphiti-api/graphiti/compare/v1.13.1...v1.13.2) (2026-08-07)
134
+
135
+
136
+ ### Bug Fixes
137
+
138
+ * drop the relationship guard install notice ([65b8be6](https://github.com/graphiti-api/graphiti/commit/65b8be65455a7d034c9dca082434c1dbfe610a7a))
139
+
140
+ ## [1.13.1](https://github.com/graphiti-api/graphiti/compare/v1.13.0...v1.13.1) (2026-08-02)
141
+
142
+
143
+ ### Bug Fixes
144
+
145
+ * 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))
146
+ * typecast null values in filter lists ([#515](https://github.com/graphiti-api/graphiti/issues/515)) ([3dcf4c8](https://github.com/graphiti-api/graphiti/commit/3dcf4c868abb12eb6b6d59cd0814b227d4838ece))
147
+
148
+
95
149
  # [1.13.0](https://github.com/graphiti-api/graphiti/compare/v1.12.2...v1.13.0) (2026-07-30)
96
150
 
97
151
 
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,8 @@ 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
+ if ENV["APPRAISAL_INITIALIZED"]
14
15
  task default: [:spec]
16
+ else
17
+ task default: [:standard, :spec, :appraisal]
15
18
  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
5
  In this repository it is [docs/upgrading/](docs/upgrading/index.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"],
@@ -49,7 +49,6 @@ module Graphiti
49
49
 
50
50
  RSpec.configure do |config|
51
51
  config.include Graphiti::SpecHelpers::RSpec
52
- config.include Graphiti::SpecHelpers::Sugar
53
52
  end
54
53
 
55
54
  Graphiti::SpecHelpers::RSpec.schema!
@@ -8,7 +8,7 @@ module Graphiti
8
8
 
9
9
  argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
10
10
 
11
- class_option :'omit-comments',
11
+ class_option :"omit-comments",
12
12
  type: :boolean,
13
13
  default: false,
14
14
  aliases: ["--omit-comments", "-c"],
@@ -26,7 +26,7 @@ module Graphiti
26
26
  aliases: ["--actions", "-a"],
27
27
  desc: 'Array of controller actions to support, e.g. "index show destroy"'
28
28
 
29
- class_option :'attributes-from',
29
+ class_option :"attributes-from",
30
30
  banner: "Model",
31
31
  type: :string,
32
32
  aliases: ["--model", "-m"],
@@ -127,8 +127,11 @@ module Graphiti
127
127
  end
128
128
  end
129
129
 
130
+ # Generated controllers only use respond_with when it will actually work:
131
+ # the responders gem being installed is not enough, the controller chain
132
+ # must include Graphiti::Rails::Responders.
130
133
  def responders?
131
- defined?(::Responders)
134
+ "ApplicationController".safe_constantize&.include?(Graphiti::Rails::Responders)
132
135
  end
133
136
 
134
137
  # Api::V1::Posts, Api::V1::PostsController and api/v1/posts all name the
@@ -12,4 +12,23 @@ class ApplicationResource < Graphiti::Resource
12
12
  self.adapter = Graphiti::Adapters::ActiveRecord
13
13
  self.base_url = Rails.application.routes.default_url_options[:host]
14
14
  self.endpoint_namespace = '<%= api_namespace %>'
15
+ <%- unless omit_comments? -%>
16
+
17
+ # Defaults every Resource inherits. These are already the values shown;
18
+ # uncomment to change them API-wide, or set them on individual Resources.
19
+ # See https://graphiti.dev/concepts/resources
20
+ #
21
+ # self.attributes_readable_by_default = true
22
+ # self.attributes_writable_by_default = true
23
+ # self.attributes_sortable_by_default = true
24
+ # self.attributes_filterable_by_default = true
25
+ # self.attributes_schema_by_default = true
26
+ # self.relationships_readable_by_default = true
27
+ # self.relationships_writable_by_default = true
28
+ # self.belongs_to_resource_ids_by_default = :foreign_key
29
+ # self.filters_accept_nil_by_default = false
30
+ # self.filters_deny_empty_by_default = false
31
+ # self.default_page_size = 20
32
+ # self.max_page_size = 1_000
33
+ <%- end -%>
15
34
  end