graphiti 2.0.0.beta.7 → 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 (238) 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 +45 -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 -1
  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/graphiti/audit/report.rb +6 -6
  20. data/lib/graphiti/audit.rb +3 -4
  21. data/lib/graphiti/delegates/pagination.rb +2 -2
  22. data/lib/graphiti/deserializer.rb +3 -3
  23. data/lib/graphiti/errors.rb +6 -6
  24. data/lib/graphiti/extensions/temp_id.rb +1 -1
  25. data/lib/graphiti/query.rb +3 -3
  26. data/lib/graphiti/rails/context.rb +2 -1
  27. data/lib/graphiti/rails/controller.rb +3 -0
  28. data/lib/graphiti/rails/debugging.rb +1 -1
  29. data/lib/graphiti/rails/responders.rb +2 -1
  30. data/lib/graphiti/resource/configuration.rb +1 -0
  31. data/lib/graphiti/resource/dsl.rb +2 -2
  32. data/lib/graphiti/resource/links.rb +1 -1
  33. data/lib/graphiti/resource/persistence.rb +1 -1
  34. data/lib/graphiti/resource/polymorphism.rb +1 -1
  35. data/lib/graphiti/resource.rb +2 -2
  36. data/lib/graphiti/resource_proxy.rb +0 -1
  37. data/lib/graphiti/scoping/filter.rb +5 -3
  38. data/lib/graphiti/scoping/paginate.rb +1 -1
  39. data/lib/graphiti/scoping/sort.rb +1 -1
  40. data/lib/graphiti/sideload/has_many.rb +1 -1
  41. data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
  42. data/lib/graphiti/spec_helpers/matchers.rb +1 -1
  43. data/lib/graphiti/types.rb +5 -1
  44. data/lib/graphiti/util/cache_debug.rb +3 -3
  45. data/lib/graphiti/util/hash.rb +1 -1
  46. data/lib/graphiti/util/persistence.rb +6 -6
  47. data/lib/graphiti/util/serializer_relationships.rb +1 -1
  48. data/lib/graphiti/version.rb +1 -1
  49. data/lib/tasks/graphiti.rake +1 -1
  50. metadata +31 -197
  51. data/docs/concepts/backends-and-models.md +0 -122
  52. data/docs/concepts/endpoints.md +0 -183
  53. data/docs/concepts/links.md +0 -212
  54. data/docs/concepts/overview.md +0 -80
  55. data/docs/concepts/persisting.md +0 -376
  56. data/docs/concepts/relationships.md +0 -627
  57. data/docs/concepts/resources.md +0 -677
  58. data/docs/getting-started/first-api.md +0 -289
  59. data/docs/getting-started/installation.md +0 -185
  60. data/docs/intro.md +0 -307
  61. data/docs/js/authentication.md +0 -63
  62. data/docs/js/ddau.md +0 -20
  63. data/docs/js/extra-params.md +0 -41
  64. data/docs/js/index.md +0 -112
  65. data/docs/js/installation.md +0 -120
  66. data/docs/js/middleware.md +0 -72
  67. data/docs/js/models.md +0 -202
  68. data/docs/js/reads.md +0 -494
  69. data/docs/js/state-syncing.md +0 -100
  70. data/docs/js/writes.md +0 -373
  71. data/docs/reference/vandal.md +0 -63
  72. data/docs/reference/why.md +0 -13
  73. data/docs/topics/authorization.md +0 -155
  74. data/docs/topics/caching.md +0 -55
  75. data/docs/topics/customizing-sideloads.md +0 -156
  76. data/docs/topics/debugging.md +0 -242
  77. data/docs/topics/error-handling.md +0 -210
  78. data/docs/topics/etags.md +0 -46
  79. data/docs/topics/hopping-relationships.md +0 -149
  80. data/docs/topics/json-attributes.md +0 -77
  81. data/docs/topics/openstruct-models.md +0 -50
  82. data/docs/topics/remote-resources.md +0 -291
  83. data/docs/topics/testing.md +0 -916
  84. data/docs/topics/without-activerecord.md +0 -324
  85. data/docs/tutorial/index.md +0 -58
  86. data/docs/tutorial/step_0.md +0 -107
  87. data/docs/tutorial/step_1.md +0 -199
  88. data/docs/tutorial/step_2.md +0 -312
  89. data/docs/tutorial/step_3.md +0 -142
  90. data/docs/tutorial/step_4.md +0 -135
  91. data/docs/tutorial/step_5.md +0 -69
  92. data/docs/tutorial/step_6.md +0 -82
  93. data/docs/tutorial/step_7.md +0 -205
  94. data/docs/tutorial/step_8.md +0 -128
  95. data/docs/tutorial/step_9.md +0 -171
  96. data/docs/upgrading.md +0 -267
  97. data/website/.gitignore +0 -20
  98. data/website/README.md +0 -43
  99. data/website/docusaurus.config.js +0 -197
  100. data/website/package-lock.json +0 -19474
  101. data/website/package.json +0 -46
  102. data/website/sidebars.js +0 -82
  103. data/website/src/css/custom.css +0 -85
  104. data/website/src/pages/markdown-page.mdx +0 -7
  105. data/website/static/.nojekyll +0 -0
  106. data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
  107. data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
  108. data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
  109. data/website/static/1.13/2019/10/14/tutorial.html +0 -198
  110. data/website/static/1.13/CNAME +0 -1
  111. data/website/static/1.13/README.md +0 -16
  112. data/website/static/1.13/assets/css/syntax.css +0 -60
  113. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  114. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  115. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  116. data/website/static/1.13/assets/favicons/browserconfig.xml +0 -9
  117. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  118. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  119. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  120. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  121. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +0 -1
  122. data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
  123. data/website/static/1.13/assets/img/backend.gif +0 -0
  124. data/website/static/1.13/assets/img/conformity.png +0 -0
  125. data/website/static/1.13/assets/img/error_payload.png +0 -0
  126. data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
  127. data/website/static/1.13/assets/img/gh.png +0 -0
  128. data/website/static/1.13/assets/img/home-bg.jpg +0 -0
  129. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  130. data/website/static/1.13/assets/img/logo-500.png +0 -0
  131. data/website/static/1.13/assets/img/logo.png +0 -0
  132. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  133. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  134. data/website/static/1.13/assets/img/persist.jpg +0 -0
  135. data/website/static/1.13/assets/img/resource.gif +0 -0
  136. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  137. data/website/static/1.13/assets/img/rest1.gif +0 -0
  138. data/website/static/1.13/assets/img/rest2.gif +0 -0
  139. data/website/static/1.13/assets/img/rest3.gif +0 -0
  140. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  141. data/website/static/1.13/assets/img/sunrise.png +0 -0
  142. data/website/static/1.13/assets/img/why.png +0 -0
  143. data/website/static/1.13/assets/js/highlight.pack.js +0 -2
  144. data/website/static/1.13/assets/main.css +0 -15518
  145. data/website/static/1.13/assets/main.css.map +0 -1
  146. data/website/static/1.13/bin/bundle +0 -109
  147. data/website/static/1.13/bin/jekyll +0 -27
  148. data/website/static/1.13/bin/kramdown +0 -27
  149. data/website/static/1.13/bin/listen +0 -27
  150. data/website/static/1.13/bin/rake +0 -27
  151. data/website/static/1.13/bin/rougify +0 -27
  152. data/website/static/1.13/bin/safe_yaml +0 -27
  153. data/website/static/1.13/bin/sass +0 -27
  154. data/website/static/1.13/bin/sass-convert +0 -27
  155. data/website/static/1.13/bin/scss +0 -27
  156. data/website/static/1.13/blog.html +0 -259
  157. data/website/static/1.13/cheatsheet.html +0 -316
  158. data/website/static/1.13/cookbooks/authorization.md +0 -0
  159. data/website/static/1.13/cookbooks/caching.md +0 -0
  160. data/website/static/1.13/cookbooks/customizing-sideloads.html +0 -325
  161. data/website/static/1.13/cookbooks/etags.md +0 -0
  162. data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
  163. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  164. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  165. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  166. data/website/static/1.13/cookbooks/without-activerecord.html +0 -510
  167. data/website/static/1.13/features.html +0 -249
  168. data/website/static/1.13/feed.xml +0 -106
  169. data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
  170. data/website/static/1.13/guides/concepts/debugging.html +0 -440
  171. data/website/static/1.13/guides/concepts/endpoints.html +0 -432
  172. data/website/static/1.13/guides/concepts/error-handling.html +0 -396
  173. data/website/static/1.13/guides/concepts/links.html +0 -501
  174. data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
  175. data/website/static/1.13/guides/concepts/resources.html +0 -2176
  176. data/website/static/1.13/guides/concepts/testing.html +0 -1469
  177. data/website/static/1.13/guides/getting-started/installation.html +0 -420
  178. data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
  179. data/website/static/1.13/guides/index.html +0 -269
  180. data/website/static/1.13/guides/overview.html +0 -325
  181. data/website/static/1.13/guides/upgrading-2-0.html +0 -193
  182. data/website/static/1.13/guides/upgrading.html +0 -314
  183. data/website/static/1.13/guides/vandal.html +0 -282
  184. data/website/static/1.13/guides/why.html +0 -1121
  185. data/website/static/1.13/index.html +0 -72
  186. data/website/static/1.13/js/authentication.html +0 -295
  187. data/website/static/1.13/js/ddau.html +0 -238
  188. data/website/static/1.13/js/extra-params.html +0 -270
  189. data/website/static/1.13/js/index.html +0 -321
  190. data/website/static/1.13/js/installation.html +0 -637
  191. data/website/static/1.13/js/introduction.html +0 -257
  192. data/website/static/1.13/js/middleware.html +0 -318
  193. data/website/static/1.13/js/reads/fieldsets.html +0 -271
  194. data/website/static/1.13/js/reads/filtering.html +0 -289
  195. data/website/static/1.13/js/reads/includes.html +0 -260
  196. data/website/static/1.13/js/reads/index.html +0 -497
  197. data/website/static/1.13/js/reads/nested-queries.html +0 -353
  198. data/website/static/1.13/js/reads/pagination.html +0 -260
  199. data/website/static/1.13/js/reads/sorting.html +0 -265
  200. data/website/static/1.13/js/reads/statistics.html +0 -289
  201. data/website/static/1.13/js/state-syncing.html +0 -340
  202. data/website/static/1.13/js/writes/deferred.html +0 -296
  203. data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
  204. data/website/static/1.13/js/writes/index.html +0 -391
  205. data/website/static/1.13/js/writes/nested.html +0 -330
  206. data/website/static/1.13/js/writes/validations.html +0 -272
  207. data/website/static/1.13/quickstart.html +0 -660
  208. data/website/static/1.13/template +0 -161
  209. data/website/static/1.13/tutorial/index.html +0 -250
  210. data/website/static/1.13/tutorial/step_0.html +0 -292
  211. data/website/static/1.13/tutorial/step_1.html +0 -517
  212. data/website/static/1.13/tutorial/step_2.html +0 -481
  213. data/website/static/1.13/tutorial/step_3.html +0 -323
  214. data/website/static/1.13/tutorial/step_4.html +0 -318
  215. data/website/static/1.13/tutorial/step_5.html +0 -265
  216. data/website/static/1.13/tutorial/step_6.html +0 -276
  217. data/website/static/1.13/tutorial/step_7.html +0 -390
  218. data/website/static/1.13/tutorial/step_8.html +0 -316
  219. data/website/static/1.13/tutorial/step_9.html +0 -365
  220. data/website/static/CNAME +0 -1
  221. data/website/static/assets/img/error_payload.png +0 -0
  222. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  223. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  224. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  225. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  226. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  227. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  228. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  229. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  230. data/website/static/assets/img/meta_total_count.png +0 -0
  231. data/website/static/img/docusaurus-social-card.jpg +0 -0
  232. data/website/static/img/docusaurus.png +0 -0
  233. data/website/static/img/favicon.ico +0 -0
  234. data/website/static/img/logo.png +0 -0
  235. data/website/static/img/logo.svg +0 -1
  236. data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
  237. data/website/static/img/undraw_docusaurus_react.svg +0 -170
  238. 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: b5605ecf7bc316cef1342565d7cd54cf7136a98915d669905550dcce32452d7c
