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
data/docs/upgrading.md DELETED
@@ -1,267 +0,0 @@
1
- ---
2
- title: 'Upgrading to Graphiti 2.0'
3
- slug: /upgrading
4
- ---
5
-
6
- # Upgrading to Graphiti 2.0
7
-
8
- Graphiti 2.0 requires **Ruby 3.2+** and **ActiveSupport 7.1+**. Rails is not a dependency, but if you use it, 7.1+. Ruby 3.1 and earlier are past end of life, and Rails 6.1 and 7.0 do not support Ruby 3.2. Apps that cannot move yet should stay on the 1.x branch, which remains open for hotfixes.
9
-
10
- ## What you have to change {#what-you-have-to-change}
11
-
12
- Four things, and three of them fail loudly if you skip them.
13
-
14
- **1. Drop three gems.** `graphiti-rails`, `graphiti_spec_helpers` and `graphiti_errors` are now part of `graphiti` itself.
15
-
16
- ```diff title="Gemfile"
17
- + gem "graphiti", "~> 2.0.0.beta" # follows the betas and picks up 2.0 final when it ships
18
- - gem "graphiti-rails"
19
- - gem "graphiti_spec_helpers"
20
- - gem "graphiti_errors"
21
- ```
22
-
23
- Graphiti raises at load if one is still installed, because they ship files that collide with Graphiti's own, so leaving them in place means load order decides which copy you get.
24
-
25
- **2. Include the Rails integration in your controllers.**
26
-
27
- ```ruby
28
- class ApplicationController < ActionController::Base
29
- include Graphiti::Rails::Controller
30
- end
31
- ```
32
-
33
- If the controller already has `include Graphiti::Rails`, replace it with `include Graphiti::Rails::Controller`.
34
-
35
- <details>
36
- <summary>What the include actually brings, and what a controller without it loses</summary>
37
-
38
- Until 2.0, Graphiti added itself to **every** controller in the application: an `around_action` wrapping each request in a Graphiti context, another wrapping it in the debugger, and a catch-all exception handler, on Devise controllers, admin controllers, HTML pages, everything.
39
-
40
- `Graphiti::Rails::Controller` now bundles all of it, and including it is required. Including it in `ApplicationController` matches 1.x behavior. Including it in an API base class scopes it and leaves the rest of the app alone. A controller without it gets no Graphiti context, no debugger, and none of Graphiti's exception handlers, so if a resource action sees an empty `Graphiti.context`, this include is what is missing.
41
-
42
- The class-level DSL travels with it, which is the one failure you see before a request is ever served:
43
-
44
- ```ruby
45
- class PostsController < ApplicationController
46
- self.sideload_allowlist = {index: [:comments]} # NoMethodError without the include
47
- end
48
- ```
49
-
50
- `sideload_allowlist` comes from `Graphiti::Context`, so a controller that never includes `Graphiti::Rails::Controller` raises `NoMethodError` while the class body is being loaded. Watch for base classes that were given `Graphiti::Rails::Responders` alone. Responders declares formats and nothing else, and does not carry the context.
51
-
52
- `Graphiti::Rails::Responders` is separate and most apps do not need it. It exists for the [`responders`](https://github.com/heartcombo/responders) gem's `respond_with`, and depends on that gem, which is why it is not part of `Graphiti::Rails::Controller`.
53
-
54
- </details>
55
-
56
- **3. Update `around_persistence` hooks**, if you have any.
57
-
58
- They now receive the already-assigned model where they used to receive the attributes hash, so a hook doing `attributes[:tenant_id] = current_tenant.id` raises. Move that to `before_attributes`, or set it on the model.
59
-
60
- <details>
61
- <summary>Before and after, and what else moved with it</summary>
62
-
63
- Attributes are now assigned to the model once, up front, before the persistence hooks run, which is what lets `build` and `find` hand you the model before anything is written. See the [lifecycle hooks guide](/concepts/persisting#persistence-lifecycle-hooks) for what that enables.
64
-
65
- That changes one hook.
66
-
67
- #### around_persistence receives the model, not the attributes hash
68
-
69
- It now wraps the save of an already-assigned model, and gets that model:
70
-
71
- ```ruby
72
- # 1.x
73
- def do_around_persistence(attributes)
74
- attributes[:tenant_id] = current_tenant.id
75
- model = yield
76
- model.log_saved!
77
- end
78
-
79
- # 2.0
80
- def do_around_persistence(model)
81
- model.tenant_id = current_tenant.id # last chance to touch the model before save, inside the transaction
82
- saved = yield
83
- saved.log_saved!
84
- end
85
- ```
86
-
87
- To migrate, move attribute-hash modifications to `before_attributes` (which still receives the mutable hash, before assignment), or set the value on the model as above. Hooks that only wrap their yield, such as transactions, timing and post-save side effects, need no changes. Graphiti 1.x releases warn at runtime when a hook would be affected.
88
-
89
- `before/around/after_attributes` and `before/around/after_save` are unchanged. Custom `create`/`update` adapter overrides keep their 1.x signatures.
90
-
91
- #### Fine print
92
-
93
- - If you inspect the model before saving, the attributes callbacks run at inspection time (in your controller, outside the save transaction). On the plain `save` path they run inside the transaction, at the same point as 1.x.
94
- - Writable guards judge persisted state: a guard asking for the model gets a fresh build/find, never the current request's unsaved changes. A payload cannot influence its own authorization.
95
- - Sideposted child models are still built and assigned during save, and `data` exposes the pre-assigned root model only.
96
-
97
- </details>
98
-
99
- **4. Wrap specs that assert on error payloads.**
100
-
101
- ```ruby
102
- RSpec.configure do |config|
103
- config.include Graphiti::Rails::TestHelpers, type: :request
104
- end
105
-
106
- it "renders a 404" do
107
- handle_request_exceptions { get "/posts/999" }
108
-
109
- expect(response.status).to eq(404)
110
- end
111
- ```
112
-
113
- Exceptions now propagate untouched in tests rather than rendering, so a spec expecting a 404 body sees the exception raised instead. This is the one that breaks the suite that would otherwise have told you the app was fine.
114
-
115
- <details>
116
- <summary>Why it has to be a request spec</summary>
117
-
118
- Exceptions propagate untouched in tests as of 2.0. A spec that asserts on a rendered error payload sees the exception raised instead, so wrap the request in `handle_request_exceptions`. The setup is [step 4 of the migration](/upgrading#what-you-have-to-change).
119
-
120
- It has to be a request spec. Exceptions are rendered in Rack middleware, which controller specs bypass, so the same assertion in a controller spec never sees a rendered payload no matter how it is wrapped.
121
-
122
- `handle_request_exceptions` replaces `GraphitiErrors.enable!` and `.disable!`, which toggled rendering globally. Wrapping the request scopes it to the example instead.
123
-
124
- </details>
125
-
126
- ## Behavior changes to be aware of {#behavior-changes}
127
-
128
- Nothing to do here. These change what a client receives or when a callback runs, and none of them warns you, because none of them is a rename.
129
-
130
- <details>
131
- <summary>A `belongs_to` renders resource ids when its foreign key already holds them, where 1.x sent only a link</summary>
132
-
133
- A `belongs_to` now renders resource ids in the payload by default, where 1.x sent only a link:
134
-
135
- ```json
136
- "employee": { "data": { "type": "employees", "id": "1" }, "links": { "related": "..." } }
137
- ```
138
-
139
- The id comes from the foreign key already on the parent, so this costs no extra queries, and clients can resolve the relationship against data they already hold instead of following the link. `has_many` is unchanged, since answering there means a query per record.
140
-
141
- Not every `belongs_to` qualifies. A remote target or a custom `primary_key` mean the foreign key is not the related id, a polymorphic target means one rendered type cannot cover every record, and a `scope` or `params` block or a `base_scope` mean the key might not survive the filter. Rendering ids for those means loading the association, so they stay opt-in as in 1.x and render nothing until you ask.
142
-
143
- Run [`bin/rake graphiti:audit`](/topics/debugging#graphiti-audit) to see where your API stands: it lists every relationship that renders no ids, and why.
144
-
145
- To go back to the old payload for one relationship:
146
-
147
- ```ruby
148
- belongs_to :employee, resource_ids: false
149
- ```
150
-
151
- Or for the whole API, on the resource everything inherits from:
152
-
153
- ```ruby
154
- class ApplicationResource < Graphiti::Resource
155
- self.abstract_class = true
156
-
157
- self.belongs_to_resource_ids_by_default = :never
158
- end
159
- ```
160
-
161
- If you carry the `Sideload::BelongsTo` monkey patch from [#167](https://github.com/graphiti-api/graphiti/issues/167), delete it and set nothing. The default now covers the safe cases on its own. To force ids onto the rest the way the patch did, set `self.belongs_to_resource_ids_by_default = :always`, at a query per record for each one.
162
-
163
- The three settings, and when a `belongs_to` cannot use its foreign key, are covered in [Customizing Relationships](/concepts/relationships#belongs-to-resource-ids).
164
-
165
- </details>
166
-
167
- <details>
168
- <summary>`ConflictRequest` renders `code: "conflict"` at 409, where `graphiti-rails` surfaced it as a 500</summary>
169
-
170
- Graphiti 1.x shipped two exception systems, `graphiti_errors` in core and `rescue_registry` in `graphiti-rails`, and both loaded in every Rails app. `rescue_registry` is now the only one, and installs automatically as a dependency.
171
-
172
- Graphiti registers handlers for `InvalidRequest` (400), `ConflictRequest` (409), `RecordNotFound` (404), `RemoteWrite` (400) and `SingularSideload` (400), plus a fallback that renders anything else as JSON:API. Register your own on any controller:
173
-
174
- ```ruby
175
- register_exception MyApp::Forbidden, status: 403
176
- register_exception MyApp::Throttled, status: 429, handler: MyApp::ThrottleHandler
177
- ```
178
-
179
- `register_exception` comes from `rescue_registry`, which adds it to every controller, so you do not need `Graphiti::Rails::Controller` to register your own exceptions or to have them rendered. What the include adds is Graphiti's own registrations above, plus the fallback that renders anything unregistered as JSON:API.
180
-
181
- Only formats in `config.graphiti.handled_exception_formats` (default `[:jsonapi]`) are rendered by Graphiti. Everything else falls through to Rails.
182
-
183
- If you subclassed `GraphitiErrors::ExceptionHandler`, note the interface changed with the gem: it is now `build_payload` / `formatted_response` / `status_code`, not `error_payload` / `status_code(error)`.
184
-
185
- Registering and customizing handlers is covered in [Error Handling](/topics/error-handling).
186
-
187
- **Conflicts now report as conflicts.** `Graphiti::Errors::ConflictRequest`, raised when a `PATCH` payload's id does not match the URL, used to render a 409 whose body said `code: "bad_request"`, `title: "Request Error"`. It now says `code: "conflict"`, `title: "Conflict Error"`. Under `graphiti-rails` this exception had no registered handler at all and surfaced as a 500, so for most apps this payload is new rather than changed.
188
-
189
- </details>
190
-
191
- <details>
192
- <summary>`Node#respond_to?` answers `true` for any attribute present in the payload</summary>
193
-
194
- `Node#respond_to?` is now a proper `respond_to_missing?`, so `node.respond_to?(:first_name)` returns `true` for attributes present in the payload where it used to return `false`. Nothing to do unless a spec asserted on the old `false`.
195
-
196
- The node helpers are covered in [#jsonapi_data](/topics/testing#jsonapi-data).
197
-
198
- </details>
199
-
200
- <details>
201
- <summary>Attributes are assigned before the persistence hooks run, so inspecting a model first moves the attributes callbacks outside the save transaction</summary>
202
-
203
- If you inspect the model before saving, the attributes callbacks run at inspection time, in your controller and outside the save transaction. On the plain `save` path they run inside the transaction, at the same point as 1.x.
204
-
205
- The hooks and their order are covered in [Persistence Lifecycle Hooks](/concepts/persisting#persistence-lifecycle-hooks).
206
-
207
- </details>
208
-
209
- ## Deprecations you should fix {#deprecations-you-should-fix}
210
-
211
- Every name below still works, warns, and will be removed in the next major. They're easy fixes though, so why not now?
212
-
213
- | 1.x | 2.0 |
214
- | --- | --- |
215
- | `require "graphiti_spec_helpers/rspec"` | `require "graphiti/spec_helpers/rspec"` |
216
- | `GraphitiSpecHelpers::RSpec` / `::Sugar` / `::Errors::*` | `Graphiti::SpecHelpers::*` |
217
- | `include Graphiti::SpecHelpers::Sugar` (`d`, `included`, `errors`, `dt`) | call `jsonapi_data`, `jsonapi_included`, `jsonapi_errors`, `json_datetime` directly |
218
- | `require "graphiti-rails"` | remove / no longer needed |
219
- | `include Graphiti::Rails` | `include Graphiti::Rails::Controller`|
220
- | `include Graphiti::Responders` | `include Graphiti::Rails::Responders` |
221
- | `jsonapi_context` | `graphiti_context` |
222
- | `GraphitiErrors::Validation::Serializer` | `Graphiti::ErrorSerializers::Validation` |
223
- | `GraphitiErrors::InvalidRequest::Serializer` | `Graphiti::ErrorSerializers::InvalidRequest` |
224
- | `GraphitiErrors::ConflictRequest::Serializer` | `Graphiti::ErrorSerializers::ConflictRequest` |
225
- | rspec shared contexts `"resource testing"`, `"remote api"` | `"graphiti resource testing"`, `"graphiti remote api"` |
226
- | `GraphitiContextProxy` | `Graphiti::SpecHelpers::ContextProxy` |
227
- | `context_namespace` | `current_action` |
228
- | `Graphiti::Rails::DEPRECATOR` | `Graphiti::DEPRECATOR` (the old name still resolves) |
229
- | `require "graphiti_errors"`, `require "graphiti/responders"` | remove / no longer needed |
230
- | `always_include_resource_ids: true` on a relationship | `resource_ids: true` |
231
-
232
- `RSpec.describe PostResource, type: :resource` still picks up the resource-testing context automatically. That has not changed.
233
-
234
- ## Removed outright {#removed-outright}
235
-
236
- | 1.x | 2.0 |
237
- | --- | --- |
238
- | `include GraphitiErrors` | `register_exception` is available on every controller |
239
- | `GraphitiErrors::ExceptionHandler` | subclass `Graphiti::Rails::ExceptionHandler` |
240
- | `GraphitiErrors.enable!` / `.disable!` | `handle_request_exceptions` |
241
- | `self.always_include_resource_ids_by_default` | `self.belongs_to_resource_ids_by_default`, which takes `:foreign_key`, `:always` or `:never` |
242
-
243
- `always_include_resource_ids_by_default` only ever shipped in `2.0.0.beta.4`, so it is gone rather than deprecated and raises `NoMethodError` at class-definition time. It applied to every relationship type, and only a `belongs_to` can render resource ids without loading an association, so the replacement covers `belongs_to` alone. `= false` becomes `:never`. There is no equivalent of `= true`, because arming every collection API-wide is the behavior it was removed for. Use `:always` for `belongs_to`.
244
-
245
- ## Without Rails {#without-rails}
246
-
247
- <details>
248
- <summary>Using the error serializers and exception handling outside Rails</summary>
249
-
250
- The serializers move but keep working: `Graphiti::ErrorSerializers::Validation`, `::InvalidRequest` and `::ConflictRequest` load with core and need no Rails.
251
-
252
- `GraphitiErrors::ExceptionHandler`, which turned any exception into a JSON:API errors payload, is replaced by `RescueRegistry::ExceptionHandler`, a runtime dependency now, and usable outside Rails:
253
-
254
- ```ruby
255
- require "rack" # or RescueRegistry::ExceptionHandler raises NameError on Rack
256
- require "rescue_registry"
257
-
258
- handler = RescueRegistry::ExceptionHandler.new(exception, status: 404)
259
- handler.build_payload # => {errors: [{code: :not_found, status: "404", ...}]}
260
- handler.formatted_response(:json) # => [404, "{\"errors\":[...]}", :json]
261
- ```
262
-
263
- `register_exception` and the rendering are Rails-only, but rescue_registry ships `RescueRegistry::ShowExceptions`, a Rack middleware for exactly this case. See its README.
264
-
265
- `GraphitiErrors.logger` has no replacement. `Graphiti.logger` is the nearest thing.
266
-
267
- </details>
data/website/.gitignore DELETED
@@ -1,20 +0,0 @@
1
- # Dependencies
2
- /node_modules
3
-
4
- # Production
5
- /build
6
-
7
- # Generated files
8
- .docusaurus
9
- .cache-loader
10
-
11
- # Misc
12
- .DS_Store
13
- .env.local
14
- .env.development.local
15
- .env.test.local
16
- .env.production.local
17
-
18
- npm-debug.log*
19
- yarn-debug.log*
20
- yarn-error.log*
data/website/README.md DELETED
@@ -1,43 +0,0 @@
1
- # Website
2
-
3
- This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install
9
- ```
10
-
11
- **Note**: feel free to use the package manager of your choice.
12
-
13
- ## Local Development
14
-
15
- ```bash
16
- npm run start
17
- ```
18
-
19
- This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
20
-
21
- ## Build
22
-
23
- ```bash
24
- npm run build
25
- ```
26
-
27
- This command generates static content into the `build` directory and can be served using any static contents hosting service.
28
-
29
- ## Deployment
30
-
31
- Using SSH:
32
-
33
- ```bash
34
- USE_SSH=true npm run deploy
35
- ```
36
-
37
- Not using SSH:
38
-
39
- ```bash
40
- GIT_USER=<Your GitHub username> npm run deploy
41
- ```
42
-
43
- If you are using GitHub Pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
@@ -1,197 +0,0 @@
1
- // @ts-check
2
- const {themes} = require('prism-react-renderer');
3
-
4
- const codeBlockTheme = (theme, backgroundColor) => ({
5
- ...theme,
6
- plain: {...theme.plain, backgroundColor},
7
- });
8
-
9
- // Docusaurus prepends baseUrl to navbar and Link hrefs, but not to raw HTML
10
- // like announcementBar content, so that markup has to build its own absolute
11
- // paths. Always has a trailing slash.
12
- const baseUrl = process.env.DOCS_BASE_URL || '/';
13
-
14
- // The theme's strikethrough and underline on diff lines are inline styles, so
15
- // CSS cannot undo them. Colour and the +/- signs already carry it.
16
- const withoutDiffDecoration = (theme) => ({
17
- ...theme,
18
- styles: theme.styles.map(({types, style}) =>
19
- types.some((type) => type === 'inserted' || type === 'deleted')
20
- ? {types, style: {...style, textDecorationLine: undefined}}
21
- : {types, style}
22
- ),
23
- });
24
-
25
- /** @type {import('@docusaurus/types').Config} */
26
- const config = {
27
- title: 'Graphiti',
28
- tagline: 'Stylish Graph APIs',
29
- favicon: 'img/favicon.ico',
30
-
31
- // Apex, matching the CNAME. This feeds canonical tags and sitemap.xml, so it
32
- // has to agree with the host actually serving the site or the two split
33
- // search ranking between them.
34
- url: 'https://graphiti.dev',
35
- // graphiti.dev resolves to this repo, so the site serves from the root.
36
- // DOCS_BASE_URL still overrides it for a build served from the project-pages
37
- // path, which is what /graphiti/ was during the beta.
38
- baseUrl,
39
- organizationName: 'graphiti-api',
40
- projectName: 'graphiti',
41
-
42
- onBrokenLinks: 'warn',
43
- markdown: {hooks: {onBrokenMarkdownLinks: 'warn'}},
44
-
45
- presets: [
46
- [
47
- 'classic',
48
- /** @type {import('@docusaurus/preset-classic').Options} */
49
- ({
50
- docs: {
51
- // markdown lives at the repo root, next to the code it documents
52
- path: '../docs',
53
- routeBasePath: '/',
54
- sidebarPath: require.resolve('./sidebars.js'),
55
- editUrl: 'https://github.com/graphiti-api/graphiti/tree/main/website/',
56
- // Unversioned docs serve at the root, and would otherwise be
57
- // labelled "Next" in the dropdown. Once `npm run docusaurus
58
- // docs:version 2.0` cuts a version, that becomes the root and the
59
- // working copy moves to /next.
60
- versions: {current: {label: '2.0 beta'}},
61
- },
62
- blog: false,
63
- theme: {customCss: require.resolve('./src/css/custom.css')},
64
- }),
65
- ],
66
- ],
67
-
68
- plugins: [
69
- [
70
- '@docusaurus/plugin-client-redirects',
71
- {
72
- // The 1.x Jekyll site served /guides/* and /cookbooks/*, and early 2.0
73
- // drafts served /quickstart and /cheatsheet. Keep those URLs alive.
74
- // Nothing under /1.13/ is touched: that is the frozen 1.x site.
75
- redirects: [
76
- {from: ['/quickstart'], to: '/getting-started/first-api'},
77
- {from: ['/cheatsheet', '/guides', '/guides/index'], to: '/'},
78
- {from: ['/guides/overview'], to: '/concepts/overview'},
79
- {from: ['/guides/why'], to: '/reference/why'},
80
- {from: ['/guides/vandal'], to: '/reference/vandal'},
81
- {from: ['/guides/upgrading', '/guides/upgrading-2-0', '/reference/upgrading-2-0', '/upgrading/controllers', '/upgrading/testing', '/upgrading/persistence-hooks', '/upgrading/exception-handling', '/upgrading/belongs-to-linkage', '/upgrading/without-rails'], to: '/upgrading'},
82
- {from: ['/guides/getting-started/installation'], to: '/getting-started/installation'},
83
-
84
- {from: ['/guides/concepts/resources'], to: '/concepts/resources'},
85
- {from: ['/guides/concepts/endpoints'], to: '/concepts/endpoints'},
86
- {from: ['/guides/concepts/links'], to: '/concepts/links'},
87
- {from: ['/guides/concepts/backends-and-models'], to: '/concepts/backends-and-models'},
88
- {from: ['/guides/concepts/testing'], to: '/topics/testing'},
89
- {from: ['/guides/concepts/debugging'], to: '/topics/debugging'},
90
- {from: ['/guides/concepts/error-handling'], to: '/topics/error-handling'},
91
- {from: ['/guides/concepts/remote-resources', '/cookbooks/remote-resources'], to: '/topics/remote-resources'},
92
-
93
- // Cookbook stubs that never had content now point at the real pages
94
- {from: ['/cookbooks/authorization'], to: '/topics/authorization'},
95
- {from: ['/cookbooks/caching'], to: '/topics/caching'},
96
- {from: ['/cookbooks/etags'], to: '/topics/etags'},
97
- {from: ['/cookbooks/json_attributes'], to: '/topics/json-attributes'},
98
- {from: ['/cookbooks/openstruct-models'], to: '/topics/openstruct-models'},
99
- {from: ['/cookbooks/without-activerecord'], to: '/topics/without-activerecord'},
100
- {from: ['/cookbooks/customizing-sideloads'], to: '/topics/customizing-sideloads'},
101
- {from: ['/cookbooks/hopping-relationships'], to: '/topics/hopping-relationships'},
102
-
103
- {from: ['/js/introduction'], to: '/js/'},
104
- {
105
- from: [
106
- '/js/reads/index',
107
- '/js/reads/filtering',
108
- '/js/reads/sorting',
109
- '/js/reads/pagination',
110
- '/js/reads/fieldsets',
111
- '/js/reads/includes',
112
- '/js/reads/nested-queries',
113
- '/js/reads/statistics',
114
- ],
115
- to: '/js/reads',
116
- },
117
- {
118
- from: [
119
- '/js/writes/index',
120
- '/js/writes/validations',
121
- '/js/writes/dirty-tracking',
122
- '/js/writes/nested',
123
- '/js/writes/deferred',
124
- ],
125
- to: '/js/writes',
126
- },
127
- ],
128
-
129
- // The 2.0 docs were staged under /graphiti/ for the whole beta, so
130
- // those URLs are in the wild. createRedirects sees every generated
131
- // route, which the static /1.13/ tree is not: that one is by hand.
132
- createRedirects(existingPath) {
133
- return [`/graphiti${existingPath}`];
134
- },
135
- },
136
- ],
137
- ],
138
-
139
- themeConfig: {
140
- announcementBar: {
141
- // Changing the id un-dismisses the bar for everyone who has closed it.
142
- id: 'graphiti-2-0-beta',
143
- content: `Graphiti 2.0 is in beta, and the docs have been reorganized to describe it. <a href="${baseUrl}1.13/">Docs for 1.x</a>.`,
144
- isCloseable: true,
145
- },
146
- colorMode: {
147
- // Dark for a first-time visitor. Flip respectPrefersColorScheme to true
148
- // to follow the OS setting instead, in which case defaultMode only
149
- // applies when the visitor has no preference either way. Once someone
150
- // uses the navbar toggle their choice is remembered and wins over both.
151
- defaultMode: 'dark',
152
- respectPrefersColorScheme: false,
153
- },
154
- navbar: {
155
- title: 'Graphiti',
156
- logo: {alt: 'Graphiti', src: 'img/logo.png'},
157
- items: [
158
- {
159
- type: 'docsVersionDropdown',
160
- position: 'right',
161
- // 1.x stays as the original Jekyll site, frozen under /1.13.
162
- //
163
- // The pathname:// prefix is required. It is Docusaurus' escape hatch
164
- // for a path that is served statically but is not an app route:
165
- // without it the link renders as a react-router push, which finds no
166
- // match and shows the 404 page even though the file is served fine.
167
- // The prefix is stripped before baseUrl is applied, and it also makes
168
- // the link count as external, hence target '_self' to keep it from
169
- // opening in a new tab.
170
- dropdownItemsAfter: [
171
- {href: 'pathname:///1.13/', label: '1.x', target: '_self'},
172
- ],
173
- },
174
- {href: 'https://github.com/graphiti-api/graphiti', label: 'GitHub', position: 'right'},
175
- {href: 'https://discord.gg/wgqkMBsSRV', label: 'Discord', position: 'right'},
176
- ],
177
- },
178
- footer: {
179
- style: 'dark',
180
- copyright: [
181
- 'Originally created by <a href="https://github.com/richmolj">Lee Richmond</a>,',
182
- '<a href="https://github.com/wadetandy">Wade Tandy</a>, and',
183
- '<a href="https://github.com/wagenet">Peter Wagenet</a>.',
184
- 'Maintained by <a href="https://github.com/jkeen">Jeff Keen</a>',
185
- 'with <a href="https://github.com/graphiti-api/graphiti/graphs/contributors">many contributors</a>.',
186
- '<br />Graphiti is released under the MIT license.',
187
- ].join(' '),
188
- },
189
- prism: {
190
- theme: withoutDiffDecoration(themes.oneLight),
191
- darkTheme: withoutDiffDecoration(codeBlockTheme(themes.oneDark, '#21252b')),
192
- additionalLanguages: ['ruby', 'bash', 'json', 'http', 'diff'],
193
- },
194
- },
195
- };
196
-
197
- module.exports = config;