4
+ data.tar.gz: 71be03dd2e18fed5485fe154957e8d5366ec21d648cfaf023281277e7d7c5e5a
5
5
  SHA512:
6
- metadata.gz: 22584fd2a83e017219fb8202c965d3d295ac341fed01b2e543355c32722cae741866f3188970ee480018d6063c78b06281045f22934c42b9645de72c6cc9562f
7
- data.tar.gz: 502371f38ad71f5e901b57ba187be70bb4d7c85c94544324e825207bc03f120fa652d0859fe56744efbdbcf826376dad84a4917444c62a7e30193482bca47700
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,26 @@
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
+
3
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)
4
25
 
5
26
 
@@ -101,6 +122,30 @@ GraphitiErrors::Validation::Serializer is now Graphiti::ErrorSerializers::Valida
101
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
102
123
  * Ruby >= 3.0 / Rails >= 6 are now required.
103
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
+
104
149
  # [1.13.0](https://github.com/graphiti-api/graphiti/compare/v1.12.2...v1.13.0) (2026-07-30)
105
150
 
106
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"],
@@ -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
@@ -1,25 +1,43 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= controller_class_name %> < ApplicationController
3
3
  <%- if actions?('index') -%>
4
+ <%- if responders? -%>
4
5
  def index
5
6
  <%= file_name.pluralize %> = <%= resource_klass %>.all(params)
6
- <%- if responders? -%>
7
7
  respond_with(<%= file_name.pluralize %>)
8
- <%- else -%>
9
- render jsonapi: <%= file_name.pluralize %>
10
- <%- end -%>
11
8
  end
9
+ <%- else -%>
10
+ <%- unless omit_comments? -%>
11
+ # With Graphiti::Rails::Responders included, these blocks collapse to
12
+ # respond_with(<%= file_name.pluralize %>). See https://graphiti.dev/getting-started/installation#responders
13
+ <%- end -%>
14
+ def index
15
+ <%= file_name.pluralize %> = <%= resource_klass %>.all(params)
16
+
17
+ respond_to do |format|
18
+ format.jsonapi { render jsonapi: <%= file_name.pluralize %> }
19
+ format.json { render json: <%= file_name.pluralize %> }
20
+ end
21
+ end
22
+ <%- end -%>
12
23
  <%- end -%>
13
24
  <%- if actions?('show') -%>
14
25
 
26
+ <%- if responders? -%>
15
27
  def show
16
28
  <%= file_name %> = <%= resource_klass %>.find(params)
17
- <%- if responders? -%>
18
29
  respond_with(<%= file_name %>)
19
- <%- else -%>
20
- render jsonapi: <%= file_name %>
21
- <%- end -%>
22
30
  end
31
+ <%- else -%>
32
+ def show
33
+ <%= file_name %> = <%= resource_klass %>.find(params)
34
+
35
+ respond_to do |format|
36
+ format.jsonapi { render jsonapi: <%= file_name %> }
37
+ format.json { render json: <%= file_name %> }
38
+ end
39
+ end
40
+ <%- end -%>
23
41
  <%- end -%>
24
42
  <%- if actions?('create') -%>
25
43