graphiti 2.0.0.beta.2 → 2.0.0.beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -86
  3. data/.github/workflows/docs.yml +60 -0
  4. data/.github/workflows/release.yml +8 -8
  5. data/.gitignore +7 -0
  6. data/.npmrc +9 -0
  7. data/.standard.yml +4 -1
  8. data/Appraisals +33 -32
  9. data/CHANGELOG.md +41 -0
  10. data/README.md +13 -2
  11. data/UPGRADING.md +2 -68
  12. data/docs/concepts/backends-and-models.md +122 -0
  13. data/docs/concepts/endpoints.md +183 -0
  14. data/docs/concepts/links.md +212 -0
  15. data/docs/concepts/overview.md +80 -0
  16. data/docs/concepts/persisting.md +376 -0
  17. data/docs/concepts/relationships.md +527 -0
  18. data/docs/concepts/resources.md +677 -0
  19. data/docs/getting-started/first-api.md +289 -0
  20. data/docs/getting-started/installation.md +185 -0
  21. data/docs/intro.md +307 -0
  22. data/docs/js/authentication.md +63 -0
  23. data/docs/js/ddau.md +20 -0
  24. data/docs/js/extra-params.md +41 -0
  25. data/docs/js/index.md +112 -0
  26. data/docs/js/installation.md +120 -0
  27. data/docs/js/middleware.md +72 -0
  28. data/docs/js/models.md +202 -0
  29. data/docs/js/reads.md +494 -0
  30. data/docs/js/state-syncing.md +100 -0
  31. data/docs/js/writes.md +373 -0
  32. data/docs/reference/vandal.md +63 -0
  33. data/docs/reference/why.md +13 -0
  34. data/docs/topics/authorization.md +155 -0
  35. data/docs/topics/caching.md +55 -0
  36. data/docs/topics/customizing-sideloads.md +156 -0
  37. data/docs/topics/debugging.md +216 -0
  38. data/docs/topics/error-handling.md +210 -0
  39. data/docs/topics/etags.md +46 -0
  40. data/docs/topics/hopping-relationships.md +149 -0
  41. data/docs/topics/json-attributes.md +77 -0
  42. data/docs/topics/openstruct-models.md +50 -0
  43. data/docs/topics/remote-resources.md +291 -0
  44. data/docs/topics/testing.md +894 -0
  45. data/docs/topics/without-activerecord.md +324 -0
  46. data/docs/tutorial/index.md +58 -0
  47. data/docs/tutorial/step_0.md +107 -0
  48. data/docs/tutorial/step_1.md +199 -0
  49. data/docs/tutorial/step_2.md +312 -0
  50. data/docs/tutorial/step_3.md +142 -0
  51. data/docs/tutorial/step_4.md +135 -0
  52. data/docs/tutorial/step_5.md +69 -0
  53. data/docs/tutorial/step_6.md +82 -0
  54. data/docs/tutorial/step_7.md +205 -0
  55. data/docs/tutorial/step_8.md +128 -0
  56. data/docs/tutorial/step_9.md +171 -0
  57. data/docs/upgrading.md +265 -0
  58. data/gemfiles/rails_7_1.gemfile +4 -3
  59. data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
  60. data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
  61. data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
  62. data/graphiti.gemspec +7 -5
  63. data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
  64. data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
  65. data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
  66. data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
  67. data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
  68. data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
  69. data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
  70. data/lib/graphiti/configuration.rb +2 -2
  71. data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
  72. data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
  73. data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
  74. data/lib/graphiti/error_serializers/validation.rb +143 -0
  75. data/lib/graphiti/errors.rb +4 -23
  76. data/lib/graphiti/query.rb +1 -1
  77. data/lib/graphiti/rails/context.rb +33 -0
  78. data/lib/graphiti/rails/controller.rb +41 -0
  79. data/lib/graphiti/rails/debugging.rb +18 -0
  80. data/lib/graphiti/rails/exception_handlers.rb +77 -0
  81. data/lib/graphiti/rails/railtie.rb +139 -0
  82. data/lib/graphiti/rails/responders.rb +21 -0
  83. data/lib/graphiti/rails/test_helpers.rb +22 -0
  84. data/lib/graphiti/rails.rb +47 -29
  85. data/lib/graphiti/resource/configuration.rb +1 -0
  86. data/lib/graphiti/resource/interface.rb +2 -2
  87. data/lib/graphiti/resource/persistence.rb +14 -2
  88. data/lib/graphiti/resource/remote.rb +2 -2
  89. data/lib/graphiti/resource/sideloading.rb +1 -1
  90. data/lib/graphiti/resource.rb +13 -1
  91. data/lib/graphiti/responders.rb +7 -20
  92. data/lib/graphiti/schema.rb +5 -1
  93. data/lib/graphiti/schema_diff.rb +4 -0
  94. data/lib/graphiti/scope.rb +45 -37
  95. data/lib/graphiti/serializer.rb +6 -0
  96. data/lib/graphiti/sideload/belongs_to.rb +38 -5
  97. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
  98. data/lib/graphiti/sideload.rb +54 -35
  99. data/lib/graphiti/spec_helpers/errors.rb +73 -0
  100. data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
  101. data/lib/graphiti/spec_helpers/helpers.rb +107 -0
  102. data/lib/graphiti/spec_helpers/node.rb +88 -0
  103. data/lib/graphiti/spec_helpers/rspec.rb +147 -0
  104. data/lib/graphiti/spec_helpers.rb +53 -0
  105. data/lib/graphiti/util/include_params.rb +2 -2
  106. data/lib/graphiti/util/persistence.rb +10 -11
  107. data/lib/graphiti/util/serializer_relationships.rb +41 -5
  108. data/lib/graphiti/version.rb +1 -1
  109. data/lib/graphiti-rails.rb +11 -0
  110. data/lib/graphiti.rb +34 -10
  111. data/lib/graphiti_errors.rb +11 -0
  112. data/lib/graphiti_spec_helpers/rspec.rb +3 -0
  113. data/lib/graphiti_spec_helpers.rb +11 -0
  114. data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
  115. data/package-lock.json +6199 -0
  116. data/package.json +5 -4
  117. data/website/.gitignore +20 -0
  118. data/website/README.md +43 -0
  119. data/website/docusaurus.config.js +141 -0
  120. data/website/package-lock.json +19474 -0
  121. data/website/package.json +46 -0
  122. data/website/sidebars.js +82 -0
  123. data/website/src/css/custom.css +58 -0
  124. data/website/src/pages/markdown-page.mdx +7 -0
  125. data/website/static/.nojekyll +0 -0
  126. data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
  127. data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
  128. data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
  129. data/website/static/1.13/2019/10/14/tutorial.html +198 -0
  130. data/website/static/1.13/CNAME +1 -0
  131. data/website/static/1.13/README.md +16 -0
  132. data/website/static/1.13/assets/css/syntax.css +60 -0
  133. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  134. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  135. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  136. data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
  137. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  138. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  139. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  140. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  141. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
  142. data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
  143. data/website/static/1.13/assets/img/backend.gif +0 -0
  144. data/website/static/1.13/assets/img/conformity.png +0 -0
  145. data/website/static/1.13/assets/img/error_payload.png +0 -0
  146. data/website/static/1.13/assets/img/gh.png +0 -0
  147. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  148. data/website/static/1.13/assets/img/logo-500.png +0 -0
  149. data/website/static/1.13/assets/img/logo.png +0 -0
  150. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  151. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  152. data/website/static/1.13/assets/img/persist.jpg +0 -0
  153. data/website/static/1.13/assets/img/resource.gif +0 -0
  154. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  155. data/website/static/1.13/assets/img/rest1.gif +0 -0
  156. data/website/static/1.13/assets/img/rest2.gif +0 -0
  157. data/website/static/1.13/assets/img/rest3.gif +0 -0
  158. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  159. data/website/static/1.13/assets/img/why.png +0 -0
  160. data/website/static/1.13/assets/js/highlight.pack.js +2 -0
  161. data/website/static/1.13/assets/main.css +15518 -0
  162. data/website/static/1.13/assets/main.css.map +1 -0
  163. data/website/static/1.13/bin/bundle +109 -0
  164. data/website/static/1.13/bin/jekyll +27 -0
  165. data/website/static/1.13/bin/kramdown +27 -0
  166. data/website/static/1.13/bin/listen +27 -0
  167. data/website/static/1.13/bin/rake +27 -0
  168. data/website/static/1.13/bin/rougify +27 -0
  169. data/website/static/1.13/bin/safe_yaml +27 -0
  170. data/website/static/1.13/bin/sass +27 -0
  171. data/website/static/1.13/bin/sass-convert +27 -0
  172. data/website/static/1.13/bin/scss +27 -0
  173. data/website/static/1.13/blog.html +259 -0
  174. data/website/static/1.13/cheatsheet.html +316 -0
  175. data/website/static/1.13/cookbooks/authorization.md +0 -0
  176. data/website/static/1.13/cookbooks/caching.md +0 -0
  177. data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
  178. data/website/static/1.13/cookbooks/etags.md +0 -0
  179. data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
  180. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  181. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  182. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  183. data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
  184. data/website/static/1.13/features.html +249 -0
  185. data/website/static/1.13/feed.xml +106 -0
  186. data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
  187. data/website/static/1.13/guides/concepts/debugging.html +440 -0
  188. data/website/static/1.13/guides/concepts/endpoints.html +432 -0
  189. data/website/static/1.13/guides/concepts/error-handling.html +396 -0
  190. data/website/static/1.13/guides/concepts/links.html +501 -0
  191. data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
  192. data/website/static/1.13/guides/concepts/resources.html +2176 -0
  193. data/website/static/1.13/guides/concepts/testing.html +1469 -0
  194. data/website/static/1.13/guides/getting-started/installation.html +420 -0
  195. data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
  196. data/website/static/1.13/guides/index.html +269 -0
  197. data/website/static/1.13/guides/overview.html +325 -0
  198. data/website/static/1.13/guides/upgrading-2-0.html +193 -0
  199. data/website/static/1.13/guides/upgrading.html +314 -0
  200. data/website/static/1.13/guides/vandal.html +282 -0
  201. data/website/static/1.13/guides/why.html +1121 -0
  202. data/website/static/1.13/index.html +72 -0
  203. data/website/static/1.13/js/authentication.html +295 -0
  204. data/website/static/1.13/js/ddau.html +238 -0
  205. data/website/static/1.13/js/extra-params.html +270 -0
  206. data/website/static/1.13/js/index.html +321 -0
  207. data/website/static/1.13/js/installation.html +637 -0
  208. data/website/static/1.13/js/introduction.html +257 -0
  209. data/website/static/1.13/js/middleware.html +318 -0
  210. data/website/static/1.13/js/reads/fieldsets.html +271 -0
  211. data/website/static/1.13/js/reads/filtering.html +289 -0
  212. data/website/static/1.13/js/reads/includes.html +260 -0
  213. data/website/static/1.13/js/reads/index.html +497 -0
  214. data/website/static/1.13/js/reads/nested-queries.html +353 -0
  215. data/website/static/1.13/js/reads/pagination.html +260 -0
  216. data/website/static/1.13/js/reads/sorting.html +265 -0
  217. data/website/static/1.13/js/reads/statistics.html +289 -0
  218. data/website/static/1.13/js/state-syncing.html +340 -0
  219. data/website/static/1.13/js/writes/deferred.html +296 -0
  220. data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
  221. data/website/static/1.13/js/writes/index.html +391 -0
  222. data/website/static/1.13/js/writes/nested.html +330 -0
  223. data/website/static/1.13/js/writes/validations.html +272 -0
  224. data/website/static/1.13/quickstart.html +660 -0
  225. data/website/static/1.13/template +161 -0
  226. data/website/static/1.13/tutorial/index.html +250 -0
  227. data/website/static/1.13/tutorial/step_0.html +292 -0
  228. data/website/static/1.13/tutorial/step_1.html +517 -0
  229. data/website/static/1.13/tutorial/step_2.html +481 -0
  230. data/website/static/1.13/tutorial/step_3.html +323 -0
  231. data/website/static/1.13/tutorial/step_4.html +318 -0
  232. data/website/static/1.13/tutorial/step_5.html +265 -0
  233. data/website/static/1.13/tutorial/step_6.html +276 -0
  234. data/website/static/1.13/tutorial/step_7.html +390 -0
  235. data/website/static/1.13/tutorial/step_8.html +316 -0
  236. data/website/static/1.13/tutorial/step_9.html +365 -0
  237. data/website/static/assets/img/error_payload.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  239. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  240. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  242. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  243. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  244. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  245. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  246. data/website/static/assets/img/meta_total_count.png +0 -0
  247. data/website/static/img/docusaurus-social-card.jpg +0 -0
  248. data/website/static/img/docusaurus.png +0 -0
  249. data/website/static/img/favicon.ico +0 -0
  250. data/website/static/img/logo.png +0 -0
  251. data/website/static/img/logo.svg +1 -0
  252. data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
  253. data/website/static/img/undraw_docusaurus_react.svg +170 -0
  254. data/website/static/img/undraw_docusaurus_tree.svg +40 -0
  255. metadata +245 -46
  256. data/gemfiles/rails_6.gemfile +0 -18
  257. data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
  258. data/gemfiles/rails_7.gemfile +0 -18
  259. data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
  260. data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
  261. data/lib/graphiti/railtie.rb +0 -121
  262. /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
  263. /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
  264. /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
  265. /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
  266. /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
  267. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
  268. /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
  269. /data/{deprecated_generators → lib/generators}/graphiti/templates/update_request_spec.rb.erb +0 -0
@@ -0,0 +1,2176 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css?family=Boogaloo" rel="stylesheet">
10
+
11
+ <link rel="apple-touch-icon" sizes="180x180" href="/1.13/assets/favicons/apple-touch-icon.png">
12
+ <link rel="icon" type="image/png" sizes="32x32" href="/1.13/assets/favicons/favicon-32x32.png">
13
+ <link rel="icon" type="image/png" sizes="16x16" href="/1.13/assets/favicons/favicon-16x16.png">
14
+ <link rel="manifest" href="/1.13/assets/favicons/site.webmanifest">
15
+ <link rel="mask-icon" href="/1.13/assets/favicons/safari-pinned-tab.svg" color="#F86DA7">
16
+ <link rel="shortcut icon" href="/1.13/assets/favicons/favicon.ico">
17
+ <meta name="msapplication-TileColor" content="#F86DA7">
18
+ <meta name="msapplication-config" content="/1.13/assets/favicons/browserconfig.xml">
19
+ <meta name="theme-color" content="#F86DA7">
20
+
21
+ <title>Graphiti</title>
22
+ <meta name="description" content="Stylish Graph APIs">
23
+
24
+ <link rel="stylesheet" href="/1.13/assets/main.css?ref=wh4t3v45">
25
+ <link rel="alternate" type="application/rss+xml" title="Graphiti" href="/1.13/feed.xml">
26
+
27
+ <!-- Global site tag (gtag.js) - Google Analytics -->
28
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-127904727-1"></script>
29
+ <script>
30
+ window.dataLayer = window.dataLayer || [];
31
+ function gtag(){dataLayer.push(arguments);}
32
+ gtag('js', new Date());
33
+
34
+ gtag('config', 'UA-127904727-1');
35
+ </script>
36
+
37
+ <!-- javascript -->
38
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
39
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
40
+
41
+
42
+ </head>
43
+
44
+
45
+ <body>
46
+ <main class="page-content" aria-label="Content">
47
+ <div class="wrapper">
48
+ <header class="navbar navbar-inverse normal" role="banner">
49
+ <div class="container">
50
+ <div class="navbar-header">
51
+ <a href="/1.13/1.13" class="navbar-brand">
52
+ <img alt="logo" src="/1.13/assets/img/logo.png">
53
+ </a>
54
+ </div>
55
+ <nav class="" role="navigation">
56
+ <ul class="nav navbar-nav nav-links">
57
+ <li>
58
+ <a class="quickstart nav-link" href="/1.13/quickstart">Quickstart</a>
59
+ </li>
60
+ <li>
61
+ <a class="guides nav-link" href="/1.13/guides">Guides</a>
62
+ </li>
63
+ <li>
64
+ <a class="tutorial nav-link" href="/1.13/tutorial">Tutorial</a>
65
+ </li>
66
+ <li>
67
+ <a class="spraypaint nav-link" href="/1.13/js">Spraypaint</a>
68
+ </li>
69
+ </ul>
70
+ <ul class="nav gh navbar-nav navbar-right visible-lg visible-md">
71
+ <li>
72
+ <span class="star">⭐</span>
73
+ <a href="https://github.com/graphiti-api/graphiti">
74
+ <img alt="github-star" style="margin-right: 65px;margin-top: -20px" src="/1.13/assets/img/gh.png">
75
+ </a>
76
+ </li>
77
+ </ul>
78
+ </nav>
79
+ </div>
80
+ </header>
81
+
82
+ <div class="container">
83
+ <div class="toc col-md-3">
84
+ <h1 id="resources">Resources</h1>
85
+
86
+ <ul>
87
+ <li>1 <a href="#overview">Overview</a></li>
88
+ <li>2 <a href="#attributes">Attributes</a>
89
+ <ul>
90
+ <li><a href="#limiting-behavior">Limiting Behavior</a></li>
91
+ <li><a href="#default-behavior">Default Behavior</a></li>
92
+ <li><a href="#customizing-display">Customizing Display</a></li>
93
+ <li><a href="#types">Types</a></li>
94
+ <li><a href="#enum-types">Enum Types</a></li>
95
+ <li><a href="#custom-types">Custom Types</a></li>
96
+ </ul>
97
+ </li>
98
+ <li>3 <a href="#querying">Querying</a>
99
+ <ul>
100
+ <li><a href="#query-interface">Query Interface</a></li>
101
+ <li><a href="#composing-with-scopes">Composing with Scopes</a></li>
102
+ <li><a href="#base-scope"><code class="language-plaintext highlighter-rouge">#base_scope</code></a></li>
103
+ <li><a href="#sort">Sort</a>
104
+ <ul>
105
+ <li><a href="#sort-options">Sort Options</a></li>
106
+ </ul>
107
+ </li>
108
+ <li><a href="#filter">Filter</a>
109
+ <ul>
110
+ <li><a href="#filter-options">Filter Options</a></li>
111
+ <li><a href="#boolean-filter">Boolean Filter</a></li>
112
+ <li><a href="#hash-filter">Hash Filter</a></li>
113
+ <li><a href="#escaping-values">Escaping Values</a></li>
114
+ </ul>
115
+ </li>
116
+ <li><a href="#statistics">Statistics</a></li>
117
+ <li><a href="#extra-fields">Extra Fields</a></li>
118
+ <li><a href="#resolve"><code class="language-plaintext highlighter-rouge">#resolve</code></a></li>
119
+ </ul>
120
+ </li>
121
+ <li>4 <a href="#configuration">Configuration</a>
122
+ <ul>
123
+ <li><a href="#polymorphic-resources">Polymorphic Resources</a></li>
124
+ </ul>
125
+ </li>
126
+ <li>5 <a href="#relationships">Relationships</a>
127
+ <ul>
128
+ <li><a href="#deep-queries">Deep Queries</a></li>
129
+ <li><a href="#customizing-relationships">Customizing Relationships</a>
130
+ <ul>
131
+ <li><a href="#conditional-relationships">Conditional Relationships</a></li>
132
+ <li><a href="#customizing-scope">Customizing Scope</a></li>
133
+ <li><a href="#customizing-assignment">Customizing Assignment</a></li>
134
+ </ul>
135
+ </li>
136
+ <li><a href="#has-many">has_many</a></li>
137
+ <li><a href="#belongs-to">belongs_to</a></li>
138
+ <li><a href="#has-one">has_one</a>
139
+ <ul>
140
+ <li><a href="#faux-has-one">Faux has_one</a></li>
141
+ </ul>
142
+ </li>
143
+ <li><a href="#many-to-many">many_to_many</a></li>
144
+ <li><a href="#polymorphic-belongs-to">polymorphic_belongs_to</a></li>
145
+ <li><a href="#polymorphic-has-many">polymorphic_has_many</a></li>
146
+ </ul>
147
+ </li>
148
+ <li>6 <a href="#generators">Generators</a></li>
149
+ <li>7 <a href="#persisting">Persisting</a>
150
+ <ul>
151
+ <li><a href="#persistence-lifecycle-hooks">Lifecycle Hooks</a></li>
152
+ <li><a href="#sideposting">Sideposting</a>
153
+ <ul>
154
+ <li><a href="#create">Create</a></li>
155
+ <li><a href="#expanded-example">Expanded Example</a></li>
156
+ </ul>
157
+ </li>
158
+ <li><a href="#validation-errors">Validation Errors</a></li>
159
+ </ul>
160
+ </li>
161
+ <li>8 <a href="#context">Context</a></li>
162
+ <li>9 <a href="#concurrency">Concurrency</a></li>
163
+ <li>10 <a href="#adapters">Adapters</a></li>
164
+ </ul>
165
+ </div>
166
+
167
+ <div class="col-md-8">
168
+
169
+ <a class="anchor" id="overview" />
170
+ <a class="header" href="#overview">
171
+ <h2>
172
+ 1 Overview
173
+ </h2>
174
+ </a>
175
+
176
+ <p align="center">
177
+ <img width="100%" src="/1.13/assets/img/rest3.gif" />
178
+ </p>
179
+
180
+ <p>The same way a <code class="language-plaintext highlighter-rouge">Model</code> is an abstraction around a database table, a
181
+ <code class="language-plaintext highlighter-rouge">Resource</code> is an abstraction around an API endpoint. It holds logic for
182
+ <strong><em>querying</em></strong>, <strong><em>persisting</em></strong>, and <strong><em>serializing</em></strong> data.</p>
183
+
184
+ <blockquote>
185
+ <p>For a condensed view of the Resource interface, see the
186
+ <a href="/1.13/cheatsheet">cheatsheet</a>.</p>
187
+ </blockquote>
188
+
189
+ <a class="anchor" id="attributes" />
190
+ <a class="header" href="#attributes">
191
+ <h2>
192
+ 2 Attributes
193
+ </h2>
194
+ </a>
195
+
196
+ <p>A <strong>Resource</strong> is composed of <strong>Attribute</strong>s. Each Attribute has a
197
+ <strong>name</strong> (e.g. <code class="language-plaintext highlighter-rouge">first_name</code>) that corresponds to a JSON key, and a
198
+ <strong>Type</strong> (e.g. <code class="language-plaintext highlighter-rouge">string</code>) that corresponds to a JSON value.</p>
199
+
200
+ <p>To define an attribute:</p>
201
+
202
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:first_name</span><span class="p">,</span> <span class="ss">:string</span></code></pre></figure>
203
+
204
+ <a class="anchor" id="limiting-behavior" />
205
+ <a class="header" href="#limiting-behavior">
206
+ <h4>
207
+ 2.1 Limiting Behavior
208
+ </h4>
209
+ </a>
210
+
211
+ <p>Each attribute consists of five flags: <code class="language-plaintext highlighter-rouge">readable</code>, <code class="language-plaintext highlighter-rouge">writable</code>,
212
+ <code class="language-plaintext highlighter-rouge">sortable</code>, <code class="language-plaintext highlighter-rouge">filterable</code>, and <code class="language-plaintext highlighter-rouge">schema</code>. Any of these flags can be turned off:</p>
213
+
214
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">sortable: </span><span class="kp">false</span></code></pre></figure>
215
+
216
+ <p>Or use <code class="language-plaintext highlighter-rouge">only/except</code> shorthand:</p>
217
+
218
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:sortable</span><span class="p">]</span>
219
+ <span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">except: </span><span class="p">[</span><span class="ss">:writable</span><span class="p">]</span></code></pre></figure>
220
+
221
+ <p>The <code class="language-plaintext highlighter-rouge">schema</code> flag is not affected by <code class="language-plaintext highlighter-rouge">only/except</code> options.
222
+ This option determines if the attribute is exported to the schema.json.</p>
223
+
224
+ <p>You might want to allow behavior only if a certain condition is met.
225
+ Pass a symbol to guard this behavior via corresponding method, only allowing the
226
+ behavior if the method returns <code class="language-plaintext highlighter-rouge">true</code>:</p>
227
+
228
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">writable: :admin?</span>
229
+
230
+ <span class="k">def</span> <span class="nf">admin?</span>
231
+ <span class="c1"># ... logic ...</span>
232
+ <span class="k">end</span></code></pre></figure>
233
+
234
+ <p>When guarding the <code class="language-plaintext highlighter-rouge">:readable</code> flag, the method can optionally accept the
235
+ model instance and the name of the attribute being serialized as arguments:</p>
236
+
237
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">readable: :allowed?</span>
238
+ <span class="n">attribute</span> <span class="ss">:age</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">readable: :attribute_allowed?</span>
239
+
240
+ <span class="k">def</span> <span class="nf">allowed?</span><span class="p">(</span><span class="n">model_instance</span><span class="p">)</span>
241
+ <span class="n">model_instance</span><span class="p">.</span><span class="nf">internal</span> <span class="o">==</span> <span class="kp">false</span>
242
+ <span class="k">end</span>
243
+
244
+ <span class="k">def</span> <span class="nf">attribute_allowed?</span><span class="p">(</span><span class="n">model_instance</span><span class="p">,</span> <span class="n">attribute_name</span><span class="p">)</span>
245
+ <span class="no">PolicyChecker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">model_instance</span><span class="p">).</span><span class="nf">attribute_readable?</span><span class="p">(</span><span class="n">attribute_name</span><span class="p">)</span>
246
+ <span class="k">end</span></code></pre></figure>
247
+
248
+ <p><code class="language-plaintext highlighter-rouge">:writable</code> guards mirror this: the guard method (or proc) can optionally accept the model being written and the name of the attribute. On an update, the model is the persisted record being modified; on a create, it is a new unsaved instance. The model is only looked up when a guard actually declares a parameter for it, so zero-argument guards behave exactly as before.</p>
249
+
250
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:salary</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">writable: :salary_writable?</span>
251
+
252
+ <span class="k">def</span> <span class="nf">salary_writable?</span><span class="p">(</span><span class="n">model_instance</span><span class="p">,</span> <span class="n">attribute_name</span><span class="p">)</span>
253
+ <span class="no">PolicyChecker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">model_instance</span><span class="p">).</span><span class="nf">attribute_writable?</span><span class="p">(</span><span class="n">attribute_name</span><span class="p">)</span>
254
+ <span class="k">end</span></code></pre></figure>
255
+
256
+ <p>If the guard returns <code class="language-plaintext highlighter-rouge">false</code>, the request is rejected with an <code class="language-plaintext highlighter-rouge">unwritable_attribute</code> validation error before anything is persisted.</p>
257
+
258
+ <a class="anchor" id="default-behavior" />
259
+ <a class="header" href="#default-behavior">
260
+ <h4>
261
+ 2.2 Default Behavior
262
+ </h4>
263
+ </a>
264
+
265
+ <p>By default, attributes are enabled for all behavior. You may want to
266
+ disable certain behavior globally, for example a read-only API. Use
267
+ these properties to affect all subclasses:</p>
268
+
269
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">self</span><span class="p">.</span><span class="nf">attributes_readable_by_default</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># default true</span>
270
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_writable_by_default</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># default true</span>
271
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_filterable_by_default</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># default true</span>
272
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_sortable_by_default</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># default true</span>
273
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_schema_by_default</span> <span class="o">=</span> <span class="kp">false</span> <span class="c1"># default true</span></code></pre></figure>
274
+
275
+ <p>As for resource defined guards, you can pass a symbol to guard the
276
+ behavior globally. This can be used to globally delegate access control to a
277
+ dedicated system.</p>
278
+
279
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">self</span><span class="p">.</span><span class="nf">attributes_readable_by_default</span> <span class="o">=</span> <span class="ss">:attribute_readable?</span> <span class="c1"># default true</span>
280
+
281
+ <span class="k">def</span> <span class="nf">attribute_readable?</span><span class="p">(</span><span class="n">model_instance</span><span class="p">,</span> <span class="n">attribute_name</span><span class="p">)</span>
282
+ <span class="no">PolicyChecker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">model_instance</span><span class="p">).</span><span class="nf">attribute_readable?</span><span class="p">(</span><span class="n">attribute_name</span><span class="p">)</span>
283
+ <span class="k">end</span></code></pre></figure>
284
+
285
+ <a class="anchor" id="customizing-display" />
286
+ <a class="header" href="#customizing-display">
287
+ <h4>
288
+ 2.3 Customizing Display
289
+ </h4>
290
+ </a>
291
+
292
+ <p>Pass a block to <code class="language-plaintext highlighter-rouge">attribute</code> to customize display:</p>
293
+
294
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
295
+ <span class="vi">@object</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">upcase</span>
296
+ <span class="k">end</span></code></pre></figure>
297
+
298
+ <p><code class="language-plaintext highlighter-rouge">@object</code> will be an instance of your model.</p>
299
+
300
+ <a class="anchor" id="types" />
301
+ <a class="header" href="#types">
302
+ <h4>
303
+ 2.4 Types
304
+ </h4>
305
+ </a>
306
+
307
+ <p>Each <strong>Attribute</strong> has a <strong>Type</strong>. Each <strong>Type</strong> defines behavior for</p>
308
+
309
+ <ul>
310
+ <li>Reading</li>
311
+ <li>Writing</li>
312
+ <li>Filtering</li>
313
+ </ul>
314
+
315
+ <p>For each of these, we’ll first attempt to <em>coerce</em> the given value to
316
+ the correct type. If that fails, we will raise an error.</p>
317
+
318
+ <p>The implementation for each of these actions lives in a <a href="https://dry-rb.org/gems/dry-types">Dry Type</a>. Take the <code class="language-plaintext highlighter-rouge">:integer_id</code> type: here we want to <em>render</em> a string, but <em>query</em> with an integer (this is the default for all Resource <code class="language-plaintext highlighter-rouge">id</code> attributes):</p>
319
+
320
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Graphiti</span><span class="o">::</span><span class="no">Types</span><span class="p">[</span><span class="ss">:integer_id</span><span class="p">]</span>
321
+
322
+ <span class="c1"># {</span>
323
+ <span class="c1"># params: Dry::Types['coercible.integer'],</span>
324
+ <span class="c1"># read: Dry::Types['coercible.string'],</span>
325
+ <span class="c1"># write: Dry::Types['coercible.integer'],</span>
326
+ <span class="c1"># ...</span>
327
+ <span class="c1"># }</span></code></pre></figure>
328
+
329
+ <p>You can edit these implementations as you wish. Let’s make the <code class="language-plaintext highlighter-rouge">:string</code> type
330
+ render an integer:</p>
331
+
332
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Graphiti</span><span class="o">::</span><span class="no">Types</span><span class="p">[</span><span class="ss">:string</span><span class="p">][</span><span class="ss">:read</span><span class="p">]</span> <span class="o">=</span> <span class="no">Dry</span><span class="o">::</span><span class="no">Types</span><span class="p">[</span><span class="s1">'coercible.integer'</span><span class="p">]</span></code></pre></figure>
333
+
334
+ <p>The built-in Types are:</p>
335
+
336
+ <ul>
337
+ <li><code class="language-plaintext highlighter-rouge">integer_id</code></li>
338
+ <li><code class="language-plaintext highlighter-rouge">string</code></li>
339
+ <li><code class="language-plaintext highlighter-rouge">integer</code></li>
340
+ <li><code class="language-plaintext highlighter-rouge">big_decimal</code></li>
341
+ <li><code class="language-plaintext highlighter-rouge">float</code></li>
342
+ <li><code class="language-plaintext highlighter-rouge">date</code></li>
343
+ <li><code class="language-plaintext highlighter-rouge">datetime</code></li>
344
+ <li><code class="language-plaintext highlighter-rouge">uuid</code></li>
345
+ <li><code class="language-plaintext highlighter-rouge">string_enum</code></li>
346
+ <li><code class="language-plaintext highlighter-rouge">integer_enum</code></li>
347
+ <li><code class="language-plaintext highlighter-rouge">boolean</code></li>
348
+ <li><code class="language-plaintext highlighter-rouge">hash</code></li>
349
+ <li><code class="language-plaintext highlighter-rouge">array</code></li>
350
+ </ul>
351
+
352
+ <p>All but the last 3 have Array doppelgängers: <code class="language-plaintext highlighter-rouge">array_of_integers</code>,
353
+ <code class="language-plaintext highlighter-rouge">array_of_dates</code>, etc.</p>
354
+
355
+ <p>The <code class="language-plaintext highlighter-rouge">integer_id</code> type says, “render as a string, but query as an
356
+ integer” and is the default for the <code class="language-plaintext highlighter-rouge">id</code> attribute. The <code class="language-plaintext highlighter-rouge">uuid</code> type says
357
+ “this is a string, but query me case-sensitive by default”.</p>
358
+
359
+ <a class="anchor" id="enum-types" />
360
+ <a class="header" href="#enum-types">
361
+ <h5>
362
+ 2.5 Enum Types
363
+ </h5>
364
+ </a>
365
+
366
+ <p>Graphiti provides two built enum types, <code class="language-plaintext highlighter-rouge">string_enum</code> and <code class="language-plaintext highlighter-rouge">integer_enum</code>. These behave
367
+ in exactly the same way as the <code class="language-plaintext highlighter-rouge">string</code> and <code class="language-plaintext highlighter-rouge">integer</code> types, respectively, except that
368
+ when declaring them as either an attribute or a filter you are required to
369
+ pass the <code class="language-plaintext highlighter-rouge">allow</code> option, which is the list of acceptable values for the field:</p>
370
+
371
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:status</span><span class="p">,</span> <span class="ss">:string_enum</span><span class="p">,</span> <span class="ss">allow: </span><span class="p">[</span><span class="s1">'draft'</span><span class="p">,</span> <span class="s1">'published'</span><span class="p">]</span></code></pre></figure>
372
+
373
+ <p>Or if your attribute is backed by an ActiveRecord, you could reference
374
+ the values directly</p>
375
+
376
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/models/post.rb</span>
377
+ <span class="k">class</span> <span class="nc">Post</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
378
+ <span class="n">enum</span> <span class="ss">status: </span><span class="p">{</span>
379
+ <span class="ss">draft: </span><span class="mi">0</span><span class="p">,</span>
380
+ <span class="ss">published: </span><span class="mi">1</span>
381
+ <span class="p">}</span>
382
+ <span class="k">end</span>
383
+
384
+ <span class="c1"># app/resources/post_resource.rb</span>
385
+ <span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
386
+ <span class="n">attribute</span> <span class="ss">:status</span><span class="p">,</span> <span class="ss">:string_enum</span><span class="p">,</span> <span class="ss">allow: </span><span class="no">Post</span><span class="p">.</span><span class="nf">statuses</span><span class="p">.</span><span class="nf">keys</span>
387
+ <span class="k">end</span></code></pre></figure>
388
+
389
+ <p>See the section on <a href="#filter-options">filter options</a> for more details on <code class="language-plaintext highlighter-rouge">allow</code> behavior</p>
390
+
391
+ <p><strong>Note</strong>: Graphiti does not currently do any value checking on enum fields when
392
+ writing an attribute, and it still expects that your model layer will validate any
393
+ data coming in.</p>
394
+
395
+ <a class="anchor" id="custom-types" />
396
+ <a class="header" href="#custom-types">
397
+ <h5>
398
+ 2.6 Custom Types
399
+ </h5>
400
+ </a>
401
+
402
+ <p><a href="https://dry-rb.org/gems/dry-types/main/custom-types/">Dry Types supports custom types</a>. Let’s register a “capital letters” type:</p>
403
+
404
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Define the Type</span>
405
+ <span class="n">definition</span> <span class="o">=</span> <span class="no">Dry</span><span class="o">::</span><span class="no">Types</span><span class="o">::</span><span class="no">Nominal</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">String</span><span class="p">)</span>
406
+ <span class="n">type</span> <span class="o">=</span> <span class="n">definition</span><span class="p">.</span><span class="nf">constructor</span> <span class="k">do</span> <span class="o">|</span><span class="n">input</span><span class="o">|</span>
407
+ <span class="n">input</span><span class="p">.</span><span class="nf">upcase</span>
408
+ <span class="k">end</span>
409
+
410
+ <span class="c1"># Register it with Graphiti</span>
411
+ <span class="no">Graphiti</span><span class="o">::</span><span class="no">Types</span><span class="p">[</span><span class="ss">:caps_lock</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
412
+ <span class="ss">params: </span><span class="n">type</span><span class="p">,</span>
413
+ <span class="ss">read: </span><span class="n">type</span><span class="p">,</span>
414
+ <span class="ss">write: </span><span class="n">type</span><span class="p">,</span>
415
+ <span class="ss">kind: </span><span class="s1">'scalar'</span><span class="p">,</span>
416
+ <span class="ss">canonical_name: :caps_lock</span><span class="p">,</span>
417
+ <span class="ss">description: </span><span class="s1">'All capital letters'</span>
418
+ <span class="p">}</span>
419
+
420
+ <span class="c1"># Use in a Resource</span>
421
+ <span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:caps_lock</span></code></pre></figure>
422
+
423
+ <a class="anchor" id="querying" />
424
+ <a class="header" href="#querying">
425
+ <h2>
426
+ 3 Querying
427
+ </h2>
428
+ </a>
429
+
430
+ <p>Resources must be able to dynamically compose a query that can be run
431
+ against an arbitrary backend (SQL, NoSQL, service calls, etc). They do
432
+ this through the concept of <strong>scoping</strong>.</p>
433
+
434
+ <p>The best way to understand scoping is to take a look at what happens “under the hood”. Here’s the simple Resource, where most of the logic is hiding in the Adapter:</p>
435
+
436
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
437
+ <span class="n">attribute</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span>
438
+ <span class="k">end</span></code></pre></figure>
439
+
440
+ <p>Now let’s show the long-hand version. This is completely runnable code (we’re just overriding the default behavior with an explicit version of the same):</p>
441
+
442
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
443
+ <span class="n">filter</span> <span class="ss">:title</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
444
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
445
+ <span class="n">scope</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">title: </span><span class="n">value</span><span class="p">)</span>
446
+ <span class="k">end</span>
447
+ <span class="k">end</span>
448
+
449
+ <span class="n">sort</span> <span class="ss">:title</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">dir</span><span class="o">|</span>
450
+ <span class="n">scope</span><span class="p">.</span><span class="nf">order</span><span class="p">(</span><span class="ss">title: </span><span class="n">dir</span><span class="p">)</span>
451
+ <span class="k">end</span>
452
+
453
+ <span class="n">paginate</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">current_page</span><span class="p">,</span> <span class="n">per_page</span><span class="o">|</span>
454
+ <span class="n">scope</span><span class="p">.</span><span class="nf">page</span><span class="p">(</span><span class="n">current_page</span><span class="p">).</span><span class="nf">per</span><span class="p">(</span><span class="n">per_page</span><span class="p">)</span>
455
+ <span class="k">end</span>
456
+
457
+ <span class="k">def</span> <span class="nf">base_scope</span>
458
+ <span class="no">Post</span><span class="p">.</span><span class="nf">all</span>
459
+ <span class="k">end</span>
460
+
461
+ <span class="k">def</span> <span class="nf">resolve</span><span class="p">(</span><span class="n">scope</span><span class="p">)</span>
462
+ <span class="n">scope</span><span class="p">.</span><span class="nf">to_a</span>
463
+ <span class="k">end</span>
464
+ <span class="k">end</span></code></pre></figure>
465
+
466
+ <p>Let’s break this down the key elements:</p>
467
+
468
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">base_scope</span>
469
+ <span class="no">Post</span><span class="p">.</span><span class="nf">all</span>
470
+ <span class="k">end</span></code></pre></figure>
471
+
472
+ <p>Graphiti builds queries just like ActiveRecord: start with a base scope (<code class="language-plaintext highlighter-rouge">Post.all</code>), and alter that scope based on the incoming request. <code class="language-plaintext highlighter-rouge">#base_scope</code> defines our starting point.</p>
473
+
474
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">filter</span> <span class="ss">:title</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
475
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
476
+ <span class="n">scope</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">title: </span><span class="n">value</span><span class="p">)</span>
477
+ <span class="k">end</span>
478
+ <span class="k">end</span></code></pre></figure>
479
+
480
+ <p>When the <code class="language-plaintext highlighter-rouge">title</code> query parameter is present, we alter the scope.</p>
481
+
482
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">resolve</span><span class="p">(</span><span class="n">scope</span><span class="p">)</span>
483
+ <span class="n">scope</span><span class="p">.</span><span class="nf">to_a</span>
484
+ <span class="k">end</span></code></pre></figure>
485
+
486
+ <p>The <code class="language-plaintext highlighter-rouge">#resolve</code> method is in charge of actually executing the query
487
+ and returning model instances.</p>
488
+
489
+ <p>In other words, this code is roughly equivalent to:</p>
490
+
491
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">scope</span> <span class="o">=</span> <span class="no">Post</span><span class="p">.</span><span class="nf">all</span> <span class="c1"># #base_scope</span>
492
+ <span class="k">if</span> <span class="n">value</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:filter</span><span class="p">].</span><span class="nf">try</span><span class="p">(</span><span class="ss">:[]</span><span class="p">,</span> <span class="ss">:title</span><span class="p">)</span>
493
+ <span class="n">scope</span> <span class="o">=</span> <span class="n">scope</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">title: </span><span class="n">value</span><span class="p">)</span> <span class="c1"># .filter</span>
494
+ <span class="k">end</span>
495
+ <span class="n">scope</span><span class="p">.</span><span class="nf">to_a</span> <span class="c1"># #resolve</span></code></pre></figure>
496
+
497
+ <a class="anchor" id="query-interface" />
498
+ <a class="header" href="#query-interface">
499
+ <h3>
500
+ 3.1 Query Interface
501
+ </h3>
502
+ </a>
503
+
504
+ <p>Resources can query and persist data without an API request or
505
+ response. To query, pass a <a href="http://jsonapi.org">JSONAPI-compliant</a> query hash:</p>
506
+
507
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span>
508
+ <span class="ss">filter: </span><span class="p">{</span> <span class="ss">first_name: </span><span class="s1">'Jane'</span> <span class="p">},</span>
509
+ <span class="ss">sort: </span><span class="s1">'-created_at'</span><span class="p">,</span>
510
+ <span class="ss">page: </span><span class="p">{</span> <span class="ss">size: </span><span class="mi">10</span><span class="p">,</span> <span class="ss">number: </span><span class="mi">2</span> <span class="p">}</span>
511
+ <span class="p">})</span></code></pre></figure>
512
+
513
+ <p>The return value from <code class="language-plaintext highlighter-rouge">.all</code> is a <strong>proxy</strong> object, similar to
514
+ <code class="language-plaintext highlighter-rouge">ActiveRecord::Relation</code>:</p>
515
+
516
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># ActiveRecord:</span>
517
+ <span class="n">employees</span> <span class="o">=</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">all</span>
518
+ <span class="n">employees</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># ActiveRecord::Relation</span>
519
+ <span class="c1"># No query fires until .map</span>
520
+ <span class="n">employees</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:first_name</span><span class="p">)</span> <span class="c1"># =&gt; ["Jane", "Joe", ...]</span>
521
+
522
+ <span class="c1"># Graphiti Resource:</span>
523
+ <span class="n">employees</span> <span class="o">=</span> <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">all</span>
524
+ <span class="n">employees</span><span class="p">.</span><span class="nf">class</span> <span class="c1"># Graphiti::ResourceProxy</span>
525
+ <span class="c1"># No query fires until .map</span>
526
+ <span class="n">employees</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:first_name</span><span class="p">)</span> <span class="c1"># =&gt; ["Jane", "Joe", ...]</span>
527
+
528
+ <span class="c1"># Access model instances directly</span>
529
+ <span class="n">employees</span><span class="p">.</span><span class="nf">data</span> <span class="c1"># =&gt; [#&lt;Employee&gt;, #&lt;Employee&gt;, ...]</span></code></pre></figure>
530
+
531
+ <p>This proxy object can render <a href="http://jsonapi.org">JSONAPI</a>, simple
532
+ JSON, or XML:</p>
533
+
534
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">employees</span> <span class="o">=</span> <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">all</span>
535
+ <span class="n">employees</span><span class="p">.</span><span class="nf">to_jsonapi</span>
536
+ <span class="n">employees</span><span class="p">.</span><span class="nf">to_json</span>
537
+ <span class="n">employees</span><span class="p">.</span><span class="nf">to_xml</span></code></pre></figure>
538
+
539
+ <p>Use <code class="language-plaintext highlighter-rouge">.find</code> to find a single record by id, raising
540
+ <code class="language-plaintext highlighter-rouge">Graphiti::Errors::RecordNotFound</code> if no records are returned:</p>
541
+
542
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">employee</span> <span class="o">=</span> <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="ss">id: </span><span class="mi">123</span><span class="p">)</span>
543
+ <span class="n">employee</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">first_name</span> <span class="c1"># =&gt; "Jane"</span></code></pre></figure>
544
+
545
+ <p><em>Note</em>: <code class="language-plaintext highlighter-rouge">SomeResource.find</code> returns a <code class="language-plaintext highlighter-rouge">ResourceProxy</code>. To access the model/record proper you will want to make sure you call <code class="language-plaintext highlighter-rouge">.data</code> on the result of find as shown above.</p>
546
+
547
+ <a class="anchor" id="composing-with-scopes" />
548
+ <a class="header" href="#composing-with-scopes">
549
+ <h3>
550
+ 3.2 Composing with Scopes
551
+ </h3>
552
+ </a>
553
+
554
+ <a class="anchor" id="base-scope" />
555
+ <a class="header" href="#base-scope">
556
+ <h4>
557
+ 3.2.1 #base_scope
558
+ </h4>
559
+ </a>
560
+
561
+ <p>Override the <code class="language-plaintext highlighter-rouge">#base_scope</code> method whenever you have logic that should
562
+ apply to <em>every</em> query. For example, if we only ever wanted to return
563
+ <code class="language-plaintext highlighter-rouge">active</code> Positions:</p>
564
+
565
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">base_scope</span>
566
+ <span class="no">Position</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">active: </span><span class="kp">true</span><span class="p">)</span>
567
+ <span class="k">end</span></code></pre></figure>
568
+
569
+ <p>This can be overridden by passing a second argument to <code class="language-plaintext highlighter-rouge">Resource.all</code>:</p>
570
+
571
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">InactivePostsController</span> <span class="o">&lt;</span> <span class="no">PostsController</span>
572
+ <span class="k">def</span> <span class="nf">index</span>
573
+ <span class="n">posts</span> <span class="o">=</span> <span class="no">PostResource</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="no">Post</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">active: </span><span class="kp">false</span><span class="p">))</span>
574
+ <span class="n">respond_with</span><span class="p">(</span><span class="n">posts</span><span class="p">)</span>
575
+ <span class="k">end</span>
576
+ <span class="k">end</span></code></pre></figure>
577
+
578
+ <a class="anchor" id="sort" />
579
+ <a class="header" href="#sort">
580
+ <h4>
581
+ 3.4 Sort
582
+ </h4>
583
+ </a>
584
+
585
+ <p>Use the <code class="language-plaintext highlighter-rouge">sort</code> DSL to customize sorting behavior.</p>
586
+
587
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">sort</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">direction</span><span class="o">|</span>
588
+ <span class="n">scope</span><span class="p">.</span><span class="nf">order</span><span class="p">(</span><span class="ss">first_name: </span><span class="n">direction</span><span class="p">,</span> <span class="ss">last_name: </span><span class="n">direction</span><span class="p">)</span>
589
+ <span class="k">end</span></code></pre></figure>
590
+
591
+ <p>If you’ve already defined a corresponding attribute, you’ll be
592
+ overriding that default behavior (and there is no need to pass a type as
593
+ the second argument):</p>
594
+
595
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span>
596
+
597
+ <span class="n">sort</span> <span class="ss">:name</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">direction</span><span class="o">|</span>
598
+ <span class="c1"># ... code ...</span>
599
+ <span class="k">end</span></code></pre></figure>
600
+
601
+ <blockquote>
602
+ <p>Note: <code class="language-plaintext highlighter-rouge">sort</code> defines a sort-only attribute. If you want other
603
+ behavior, like filtering, it’s best to define the attribute first.</p>
604
+ </blockquote>
605
+
606
+ <a class="anchor" id="sort-options" />
607
+ <a class="header" href="#sort-options">
608
+ <h5>
609
+ 3.4.1 Sort Options
610
+ </h5>
611
+ </a>
612
+
613
+ <p>Pass <code class="language-plaintext highlighter-rouge">:only</code> if you support just a single direction:</p>
614
+
615
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">sort</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:desc</span><span class="p">]</span></code></pre></figure>
616
+
617
+ <a class="anchor" id="filter" />
618
+ <a class="header" href="#filter">
619
+ <h4>
620
+ 3.5 Filter
621
+ </h4>
622
+ </a>
623
+
624
+ <p>Use the <code class="language-plaintext highlighter-rouge">filter</code> DSL to customize each <em>operator</em>:</p>
625
+
626
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">filter</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
627
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
628
+ <span class="n">scope</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">first_name: </span><span class="n">value</span><span class="p">)</span>
629
+ <span class="k">end</span>
630
+
631
+ <span class="c1"># prefix do ... end</span>
632
+ <span class="c1"># suffix do ... end</span>
633
+ <span class="c1"># etc</span>
634
+ <span class="k">end</span></code></pre></figure>
635
+
636
+ <p>The built-in operators for ActiveRecord are:</p>
637
+
638
+ <ul>
639
+ <li>eq (case-insensitive)</li>
640
+ <li>eql (case-sensitive)</li>
641
+ <li>prefix</li>
642
+ <li>suffix</li>
643
+ <li>match</li>
644
+ <li>gt (greater-than)</li>
645
+ <li>gte (greater-than-or-equal-to)</li>
646
+ <li>lt (less-than)</li>
647
+ <li>lte (less-than-or-equal-to)</li>
648
+ </ul>
649
+
650
+ <blockquote>
651
+ <p>Note that Graphiti expects filters to support multiple values by
652
+ default, so <code class="language-plaintext highlighter-rouge">value</code> will be an array. Pass <code class="language-plaintext highlighter-rouge">single: true</code> if you do
653
+ not support multiple values.</p>
654
+ </blockquote>
655
+
656
+ <blockquote>
657
+ <p>To pass multiple values in a query string, comma-delimit:
658
+ <code class="language-plaintext highlighter-rouge">/employees?filter[name]=Jane,John</code></p>
659
+ </blockquote>
660
+
661
+ <p>If you’ve already defined a corresponding attribute, you’ll be
662
+ overriding that default behavior (and there is no need to pass a type as
663
+ the second argument):</p>
664
+
665
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">attribute</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span>
666
+
667
+ <span class="n">filter</span> <span class="ss">:name</span> <span class="k">do</span>
668
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
669
+ <span class="c1"># ... code ...</span>
670
+ <span class="k">end</span>
671
+ <span class="k">end</span></code></pre></figure>
672
+
673
+ <p>You can define custom operators on-the-fly:</p>
674
+
675
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">filter</span> <span class="ss">:name</span> <span class="k">do</span>
676
+ <span class="n">fuzzy_match</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
677
+ <span class="c1"># ... code ...</span>
678
+ <span class="k">end</span>
679
+ <span class="k">end</span></code></pre></figure>
680
+
681
+ <p>Will now support <code class="language-plaintext highlighter-rouge">filter[name][fuzzy_match]=foo</code></p>
682
+
683
+ <blockquote>
684
+ <p>Note: <code class="language-plaintext highlighter-rouge">filter</code> defines a filter-only attribute. If you want other
685
+ behavior, like sorting, it’s best to define the attribute first.</p>
686
+ </blockquote>
687
+
688
+ <a class="anchor" id="filter-options" />
689
+ <a class="header" href="#filter-options">
690
+ <h5>
691
+ 3.5.1 Filter Options
692
+ </h5>
693
+ </a>
694
+
695
+ <p>Pass <code class="language-plaintext highlighter-rouge">:only</code> or <code class="language-plaintext highlighter-rouge">:except</code> to limit possible operators:</p>
696
+
697
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">filter</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:eq</span><span class="p">,</span> <span class="ss">:suffix</span><span class="p">]</span></code></pre></figure>
698
+
699
+ <p>Pass <code class="language-plaintext highlighter-rouge">:allow</code> or <code class="language-plaintext highlighter-rouge">:reject</code> to only allow filtering on certain values, or
700
+ reject bad values:</p>
701
+
702
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">filter</span> <span class="ss">:size</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">allow: </span><span class="p">[</span><span class="s1">'Big'</span><span class="p">,</span> <span class="s1">'Medium'</span><span class="p">,</span> <span class="s1">'Small'</span><span class="p">]</span>
703
+
704
+ <span class="n">filter</span> <span class="ss">:size</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">reject: </span><span class="p">[</span><span class="s1">'X-Large'</span><span class="p">]</span></code></pre></figure>
705
+
706
+ <p>By default, all filters accept multiple values, causing the yielded
707
+ <code class="language-plaintext highlighter-rouge">value</code> to always be an array. Pass <code class="language-plaintext highlighter-rouge">single: true</code> to only allow a
708
+ single value:</p>
709
+
710
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Default behavior</span>
711
+ <span class="n">filter</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
712
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
713
+ <span class="n">value</span> <span class="c1"># =&gt; ["Jane"]</span>
714
+ <span class="k">end</span>
715
+ <span class="k">end</span>
716
+
717
+ <span class="c1"># With single: true</span>
718
+ <span class="n">filter</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">single: </span><span class="kp">true</span> <span class="k">do</span>
719
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
720
+ <span class="n">value</span> <span class="c1"># =&gt; "Jane"</span>
721
+ <span class="k">end</span>
722
+ <span class="k">end</span></code></pre></figure>
723
+
724
+ <p>Filters can be required:</p>
725
+
726
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Via attribute</span>
727
+ <span class="n">attribute</span> <span class="ss">:customer_id</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">filterable: :required</span>
728
+
729
+ <span class="c1"># Via filter</span>
730
+ <span class="n">filter</span> <span class="ss">:customer_id</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">required: </span><span class="kp">true</span></code></pre></figure>
731
+
732
+ <p>Filters can also depend on other filters, requiring all criteria to be
733
+ present:</p>
734
+
735
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># We query customers by id AND type, not one or the other</span>
736
+ <span class="n">filter</span> <span class="ss">:customer_id</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">dependent: </span><span class="p">[</span><span class="ss">:customer_type</span><span class="p">]</span>
737
+ <span class="n">filter</span> <span class="ss">:customer_type</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">dependent: </span><span class="p">[</span><span class="ss">:customer_id</span><span class="p">]</span></code></pre></figure>
738
+
739
+ <a class="anchor" id="boolean-filter" />
740
+ <a class="header" href="#boolean-filter">
741
+ <h5>
742
+ 3.5.2 Boolean Filter
743
+ </h5>
744
+ </a>
745
+
746
+ <p>It doesn’t make sense for a filter with type <code class="language-plaintext highlighter-rouge">boolean</code> to accept
747
+ multiple values. These filters will be <code class="language-plaintext highlighter-rouge">single: true</code> by default.</p>
748
+
749
+ <a class="anchor" id="hash-filter" />
750
+ <a class="header" href="#hash-filter">
751
+ <h5>
752
+ 3.5.3 Hash Filter
753
+ </h5>
754
+ </a>
755
+
756
+ <p>Filters with type <code class="language-plaintext highlighter-rouge">hash</code> will automatically parse JSON when passed in a
757
+ URL query string:</p>
758
+
759
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># GET /employees?filter[metadata]={ "foo": 100 }</span>
760
+
761
+ <span class="n">filter</span> <span class="ss">:metadata</span><span class="p">,</span> <span class="ss">:hash</span> <span class="k">do</span>
762
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
763
+ <span class="n">value</span> <span class="c1"># =&gt; [{ "foo" =&gt; 100 }]</span>
764
+ <span class="k">end</span>
765
+ <span class="k">end</span></code></pre></figure>
766
+
767
+ <a class="anchor" id="escaping-values" />
768
+ <a class="header" href="#escaping-values">
769
+ <h5>
770
+ 3.5.4 Escaping Values
771
+ </h5>
772
+ </a>
773
+
774
+ <p>By default, Graphiti parses a comma-delimited string as an array. There
775
+ are times you may not want this - for instance a “keyword search” field
776
+ that could contain a comma.</p>
777
+
778
+ <p>Wrap values in <code class="language-plaintext highlighter-rouge">{{curlies}}</code> to avoid parsing:</p>
779
+
780
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># GET /employees?filter[keywords]={{some,value}}</span>
781
+
782
+ <span class="n">filter</span> <span class="ss">:keywords</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
783
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
784
+ <span class="n">value</span> <span class="c1"># =&gt; "some,value"</span>
785
+ <span class="k">end</span>
786
+ <span class="k">end</span></code></pre></figure>
787
+
788
+ <p>You can also define arrays explicitly instead of delimiting on comma:</p>
789
+
790
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># GET /employees?filter[keywords]=[some,value]</span>
791
+
792
+ <span class="n">filter</span> <span class="ss">:keywords</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
793
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
794
+ <span class="n">value</span> <span class="c1"># =&gt; ["some", "value"]</span>
795
+ <span class="k">end</span>
796
+ <span class="k">end</span></code></pre></figure>
797
+
798
+ <p>If a filter is marked <code class="language-plaintext highlighter-rouge">single: true</code>, we’ll avoid any array parsing and
799
+ escape the value for you, filtering on the string as given.</p>
800
+
801
+ <p>By default a value that comes in as <code class="language-plaintext highlighter-rouge">null</code> is treated as a string <code class="language-plaintext highlighter-rouge">"null"</code>.
802
+ To coerce <code class="language-plaintext highlighter-rouge">null</code> to a Ruby <code class="language-plaintext highlighter-rouge">nil</code> mark the filter with <code class="language-plaintext highlighter-rouge">allow_nil: true</code>.
803
+ This can be changed for all attributes by setting <code class="language-plaintext highlighter-rouge">filters_accept_nil_by_default</code></p>
804
+
805
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
806
+ <span class="nb">self</span><span class="p">.</span><span class="nf">filters_accept_nil_by_default</span> <span class="o">=</span> <span class="kp">true</span>
807
+ <span class="k">end</span></code></pre></figure>
808
+
809
+ <a class="anchor" id="statistics" />
810
+ <a class="header" href="#statistics">
811
+ <h4>
812
+ 3.6 Statistics
813
+ </h4>
814
+ </a>
815
+
816
+ <p>Statistics are useful and common. Consider a datagrid listing posts - we might want a “Total Posts” count displayed above the grid without firing an additional request. Notably, that statistic <strong>should</strong> take into account filtering, but <strong>should not</strong> take into account pagination.</p>
817
+
818
+ <p>All resources have a total count statistic by default:</p>
819
+
820
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">PostResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span>
821
+ <span class="ss">stats: </span><span class="p">{</span> <span class="ss">total: </span><span class="s1">'count'</span> <span class="p">}</span>
822
+ <span class="p">})</span></code></pre></figure>
823
+
824
+ <p><code class="language-plaintext highlighter-rouge">/posts?stats[total]=count</code></p>
825
+
826
+ <p>Would cause the <code class="language-plaintext highlighter-rouge">meta</code> section of the response to be:</p>
827
+
828
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
829
+ <span class="ss">meta: </span><span class="p">{</span>
830
+ <span class="ss">stats: </span><span class="p">{</span>
831
+ <span class="ss">total: </span><span class="p">{</span>
832
+ <span class="ss">count: </span><span class="mi">100</span>
833
+ <span class="p">}</span>
834
+ <span class="p">}</span>
835
+ <span class="p">}</span>
836
+ <span class="p">}</span></code></pre></figure>
837
+
838
+ <p>Allow a given statistic to be requested using <code class="language-plaintext highlighter-rouge">.stat</code>:</p>
839
+
840
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">stat</span> <span class="ss">total: </span><span class="p">[</span><span class="ss">:count</span><span class="p">]</span>
841
+ <span class="n">stat</span> <span class="ss">rating: </span><span class="p">[</span><span class="ss">:average</span><span class="p">]</span>
842
+ <span class="n">stat</span> <span class="ss">likes: </span><span class="p">[</span><span class="ss">:sum</span><span class="p">]</span>
843
+ <span class="n">stat</span> <span class="ss">score: </span><span class="p">[</span><span class="ss">:maximum</span><span class="p">]</span>
844
+ <span class="n">stat</span> <span class="ss">score: </span><span class="p">[</span><span class="ss">:maximum</span><span class="p">]</span>
845
+
846
+ <span class="c1"># e.g.</span>
847
+ <span class="c1"># {</span>
848
+ <span class="c1"># meta: {</span>
849
+ <span class="c1"># stats: {</span>
850
+ <span class="c1"># rating: {</span>
851
+ <span class="c1"># average: 74</span>
852
+ <span class="c1"># }</span>
853
+ <span class="c1"># }</span>
854
+ <span class="c1"># }</span>
855
+ <span class="c1"># }</span></code></pre></figure>
856
+
857
+ <p>You can also define custom statistics:</p>
858
+
859
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">stat</span> <span class="ss">rating: </span><span class="p">[</span><span class="ss">:average</span><span class="p">]</span> <span class="k">do</span>
860
+ <span class="n">standard_deviation</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="kp">attr</span><span class="o">|</span>
861
+ <span class="c1"># your standard deviation code here</span>
862
+ <span class="k">end</span>
863
+ <span class="k">end</span></code></pre></figure>
864
+
865
+ <a class="anchor" id="extra-fields" />
866
+ <a class="header" href="#extra-fields">
867
+ <h4>
868
+ 3.7 Extra Fields
869
+ </h4>
870
+ </a>
871
+
872
+ <p>Sometimes you have a field that is not always needed, and perhaps
873
+ computationally expensive. In this case, you only want the field
874
+ returned when explicitly requested by the client. To do this:</p>
875
+
876
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">extra_attribute</span> <span class="ss">:net_worth</span></code></pre></figure>
877
+
878
+ <p>This works just like <code class="language-plaintext highlighter-rouge">attribute</code>, except the field is read-only and will
879
+ only be returned when requested. The query parameter signature matches
880
+ <code class="language-plaintext highlighter-rouge">fields</code>: <code class="language-plaintext highlighter-rouge">?extra_fields[employees]=net_worth</code>.</p>
881
+
882
+ <p>You may want to adjust your scope to eager load data when a given extra
883
+ field is requested. To do this:</p>
884
+
885
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">resource</span><span class="p">.</span><span class="nf">on_extra_attribute</span> <span class="ss">:net_worth</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="o">|</span>
886
+ <span class="n">scope</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="ss">:assets</span><span class="p">)</span>
887
+ <span class="k">end</span></code></pre></figure>
888
+
889
+ <a class="anchor" id="resolve" />
890
+ <a class="header" href="#resolve">
891
+ <h4>
892
+ 3.8 #resolve
893
+ </h4>
894
+ </a>
895
+
896
+ <p>After we build up a query, we pass it to <code class="language-plaintext highlighter-rouge">#resolve</code>. Resolve <strong>must</strong> do
897
+ two things:</p>
898
+
899
+ <ul>
900
+ <li>Execute the query</li>
901
+ <li>Return an array of <code class="language-plaintext highlighter-rouge">Model</code> instances</li>
902
+ </ul>
903
+
904
+ <p>Override <code class="language-plaintext highlighter-rouge">#resolve</code> if you need more than the default behavior:</p>
905
+
906
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">resolve</span><span class="p">(</span><span class="n">scope</span><span class="p">)</span>
907
+ <span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">info</span> <span class="s2">"begin resolving scope..."</span>
908
+ <span class="n">result</span> <span class="o">=</span> <span class="k">super</span>
909
+ <span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">info</span> <span class="s2">"resolved!"</span>
910
+ <span class="n">result</span>
911
+ <span class="k">end</span></code></pre></figure>
912
+
913
+ <a class="anchor" id="configuration" />
914
+ <a class="header" href="#configuration">
915
+ <h2>
916
+ 4 Configuration
917
+ </h2>
918
+ </a>
919
+
920
+ <p>Here’s a Resource with explicit defaults:</p>
921
+
922
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
923
+ <span class="nb">self</span><span class="p">.</span><span class="nf">model</span> <span class="o">=</span> <span class="no">Post</span>
924
+ <span class="nb">self</span><span class="p">.</span><span class="nf">type</span> <span class="o">=</span> <span class="s1">'posts'</span>
925
+
926
+ <span class="c1"># Only used if you care about Links</span>
927
+ <span class="n">primary_endpoint</span> <span class="s1">'/posts'</span><span class="p">,</span> <span class="p">[</span><span class="ss">:index</span><span class="p">,</span> <span class="ss">:show</span><span class="p">,</span> <span class="ss">:create</span><span class="p">,</span> <span class="ss">:update</span><span class="p">,</span> <span class="ss">:destroy</span><span class="p">]</span>
928
+
929
+ <span class="c1"># default nil</span>
930
+ <span class="nb">self</span><span class="p">.</span><span class="nf">default_sort</span> <span class="o">=</span> <span class="p">[{</span> <span class="ss">title: :asc</span> <span class="p">}]</span>
931
+
932
+ <span class="c1"># default 20</span>
933
+ <span class="nb">self</span><span class="p">.</span><span class="nf">default_page_size</span> <span class="o">=</span> <span class="mi">10</span>
934
+ <span class="k">end</span></code></pre></figure>
935
+
936
+ <p>Typically you’d inherit from <code class="language-plaintext highlighter-rouge">ApplicationResource</code>. Here are some common higher-level customization options that will affect subclasses:</p>
937
+
938
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ApplicationResource</span> <span class="o">&lt;</span> <span class="no">Graphiti</span><span class="o">::</span><span class="no">Resource</span>
939
+ <span class="c1"># Must be set when no corresponding model/query</span>
940
+ <span class="nb">self</span><span class="p">.</span><span class="nf">abstract_class</span> <span class="o">=</span> <span class="kp">true</span>
941
+
942
+ <span class="c1"># Subclasses can override if needed</span>
943
+ <span class="nb">self</span><span class="p">.</span><span class="nf">adapter</span> <span class="o">=</span> <span class="no">Graphiti</span><span class="o">::</span><span class="no">Adapters</span><span class="o">::</span><span class="no">ActiveRecord</span>
944
+
945
+ <span class="c1"># Default attribute flags:</span>
946
+ <span class="c1"># attribute :title, :string,</span>
947
+ <span class="c1"># readable: default,</span>
948
+ <span class="c1"># writable: default,</span>
949
+ <span class="c1"># sortable: default,</span>
950
+ <span class="c1"># filterable: default</span>
951
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_readable_by_default</span> <span class="o">=</span> <span class="kp">true</span>
952
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_writable_by_default</span> <span class="o">=</span> <span class="kp">true</span>
953
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_sortable_by_default</span> <span class="o">=</span> <span class="kp">true</span>
954
+ <span class="nb">self</span><span class="p">.</span><span class="nf">attributes_filterable_by_default</span> <span class="o">=</span> <span class="kp">true</span>
955
+
956
+ <span class="c1"># Used for link generation</span>
957
+ <span class="nb">self</span><span class="p">.</span><span class="nf">base_url</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">default_url_options</span><span class="p">[</span><span class="ss">:host</span><span class="p">]</span>
958
+ <span class="c1"># Used for link generation</span>
959
+ <span class="c1"># Suggest referencing this config/routes.rb:</span>
960
+ <span class="c1"># scope path: ApplicationResource.endpoint_namespace do</span>
961
+ <span class="c1"># resources :posts</span>
962
+ <span class="c1"># end</span>
963
+ <span class="nb">self</span><span class="p">.</span><span class="nf">endpoint_namespace</span> <span class="o">=</span> <span class="s1">'/api/v1'</span>
964
+
965
+ <span class="c1"># Will raise an error if a resource is being accessed from a URL it is not allowlisted for</span>
966
+ <span class="c1"># Helpful for link validation</span>
967
+ <span class="nb">self</span><span class="p">.</span><span class="nf">validate_endpoints</span> <span class="o">=</span> <span class="kp">false</span>
968
+
969
+ <span class="c1"># Automatically generate JSONAPI links?</span>
970
+ <span class="nb">self</span><span class="p">.</span><span class="nf">autolink</span> <span class="o">=</span> <span class="kp">true</span>
971
+ <span class="k">end</span></code></pre></figure>
972
+
973
+ <a class="anchor" id="polymorphic-resources" />
974
+ <a class="header" href="#polymorphic-resources">
975
+ <h3>
976
+ 4.1 Polymorphic Resources
977
+ </h3>
978
+ </a>
979
+
980
+ <p>Polymorphic Resources are similar to <a href="https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html">ActiveRecord STI</a>: when a single query can return multiple Resource instances. We may query <code class="language-plaintext highlighter-rouge">/tasks</code>, but return <code class="language-plaintext highlighter-rouge">bugs</code>, <code class="language-plaintext highlighter-rouge">features</code>, <code class="language-plaintext highlighter-rouge">epics</code>, etc.</p>
981
+
982
+ <p>For example, given the <code class="language-plaintext highlighter-rouge">ActiveRecord</code> models:</p>
983
+
984
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Employee</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
985
+ <span class="n">has_many</span> <span class="ss">:tasks</span>
986
+ <span class="k">end</span>
987
+
988
+ <span class="c1"># tasks table has a 'type' column</span>
989
+ <span class="k">class</span> <span class="nc">Task</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
990
+ <span class="n">belongs_to</span> <span class="ss">:employee</span>
991
+ <span class="k">end</span>
992
+
993
+ <span class="k">class</span> <span class="nc">Bug</span> <span class="o">&lt;</span> <span class="no">Task</span>
994
+ <span class="k">end</span>
995
+
996
+ <span class="c1"># ONLY Feature has #points</span>
997
+ <span class="k">class</span> <span class="nc">Feature</span> <span class="o">&lt;</span> <span class="no">Task</span>
998
+ <span class="k">def</span> <span class="nf">points</span>
999
+ <span class="mi">5</span>
1000
+ <span class="k">end</span>
1001
+ <span class="k">end</span>
1002
+
1003
+ <span class="c1"># ONLY Epic has the milestones relationship</span>
1004
+ <span class="k">class</span> <span class="nc">Epic</span> <span class="o">&lt;</span> <span class="no">Task</span>
1005
+ <span class="n">has_many</span> <span class="ss">:milestones</span>
1006
+ <span class="k">end</span>
1007
+
1008
+ <span class="k">class</span> <span class="nc">Milestone</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1009
+ <span class="n">belongs_to</span> <span class="ss">:epic</span>
1010
+ <span class="k">end</span></code></pre></figure>
1011
+
1012
+ <p>We could define the following Polymorphic Resources:</p>
1013
+
1014
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">TaskResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1015
+ <span class="c1"># Reference child classes</span>
1016
+ <span class="nb">self</span><span class="p">.</span><span class="nf">polymorphic</span> <span class="o">=</span> <span class="p">[</span>
1017
+ <span class="s1">'BugResource'</span><span class="p">,</span>
1018
+ <span class="s1">'FeatureResource'</span><span class="p">,</span>
1019
+ <span class="s1">'EpicResource'</span>
1020
+ <span class="p">]</span>
1021
+
1022
+ <span class="n">attribute</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span>
1023
+ <span class="k">end</span>
1024
+
1025
+ <span class="k">class</span> <span class="nc">BugResource</span> <span class="o">&lt;</span> <span class="no">TaskResource</span>
1026
+ <span class="k">end</span>
1027
+
1028
+ <span class="k">class</span> <span class="nc">FeatureResource</span> <span class="o">&lt;</span> <span class="no">TaskResource</span>
1029
+ <span class="n">attribute</span> <span class="ss">:points</span><span class="p">,</span> <span class="ss">:integer</span>
1030
+ <span class="k">end</span>
1031
+
1032
+ <span class="k">class</span> <span class="nc">EpicResource</span> <span class="o">&lt;</span> <span class="no">TaskResource</span>
1033
+ <span class="n">has_many</span> <span class="ss">:milestones</span>
1034
+ <span class="k">end</span>
1035
+
1036
+ <span class="k">class</span> <span class="nc">MilestoneResource</span> <span class="o">&lt;</span> <span class="no">TaskResource</span>
1037
+ <span class="n">belongs_to</span> <span class="ss">:epic</span>
1038
+ <span class="k">end</span></code></pre></figure>
1039
+
1040
+ <p>If we hit a <code class="language-plaintext highlighter-rouge">/tasks</code> endpoint, we’d get back <a href="http://jsonapi.org/format/#document-resource-identifier-objects">JSONAPI types</a> of <code class="language-plaintext highlighter-rouge">bugs</code>, <code class="language-plaintext highlighter-rouge">features</code> and <code class="language-plaintext highlighter-rouge">epics</code>. Only <code class="language-plaintext highlighter-rouge">features</code> would render the <code class="language-plaintext highlighter-rouge">points</code> attribute, and only <code class="language-plaintext highlighter-rouge">epics</code> would render the <code class="language-plaintext highlighter-rouge">milestones relationship</code>.</p>
1041
+
1042
+ <p>A query to <code class="language-plaintext highlighter-rouge">/tasks?include=milestones</code> would correctly only query
1043
+ and render Milestones for Epics.</p>
1044
+
1045
+ <a class="anchor" id="relationships" />
1046
+ <a class="header" href="#relationships">
1047
+ <h2>
1048
+ 5 Relationships
1049
+ </h2>
1050
+ </a>
1051
+
1052
+ <p>Resources can connect to other Resources via <strong>relationships</strong>.
1053
+ Each relationship determines behavior for:</p>
1054
+
1055
+ <ul>
1056
+ <li>Sideloading (load both Resources in a single request)</li>
1057
+ <li>Links (URL to lazy-load in separate request)</li>
1058
+ <li>Sideposting (save both in single request)</li>
1059
+ </ul>
1060
+
1061
+ <p>When connecting resources, you can imagine the logic similar to
1062
+ <code class="language-plaintext highlighter-rouge">ActiveRecord</code>’s <code class="language-plaintext highlighter-rouge">.includes</code>:</p>
1063
+
1064
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1065
+ <span class="n">has_many</span> <span class="ss">:comments</span>
1066
+ <span class="k">end</span>
1067
+
1068
+ <span class="k">class</span> <span class="nc">CommentResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1069
+ <span class="n">attribute</span> <span class="ss">:post_id</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:filterable</span><span class="p">]</span>
1070
+ <span class="n">belongs_to</span> <span class="ss">:post</span>
1071
+ <span class="k">end</span>
1072
+
1073
+ <span class="no">PostResource</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="ss">include: </span><span class="s1">'comments'</span><span class="p">)</span>
1074
+ <span class="c1"># Under the hood:</span>
1075
+ <span class="c1"># CommentResource.all(filter: { post_id: array_of_post_ids })</span>
1076
+
1077
+ <span class="no">CommentResource</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="ss">include: </span><span class="s1">'post'</span><span class="p">)</span>
1078
+ <span class="c1"># Under the hood:</span>
1079
+ <span class="c1"># PostResource.all(filter: { id: array_of_comment_ids })</span></code></pre></figure>
1080
+
1081
+ <blockquote>
1082
+ <p>Note the explicit <code class="language-plaintext highlighter-rouge">post_id</code> filter on <code class="language-plaintext highlighter-rouge">CommentResource</code></p>
1083
+ </blockquote>
1084
+
1085
+ <a class="anchor" id="deep-queries" />
1086
+ <a class="header" href="#deep-queries">
1087
+ <h3>
1088
+ 5.1 Deep Queries
1089
+ </h3>
1090
+ </a>
1091
+
1092
+ <p>A query that applies to a relationship is referred to as a <strong>deep
1093
+ query</strong>. Use the dot-syntax to deep query:</p>
1094
+
1095
+ <p><code class="language-plaintext highlighter-rouge">/employees?include=positions&amp;filter[positions.title]=Manager</code></p>
1096
+
1097
+ <p><code class="language-plaintext highlighter-rouge">/employees?include=positions.department&amp;filter[positions.department.name]=Engineering</code></p>
1098
+
1099
+ <p>The above references the <strong>relationship name</strong>. For simplicity, you can
1100
+ also pass the JSONAPI type in brackets:</p>
1101
+
1102
+ <p><code class="language-plaintext highlighter-rouge">/employees?include=positions.department&amp;filter[departments][name]=Engineering</code></p>
1103
+
1104
+ <p>Sorting and pagination currently only support the JSONAPI type:</p>
1105
+
1106
+ <p><code class="language-plaintext highlighter-rouge">/employees?include=positions.department&amp;sort=departments.name</code></p>
1107
+
1108
+ <p><code class="language-plaintext highlighter-rouge">/employees?include=positions.department&amp;page[departments][size]=10</code></p>
1109
+
1110
+ <a class="anchor" id="customizing-relationships" />
1111
+ <a class="header" href="#customizing-relationships">
1112
+ <h4>
1113
+ 5.2 Customizing Relationships
1114
+ </h4>
1115
+ </a>
1116
+
1117
+ <p>The default options you can override are:</p>
1118
+
1119
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:positions</span><span class="p">,</span>
1120
+ <span class="ss">foreign_key: :employee_id</span><span class="p">,</span>
1121
+ <span class="ss">primary_key: :id</span><span class="p">,</span>
1122
+ <span class="ss">resource: </span><span class="no">EmployeeResource</span><span class="p">,</span>
1123
+ <span class="ss">readable: </span><span class="kp">true</span><span class="p">,</span>
1124
+ <span class="ss">writable: </span><span class="kp">true</span><span class="p">,</span>
1125
+ <span class="ss">link: </span><span class="nb">self</span><span class="p">.</span><span class="nf">autolink</span><span class="p">,</span> <span class="c1"># default true</span>
1126
+ <span class="ss">single: </span><span class="kp">false</span><span class="p">,</span> <span class="c1"># only allow this sideload when one employee</span>
1127
+ <span class="ss">always_include_resource_ids: </span><span class="kp">false</span></code></pre></figure>
1128
+
1129
+ <p><em>note</em>: Setting <code class="language-plaintext highlighter-rouge">always_include_resource_ids: true</code> could result in 1+N queries (see <a href="https://github.com/graphiti-api/graphiti/issues/167#issuecomment-686866646">#167</a>)</p>
1130
+
1131
+ <a class="anchor" id="conditional-relationships" />
1132
+ <a class="header" href="#conditional-relationships">
1133
+ <h5>
1134
+ 5.2.1 Conditional Relationships
1135
+ </h5>
1136
+ </a>
1137
+
1138
+ <p>Like attributes, the <code class="language-plaintext highlighter-rouge">readable</code> and <code class="language-plaintext highlighter-rouge">writable</code> flags on a relationship accept more than a boolean: pass a symbol, string, or proc and the relationship becomes conditional, evaluated per-request.</p>
1139
+
1140
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">EmployeeResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1141
+ <span class="n">has_many</span> <span class="ss">:salary_histories</span><span class="p">,</span> <span class="ss">readable: :admin?</span><span class="p">,</span> <span class="ss">writable: :admin?</span>
1142
+
1143
+ <span class="k">def</span> <span class="nf">admin?</span>
1144
+ <span class="n">context</span><span class="p">.</span><span class="nf">current_user</span><span class="p">.</span><span class="nf">admin?</span>
1145
+ <span class="k">end</span>
1146
+ <span class="k">end</span></code></pre></figure>
1147
+
1148
+ <p>When a readable guard returns <code class="language-plaintext highlighter-rouge">false</code>, the relationship is omitted from the serialized output and any attempt to sideload it via <code class="language-plaintext highlighter-rouge">?include=</code> is silently scrubbed from the request. When a writable guard returns <code class="language-plaintext highlighter-rouge">false</code>, sideposting to that relationship is rejected with an <code class="language-plaintext highlighter-rouge">unwritable_relationship</code> validation error.</p>
1149
+
1150
+ <p>Unlike attribute guards, relationship guards take no arguments. Include scrubbing happens before any records have been fetched, so there is no model to hand them — base the decision on <code class="language-plaintext highlighter-rouge">context</code> alone.</p>
1151
+
1152
+ <p>The guard can live on either side of the relationship. Graphiti first looks for the method on the resource declaring the relationship; if it isn’t defined there but is defined on the related resource, the related resource’s method is used. Defining the guard on the related resource lets a single guard cover every relationship pointing at it:</p>
1153
+
1154
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">SalaryHistoryResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1155
+ <span class="c1"># Any resource declaring a relationship to SalaryHistoryResource with</span>
1156
+ <span class="c1"># readable: :admin? will use this method, unless it defines its own.</span>
1157
+ <span class="k">def</span> <span class="nf">admin?</span>
1158
+ <span class="n">context</span><span class="p">.</span><span class="nf">current_user</span><span class="p">.</span><span class="nf">admin?</span>
1159
+ <span class="k">end</span>
1160
+ <span class="k">end</span></code></pre></figure>
1161
+
1162
+ <blockquote>
1163
+ <p><strong>Upgrading to 1.12:</strong> relationship guards are new enforcement, not a new
1164
+ option. Before 1.12, a symbol, string, or proc passed to a relationship’s
1165
+ <code class="language-plaintext highlighter-rouge">readable</code>/<code class="language-plaintext highlighter-rouge">writable</code> was accepted and silently treated as <code class="language-plaintext highlighter-rouge">true</code> — the guard
1166
+ was never called. Those guards now run. If your app already passes one of
1167
+ these, a relationship that has been serialized all along may start
1168
+ disappearing from responses.</p>
1169
+
1170
+ <p>To list every guarded relationship in your app before deploying, run
1171
+ <code class="language-plaintext highlighter-rouge">bin/rails runner 'puts Graphiti.guarded_relationships'</code>.</p>
1172
+
1173
+ <p>Apps using <code class="language-plaintext highlighter-rouge">schema.json</code> also get this for free: guarded relationships are
1174
+ flagged in the schema, and the schema check reports them as
1175
+ <code class="language-plaintext highlighter-rouge">became guarded</code>.</p>
1176
+ </blockquote>
1177
+
1178
+ <a class="anchor" id="customizing-scope" />
1179
+ <a class="header" href="#customizing-scope">
1180
+ <h5>
1181
+ 5.2.2 Customizing Scope
1182
+ </h5>
1183
+ </a>
1184
+
1185
+ <p>Use <code class="language-plaintext highlighter-rouge">params</code> to change the query parameters that will be passed to the
1186
+ associated Resource:</p>
1187
+
1188
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:active_positions</span><span class="p">,</span> <span class="ss">resource: </span><span class="no">PositionResource</span> <span class="k">do</span>
1189
+ <span class="n">params</span> <span class="k">do</span> <span class="o">|</span><span class="nb">hash</span><span class="p">,</span> <span class="n">employees</span><span class="o">|</span>
1190
+ <span class="nb">hash</span><span class="p">[</span><span class="ss">:filter</span><span class="p">][</span><span class="ss">:active</span><span class="p">]</span> <span class="o">=</span> <span class="kp">true</span>
1191
+ <span class="k">end</span>
1192
+ <span class="k">end</span>
1193
+
1194
+ <span class="c1"># Would cause the underlying query:</span>
1195
+ <span class="c1">#</span>
1196
+ <span class="c1"># PositionResource.all({</span>
1197
+ <span class="c1"># filter: {</span>
1198
+ <span class="c1"># employee_id: array_of_employee_ids</span>
1199
+ <span class="c1"># active: true</span>
1200
+ <span class="c1"># }</span>
1201
+ <span class="c1"># })</span></code></pre></figure>
1202
+
1203
+ <p>If there is no existing AR association for this we would also need to make it a getter/setter on the model.</p>
1204
+
1205
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/models/position.rb</span>
1206
+ <span class="nb">attr_accessor</span> <span class="ss">:active_positions</span></code></pre></figure>
1207
+
1208
+ <a class="anchor" id="customizing-assignment" />
1209
+ <a class="header" href="#customizing-assignment">
1210
+ <h5>
1211
+ 5.2.3 Customizing Assignment
1212
+ </h5>
1213
+ </a>
1214
+
1215
+ <p>Once we’ve fetched primary data and its relationship (e.g. we have an
1216
+ <code class="language-plaintext highlighter-rouge">employees</code> array and <code class="language-plaintext highlighter-rouge">positions</code> array), we need to associate these
1217
+ objects:</p>
1218
+
1219
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">employees</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span>
1220
+ <span class="n">e</span><span class="p">.</span><span class="nf">positions</span> <span class="o">=</span> <span class="n">positions</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="nb">p</span><span class="o">|</span> <span class="nb">p</span><span class="p">.</span><span class="nf">employee_id</span> <span class="o">==</span> <span class="n">e</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span>
1221
+ <span class="k">end</span></code></pre></figure>
1222
+
1223
+ <p>Occasionally this logic will be non-standard or more complex. Use
1224
+ <code class="language-plaintext highlighter-rouge">assign_each</code> to customize, returning all relevant children for the
1225
+ given parent:</p>
1226
+
1227
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:positions</span> <span class="k">do</span>
1228
+ <span class="n">assign_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">employee</span><span class="p">,</span> <span class="n">positions</span><span class="o">|</span>
1229
+ <span class="n">positions</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="nb">p</span><span class="o">|</span> <span class="nb">p</span><span class="p">.</span><span class="nf">belongs_to?</span><span class="p">(</span><span class="n">employee</span><span class="p">)</span> <span class="p">}</span>
1230
+ <span class="k">end</span>
1231
+ <span class="k">end</span></code></pre></figure>
1232
+
1233
+ <p>Or if all else fails, use <code class="language-plaintext highlighter-rouge">#assign</code> to control all the logic:</p>
1234
+
1235
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:positions</span> <span class="k">do</span>
1236
+ <span class="n">assign</span> <span class="k">do</span> <span class="o">|</span><span class="n">employees</span><span class="p">,</span> <span class="n">positions</span><span class="o">|</span>
1237
+ <span class="n">employees</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">employee</span><span class="o">|</span>
1238
+ <span class="n">positions</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="nb">p</span><span class="o">|</span> <span class="nb">p</span><span class="p">.</span><span class="nf">belongs_to?</span><span class="p">(</span><span class="n">employee</span><span class="p">)</span> <span class="p">}</span>
1239
+ <span class="k">end</span>
1240
+ <span class="k">end</span>
1241
+ <span class="k">end</span></code></pre></figure>
1242
+
1243
+ <p><strong>Note</strong>: ActiveRecord will sometimes cause unexpected queries when
1244
+ assigning. If you’re overriding <code class="language-plaintext highlighter-rouge">#assign</code>, make sure to keep an eye on
1245
+ this. If using <code class="language-plaintext highlighter-rouge">#assign_each</code>, you’re fine because the adapter will take
1246
+ care of this for you.</p>
1247
+
1248
+ <a class="anchor" id="has-many" />
1249
+ <a class="header" href="#has-many">
1250
+ <h4>
1251
+ 5.3 has_many
1252
+ </h4>
1253
+ </a>
1254
+
1255
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:positions</span></code></pre></figure>
1256
+
1257
+ <p>Defaults to these common options:</p>
1258
+
1259
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:positions</span><span class="p">,</span>
1260
+ <span class="ss">foreign_key: :employee_id</span><span class="p">,</span>
1261
+ <span class="ss">primary_key: :id</span><span class="p">,</span>
1262
+ <span class="ss">always_include_resource_ids: </span><span class="kp">false</span><span class="p">,</span>
1263
+ <span class="ss">resource: </span><span class="no">PositionResource</span></code></pre></figure>
1264
+
1265
+ <p>Which would cause the following query when sideloading:</p>
1266
+
1267
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">PositionResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span> <span class="ss">filter: </span><span class="p">{</span> <span class="n">employee_id</span> <span class="o">=&gt;</span> <span class="n">employee_ids</span> <span class="p">}</span> <span class="p">})</span></code></pre></figure>
1268
+
1269
+ <p>This means <strong>we need to make sure that filter is supported</strong>:</p>
1270
+
1271
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PositionResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1272
+ <span class="n">attribute</span> <span class="ss">:employee_id</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:filterable</span><span class="p">]</span>
1273
+ <span class="c1"># ... code ...</span>
1274
+ <span class="k">end</span></code></pre></figure>
1275
+
1276
+ <p>Once we’ve resolved <code class="language-plaintext highlighter-rouge">employees</code> and <code class="language-plaintext highlighter-rouge">positions</code> the resulting objects
1277
+ would be associated with logic similar to:</p>
1278
+
1279
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">employees</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span>
1280
+ <span class="n">e</span><span class="p">.</span><span class="nf">positions</span> <span class="o">=</span> <span class="n">positions</span><span class="p">.</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="nb">p</span><span class="o">|</span> <span class="nb">p</span><span class="p">.</span><span class="nf">employee_id</span> <span class="o">==</span> <span class="n">e</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span>
1281
+ <span class="k">end</span></code></pre></figure>
1282
+
1283
+ <p>And generate a Link:</p>
1284
+
1285
+ <p><code class="language-plaintext highlighter-rouge">/positions?filter[employee_id]=1,2,3</code></p>
1286
+
1287
+ <a class="anchor" id="belongs-to" />
1288
+ <a class="header" href="#belongs-to">
1289
+ <h4>
1290
+ 5.4 belongs_to
1291
+ </h4>
1292
+ </a>
1293
+
1294
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">belongs_to</span> <span class="ss">:employee</span></code></pre></figure>
1295
+
1296
+ <p>Defaults to these common options:</p>
1297
+
1298
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">belongs_to</span> <span class="ss">:employee</span><span class="p">,</span>
1299
+ <span class="ss">foreign_key: :employee_id</span><span class="p">,</span>
1300
+ <span class="ss">primary_key: :id</span><span class="p">,</span>
1301
+ <span class="ss">always_include_resource_ids: </span><span class="kp">false</span><span class="p">,</span>
1302
+ <span class="ss">resource: </span><span class="no">EmployeeResource</span></code></pre></figure>
1303
+
1304
+ <p>Which would cause the following query when sideloading:</p>
1305
+
1306
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span> <span class="ss">filter: </span><span class="p">{</span> <span class="nb">id</span> <span class="o">=&gt;</span> <span class="n">position_ids</span> <span class="p">}</span> <span class="p">})</span></code></pre></figure>
1307
+
1308
+ <p>And assign the resulting objects with logic similar to:</p>
1309
+
1310
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">positions</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">p</span><span class="o">|</span>
1311
+ <span class="nb">p</span><span class="p">.</span><span class="nf">employee</span> <span class="o">=</span> <span class="n">employees</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="nb">p</span><span class="p">.</span><span class="nf">employee_id</span> <span class="o">==</span> <span class="n">e</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span>
1312
+ <span class="k">end</span></code></pre></figure>
1313
+
1314
+ <p>And generate a Link:</p>
1315
+
1316
+ <p><code class="language-plaintext highlighter-rouge">/employees?filter[id]=1,2,3</code></p>
1317
+
1318
+ <a class="anchor" id="has-one" />
1319
+ <a class="header" href="#has-one">
1320
+ <h4>
1321
+ 5.5 has_one
1322
+ </h4>
1323
+ </a>
1324
+
1325
+ <p><code class="language-plaintext highlighter-rouge">has_one</code> works exactly like <code class="language-plaintext highlighter-rouge">has_many</code>, but only one record will be
1326
+ returned. When sideloading this will be a single element, much like
1327
+ <code class="language-plaintext highlighter-rouge">belongs_to</code>.</p>
1328
+
1329
+ <p>There is one small caveat: Links always point to an <code class="language-plaintext highlighter-rouge">index</code> action, so
1330
+ we can apply filters. That means following <em><code class="language-plaintext highlighter-rouge">has_one</code> Link will lead to
1331
+ an array</em>, and you should select the first record.</p>
1332
+
1333
+ <a class="anchor" id="faux-has-one" />
1334
+ <a class="header" href="#faux-has-one">
1335
+ <h5>
1336
+ 5.5.1 Faux has_one
1337
+ </h5>
1338
+ </a>
1339
+
1340
+ <p>A “Faux Has One” occurs when there is more than one record of
1341
+ associated data, but we only want to return the <em>first</em> record in that
1342
+ array. Consider this <code class="language-plaintext highlighter-rouge">ActiveRecord</code> relationship:</p>
1343
+
1344
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/models/employee.rb</span>
1345
+ <span class="n">has_many</span> <span class="ss">:positions</span>
1346
+ <span class="n">has_one</span> <span class="ss">:current_position</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">where</span><span class="p">(</span><span class="ss">created_at: :desc</span><span class="p">)</span> <span class="p">},</span> <span class="ss">class_name: </span><span class="s1">'Position'</span>
1347
+
1348
+ <span class="no">Employee</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="s1">'current_position'</span><span class="p">).</span><span class="nf">to_a</span>
1349
+
1350
+ <span class="c1"># SELECT * FROM employees</span>
1351
+ <span class="c1"># SELECT * FROM positions WHERE employee_id IN (?) ORDER BY created_at DESC</span></code></pre></figure>
1352
+
1353
+ <p>When we eager load, <em>more than one Position is returned from the
1354
+ database query</em>. Assigning only the first record and dropping the rest
1355
+ occurs in ruby, not the database query.</p>
1356
+
1357
+ <p>The same thing happens in Graphiti:</p>
1358
+
1359
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/employee_resource.rb</span>
1360
+ <span class="n">has_many</span> <span class="ss">:positions</span>
1361
+ <span class="n">has_one</span> <span class="ss">:current_position</span><span class="p">,</span> <span class="ss">resource: </span><span class="no">PositionResource</span> <span class="k">do</span>
1362
+ <span class="n">params</span> <span class="k">do</span> <span class="o">|</span><span class="nb">hash</span><span class="o">|</span>
1363
+ <span class="nb">hash</span><span class="p">[</span><span class="ss">:sort</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'-created_at'</span>
1364
+ <span class="k">end</span>
1365
+ <span class="k">end</span>
1366
+
1367
+ <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="ss">include: </span><span class="s1">'current_position'</span><span class="p">)</span>
1368
+ <span class="c1"># PositionResource.all({</span>
1369
+ <span class="c1"># filter: { employee_id: employee_ids },</span>
1370
+ <span class="c1"># sort: '-created_at'</span>
1371
+ <span class="c1"># })</span></code></pre></figure>
1372
+
1373
+ <p>Though everything works as expected, a large number of Position records
1374
+ can incur a performance penalty (as we’d be instantiating a large number
1375
+ of ActiveRecord objects).</p>
1376
+
1377
+ <p>For this reason, you are encouraged to model Faux Has One’s in such a
1378
+ way that the underlying database query only returns the relevant single
1379
+ record. Imagine if we had a <code class="language-plaintext highlighter-rouge">historical_index</code> column on <code class="language-plaintext highlighter-rouge">positions</code>,
1380
+ where a value of <code class="language-plaintext highlighter-rouge">1</code> meant “most recent”:</p>
1381
+
1382
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/models/employee.rb</span>
1383
+ <span class="n">has_many</span> <span class="ss">:positions</span>
1384
+ <span class="n">has_one</span> <span class="ss">:current_position</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">where</span><span class="p">(</span><span class="ss">historical_index: </span><span class="mi">1</span><span class="p">)</span> <span class="p">},</span> <span class="ss">class_name: </span><span class="s1">'Position'</span>
1385
+
1386
+ <span class="no">Employee</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="s1">'current_position'</span><span class="p">).</span><span class="nf">to_a</span>
1387
+
1388
+ <span class="c1"># SELECT * FROM employees</span>
1389
+ <span class="c1"># SELECT * FROM positions WHERE employee_id IN (?) AND historical_index = 1</span></code></pre></figure>
1390
+
1391
+ <p>We’ve ensured the <em>query itself</em> only returns a single record.
1392
+ Optimizing a Graphiti API is the same as optimizing queries.</p>
1393
+
1394
+ <a class="anchor" id="many-to-many" />
1395
+ <a class="header" href="#many-to-many">
1396
+ <h4>
1397
+ 5.6 many_to_many
1398
+ </h4>
1399
+ </a>
1400
+
1401
+ <blockquote>
1402
+ <p>This relationship is specific to relational databases that use a “join
1403
+ table” between two tables.</p>
1404
+ </blockquote>
1405
+
1406
+ <p>Though you can make this work for other ORMs/clients, it’s easiest to
1407
+ explain by focusing on <code class="language-plaintext highlighter-rouge">ActiveRecord</code>.</p>
1408
+
1409
+ <p>First, <strong>you must use <a href="https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association">has_many :through</a> and not has_and_belongs_to_many</strong>:</p>
1410
+
1411
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Employee</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1412
+ <span class="n">has_many</span> <span class="ss">:team_memberships</span>
1413
+ <span class="n">has_many</span> <span class="ss">:teams</span><span class="p">,</span> <span class="ss">through: :team_memberships</span>
1414
+ <span class="k">end</span>
1415
+
1416
+ <span class="k">class</span> <span class="nc">TeamMembership</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1417
+ <span class="n">belongs_to</span> <span class="ss">:employee</span>
1418
+ <span class="n">belongs_to</span> <span class="ss">:team</span>
1419
+ <span class="k">end</span>
1420
+
1421
+ <span class="k">class</span> <span class="nc">Team</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1422
+ <span class="n">has_many</span> <span class="ss">:team_memberships</span>
1423
+ <span class="n">has_many</span> <span class="ss">:employees</span><span class="p">,</span> <span class="ss">through: :team_memberships</span>
1424
+ <span class="k">end</span></code></pre></figure>
1425
+
1426
+ <p>You can always expose <code class="language-plaintext highlighter-rouge">team_memberships</code> to your API - particularly
1427
+ useful if that table holds metadata about the relationship.</p>
1428
+
1429
+ <p>Other times, however, clients of the API should not have knowledge of
1430
+ this implementation detail. In these cases, use <code class="language-plaintext highlighter-rouge">many_to_many</code>:</p>
1431
+
1432
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">EmployeeResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1433
+ <span class="n">many_to_many</span> <span class="ss">:teams</span>
1434
+ <span class="k">end</span>
1435
+ <span class="c1"># Generates the Link</span>
1436
+ <span class="c1"># /teams?filter[employee_id]=1,2,3</span>
1437
+
1438
+ <span class="k">class</span> <span class="nc">TeamResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1439
+ <span class="n">many_to_many</span> <span class="ss">:employees</span>
1440
+ <span class="k">end</span>
1441
+ <span class="c1"># Generates the Link</span>
1442
+ <span class="c1"># /teams?filter[team_id]=1,2,3</span></code></pre></figure>
1443
+
1444
+ <p>The <code class="language-plaintext highlighter-rouge">many_to_many</code> call will automatically add a Filter to the
1445
+ associated resource. The logic for that filter, in the case of <code class="language-plaintext highlighter-rouge">ActiveRecord</code>:</p>
1446
+
1447
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/employee_resource.rb</span>
1448
+
1449
+ <span class="n">filter</span> <span class="ss">:team_id</span><span class="p">,</span> <span class="ss">:integer</span> <span class="k">do</span>
1450
+ <span class="n">eq</span> <span class="k">do</span> <span class="o">|</span><span class="n">scope</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
1451
+ <span class="n">scope</span>
1452
+ <span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="ss">:team_memberships</span><span class="p">)</span>
1453
+ <span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">team_memberships: </span><span class="p">{</span> <span class="ss">team_id: </span><span class="n">value</span> <span class="p">}</span>
1454
+ <span class="k">end</span>
1455
+ <span class="k">end</span></code></pre></figure>
1456
+
1457
+ <p>To customize the foreign key, you will need to specify a hash rather
1458
+ than a symbol. The hash key is the relationship name, so the above is
1459
+ equivalent to</p>
1460
+
1461
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/employee_resource.rb</span>
1462
+
1463
+ <span class="n">many_to_many</span> <span class="ss">:teams</span><span class="p">,</span> <span class="ss">foreign_key: </span><span class="p">{</span> <span class="ss">team_memberships: :team_id</span> <span class="p">}</span></code></pre></figure>
1464
+
1465
+ <p>If using ActiveRecord, and the API relationship name does not match your
1466
+ Model relationship name, use <code class="language-plaintext highlighter-rouge">:as</code> to specify the model relationship
1467
+ that should be used to derive the query:</p>
1468
+
1469
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># The API relationship is "teams", ActiveRecord has "groups"</span>
1470
+ <span class="n">many_to_many</span> <span class="ss">:teams</span><span class="p">,</span> <span class="ss">as: :groups</span></code></pre></figure>
1471
+
1472
+ <a class="anchor" id="polymorphic-belongs-to" />
1473
+ <a class="header" href="#polymorphic-belongs-to">
1474
+ <h4>
1475
+ 5.7 polymorphic_belongs_to
1476
+ </h4>
1477
+ </a>
1478
+
1479
+ <p>With polymorphic associations, a Resource can belong to more than one other Resource, on a single association. Though these relationships are not specific to <code class="language-plaintext highlighter-rouge">ActiveRecord</code>, we’ll use <code class="language-plaintext highlighter-rouge">ActiveRecord</code> conventions to describe the use case.</p>
1480
+
1481
+ <p>Given the following <a href="https://guides.rubyonrails.org/association_basics.html#polymorphic-associations">polymorphic ActiveRecords</a>:</p>
1482
+
1483
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Note</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1484
+ <span class="n">belongs_to</span> <span class="ss">:notable</span><span class="p">,</span> <span class="ss">polymorphic: </span><span class="kp">true</span>
1485
+ <span class="k">end</span>
1486
+
1487
+ <span class="k">class</span> <span class="nc">Employee</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1488
+ <span class="n">has_many</span> <span class="ss">:notes</span><span class="p">,</span> <span class="ss">as: :notable</span>
1489
+ <span class="k">end</span>
1490
+
1491
+ <span class="k">class</span> <span class="nc">Department</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1492
+ <span class="n">has_many</span> <span class="ss">:notes</span><span class="p">,</span> <span class="ss">as: :notable</span>
1493
+ <span class="k">end</span>
1494
+
1495
+ <span class="k">class</span> <span class="nc">Team</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
1496
+ <span class="n">has_many</span> <span class="ss">:notes</span><span class="p">,</span> <span class="ss">as: :notable</span>
1497
+ <span class="k">end</span></code></pre></figure>
1498
+
1499
+ <p>By <code class="language-plaintext highlighter-rouge">ActiveRecord</code> convention, the <code class="language-plaintext highlighter-rouge">notes</code> table would have columns
1500
+ <code class="language-plaintext highlighter-rouge">notable_id</code> and <code class="language-plaintext highlighter-rouge">notable_type</code>.</p>
1501
+
1502
+ <p>Graphiti has the same concept. In this case we would group all the notes
1503
+ by a given <code class="language-plaintext highlighter-rouge">notable_type</code>, and follow a different <code class="language-plaintext highlighter-rouge">belongs_to</code>
1504
+ association for each group:</p>
1505
+
1506
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/note_resource.rb</span>
1507
+ <span class="n">polymorphic_belongs_to</span> <span class="ss">:notable</span> <span class="k">do</span>
1508
+ <span class="n">group_by</span><span class="p">(</span><span class="ss">:notable_type</span><span class="p">)</span> <span class="k">do</span>
1509
+ <span class="n">on</span><span class="p">(</span><span class="ss">:Employee</span><span class="p">)</span>
1510
+ <span class="n">on</span><span class="p">(</span><span class="ss">:Department</span><span class="p">)</span>
1511
+ <span class="n">on</span><span class="p">(</span><span class="ss">:Team</span><span class="p">)</span>
1512
+ <span class="k">end</span>
1513
+ <span class="k">end</span></code></pre></figure>
1514
+
1515
+ <p>The <code class="language-plaintext highlighter-rouge">on</code> DSL is shorthand for a <code class="language-plaintext highlighter-rouge">belongs_to</code> relationship that accepts
1516
+ all the usual options and customizations:</p>
1517
+
1518
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">on</span><span class="p">(</span><span class="ss">:Employee</span><span class="p">).</span><span class="nf">belongs_to</span> <span class="ss">:employee</span><span class="p">,</span>
1519
+ <span class="ss">resource: </span><span class="no">EmployeeResource</span>
1520
+ <span class="c1"># ... etc ...</span></code></pre></figure>
1521
+
1522
+ <p>In other words: group all Notes by <code class="language-plaintext highlighter-rouge">notable_type</code>, and for all that have
1523
+ the value of <code class="language-plaintext highlighter-rouge">"Employee"</code> use the <code class="language-plaintext highlighter-rouge">belongs_to :employee</code> relationship
1524
+ for further querying.</p>
1525
+
1526
+ <a class="anchor" id="polymorphic-has-many" />
1527
+ <a class="header" href="#polymorphic-has-many">
1528
+ <h4>
1529
+ 5.8 polymorphic_has_many
1530
+ </h4>
1531
+ </a>
1532
+
1533
+ <p>Continuing from the prior section, the corresponding association of a
1534
+ <code class="language-plaintext highlighter-rouge">polymorphic_belongs_to</code> is a <code class="language-plaintext highlighter-rouge">polymorphic_has_many</code>:</p>
1535
+
1536
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">EmployeeResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1537
+ <span class="n">polymorphic_has_many</span> <span class="ss">:notes</span><span class="p">,</span> <span class="ss">as: :notable</span>
1538
+ <span class="k">end</span></code></pre></figure>
1539
+
1540
+ <p>Predictably, this causes the query:</p>
1541
+
1542
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">NoteResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span>
1543
+ <span class="ss">filter: </span><span class="p">{</span>
1544
+ <span class="ss">notable_type: </span><span class="s1">'Employee'</span><span class="p">,</span>
1545
+ <span class="ss">notable_id: </span><span class="n">employee_ids</span>
1546
+ <span class="p">}</span>
1547
+ <span class="p">})</span></code></pre></figure>
1548
+
1549
+ <p>And the Link</p>
1550
+
1551
+ <p><code class="language-plaintext highlighter-rouge">/notes?filter[notable_id]=1,2,3&amp;filter[notable_type]=Employee</code></p>
1552
+
1553
+ <p>Which means the following filters are required:</p>
1554
+
1555
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">NoteResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1556
+ <span class="n">attribute</span> <span class="ss">:notable_id</span><span class="p">,</span> <span class="ss">:integer</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:filterable</span><span class="p">]</span>
1557
+ <span class="n">attribute</span> <span class="ss">:notable_type</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:filterable</span><span class="p">]</span>
1558
+ <span class="c1"># ... code ...</span>
1559
+ <span class="k">end</span></code></pre></figure>
1560
+
1561
+ <a class="anchor" id="generators" />
1562
+ <a class="header" href="#generators">
1563
+ <h2>
1564
+ 6 Generators
1565
+ </h2>
1566
+ </a>
1567
+
1568
+ <p>To generate a Resource:</p>
1569
+
1570
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails generate graphiti:resource NAME <span class="o">[</span>attribute:type] <span class="o">[</span>options]</code></pre></figure>
1571
+
1572
+ <p>For example:</p>
1573
+
1574
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails generate graphiti:resource Employee first_name:string age:integer</code></pre></figure>
1575
+
1576
+ <p>Will add a route, controller, resource, and tests.</p>
1577
+
1578
+ <p>Limit the actions this resource supports with <code class="language-plaintext highlighter-rouge">-a</code>:</p>
1579
+
1580
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails generate graphiti:resource Employee <span class="nt">-a</span> index show</code></pre></figure>
1581
+
1582
+ <a class="anchor" id="persisting" />
1583
+ <a class="header" href="#persisting">
1584
+ <h2>
1585
+ 7 Persisting
1586
+ </h2>
1587
+ </a>
1588
+
1589
+ <p>Graphiti allows writing a graph of data in a single request. We’ll do
1590
+ the work of parsing the graph and ordering operations, so you can focus
1591
+ on the part you care about: the logic for actually persisting an object.</p>
1592
+
1593
+ <p>By default, persistence operations are handled by your adapter. The
1594
+ “expanded” view of the ActiveRecord implementation is below:</p>
1595
+
1596
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/employee_resource.rb</span>
1597
+
1598
+ <span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
1599
+ <span class="n">employee</span> <span class="o">=</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">new</span>
1600
+ <span class="n">attributes</span><span class="p">.</span><span class="nf">each_pair</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
1601
+ <span class="n">employee</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="ss">="</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
1602
+ <span class="k">end</span>
1603
+ <span class="n">employee</span><span class="p">.</span><span class="nf">save</span>
1604
+ <span class="n">employee</span>
1605
+ <span class="k">end</span>
1606
+
1607
+ <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
1608
+ <span class="n">employee</span> <span class="o">=</span> <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">attributes</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">data</span>
1609
+ <span class="n">attributes</span><span class="p">.</span><span class="nf">each_pair</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
1610
+ <span class="n">employee</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="ss">="</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
1611
+ <span class="k">end</span>
1612
+ <span class="n">employee</span><span class="p">.</span><span class="nf">save</span>
1613
+ <span class="n">employee</span>
1614
+ <span class="k">end</span>
1615
+
1616
+ <span class="k">def</span> <span class="nf">destroy</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
1617
+ <span class="n">employee</span> <span class="o">=</span> <span class="no">EmployeeResource</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">attributes</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">data</span>
1618
+ <span class="n">employee</span><span class="p">.</span><span class="nf">destroy</span>
1619
+ <span class="n">employee</span>
1620
+ <span class="k">end</span></code></pre></figure>
1621
+
1622
+ <ul>
1623
+ <li>You are encouraged <strong>not</strong> to override these directly. Instead, use
1624
+ hooks (see next section).</li>
1625
+ <li>We’ll process any <code class="language-plaintext highlighter-rouge">writable: false</code> or guarded attributes prior to
1626
+ these methods.</li>
1627
+ <li>After these methods, we’ll check the Model instance for validation
1628
+ errors, rolling back the transaction if any Model in the graph is
1629
+ invalid.</li>
1630
+ <li>These methods <strong>must return the Model instance</strong>.</li>
1631
+ </ul>
1632
+
1633
+ <a class="anchor" id="persistence-lifecycle-hooks" />
1634
+ <a class="header" href="#persistence-lifecycle-hooks">
1635
+ <h3>
1636
+ 7.1 Persistence Lifecycle Hooks
1637
+ </h3>
1638
+ </a>
1639
+
1640
+ <p>Let’s dive into a persistence request. If you look at the code snippets in
1641
+ the prior section, the flow breaks down into 3 steps:</p>
1642
+
1643
+ <ul>
1644
+ <li>Build or find the model</li>
1645
+ <li>Assign attributes to the model</li>
1646
+ <li>Save</li>
1647
+ </ul>
1648
+
1649
+ <p>You can hook into each step:</p>
1650
+
1651
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
1652
+ <span class="n">before_attributes</span> <span class="k">do</span> <span class="o">|</span><span class="n">attributes</span><span class="o">|</span>
1653
+ <span class="c1"># Before attributes have been assigned to the model</span>
1654
+ <span class="k">end</span>
1655
+
1656
+ <span class="n">after_attributes</span> <span class="k">do</span> <span class="o">|</span><span class="n">model</span><span class="o">|</span>
1657
+ <span class="c1"># After attributes have been assigned to the model</span>
1658
+ <span class="k">end</span>
1659
+
1660
+ <span class="n">around_attributes</span> <span class="ss">:do_around_attributes</span>
1661
+
1662
+ <span class="k">def</span> <span class="nf">do_around_attributes</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
1663
+ <span class="c1"># before</span>
1664
+ <span class="n">model_instance</span> <span class="o">=</span> <span class="k">yield</span> <span class="n">attributes</span>
1665
+ <span class="c1"># after</span>
1666
+ <span class="k">end</span>
1667
+
1668
+ <span class="n">before_save</span> <span class="k">do</span> <span class="o">|</span><span class="n">model</span><span class="o">|</span>
1669
+ <span class="c1"># After attributes assigned, but before persisting</span>
1670
+ <span class="k">end</span>
1671
+
1672
+ <span class="n">after_save</span> <span class="k">do</span> <span class="o">|</span><span class="n">model</span><span class="o">|</span>
1673
+ <span class="c1"># After model has been saved</span>
1674
+ <span class="k">end</span>
1675
+
1676
+ <span class="n">around_save</span> <span class="ss">:do_around_save</span>
1677
+
1678
+ <span class="k">def</span> <span class="nf">do_around_save</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
1679
+ <span class="c1"># before</span>
1680
+ <span class="k">yield</span> <span class="n">model</span>
1681
+ <span class="c1"># after</span>
1682
+ <span class="k">end</span>
1683
+
1684
+ <span class="c1"># This is an *override*</span>
1685
+ <span class="c1"># During #create, build a blank model instance</span>
1686
+ <span class="c1"># By default, we'd call adapter.build(model_class)</span>
1687
+ <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="n">model_class</span><span class="p">)</span>
1688
+ <span class="n">model_class</span><span class="p">.</span><span class="nf">new</span>
1689
+ <span class="k">end</span>
1690
+
1691
+ <span class="c1"># This is an *override*</span>
1692
+ <span class="c1"># During #create/#update, assign new attributes to the model instance</span>
1693
+ <span class="c1"># By default, we'd call adapter.assign_attributes(model_instance, attributes)</span>
1694
+ <span class="k">def</span> <span class="nf">assign_attributes</span><span class="p">(</span><span class="n">model_instance</span><span class="p">,</span> <span class="n">attributes</span><span class="p">)</span>
1695
+ <span class="n">attributes</span><span class="p">.</span><span class="nf">each_pair</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
1696
+ <span class="n">model_instance</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="ss">="</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
1697
+ <span class="k">end</span>
1698
+ <span class="k">end</span>
1699
+
1700
+ <span class="c1"># This is an *override*</span>
1701
+ <span class="c1"># During #create/#update, actually save the model instance</span>
1702
+ <span class="c1"># By default, we'd call adapter.save(model_instance)</span>
1703
+ <span class="k">def</span> <span class="nf">save</span><span class="p">(</span><span class="n">model_instance</span><span class="p">)</span>
1704
+ <span class="n">model_instance</span><span class="p">.</span><span class="nf">save</span>
1705
+ <span class="n">model_instance</span>
1706
+ <span class="k">end</span>
1707
+
1708
+
1709
+ <span class="c1"># This is an *override*</span>
1710
+ <span class="c1"># During #destroy, actually save the model instance</span>
1711
+ <span class="c1"># By default, we'd call adapter.destroy(model_instance)</span>
1712
+ <span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="n">model_instance</span><span class="p">)</span>
1713
+ <span class="n">model_instance</span><span class="p">.</span><span class="nf">destroy</span>
1714
+ <span class="n">model_instance</span>
1715
+ <span class="k">end</span>
1716
+
1717
+ <span class="c1"># Finally, you may want to hook around *all* the above steps:</span>
1718
+ <span class="c1"># Only applies to #create/#update</span>
1719
+ <span class="n">around_persistence</span> <span class="ss">:do_around_persistence</span>
1720
+
1721
+ <span class="k">def</span> <span class="nf">do_around_persistence</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
1722
+ <span class="n">attributes</span><span class="p">[</span><span class="ss">:foo</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'bar'</span>
1723
+ <span class="n">model</span> <span class="o">=</span> <span class="k">yield</span> <span class="c1"># build/find, assign attrs, save</span>
1724
+ <span class="n">model</span><span class="p">.</span><span class="nf">update_counter_cache</span>
1725
+ <span class="k">end</span>
1726
+ <span class="k">end</span></code></pre></figure>
1727
+
1728
+ <ul>
1729
+ <li>All hooks have <code class="language-plaintext highlighter-rouge">only/except</code> options, e.g. <code class="language-plaintext highlighter-rouge">before_attributes only:
1730
+ [:update]</code></li>
1731
+ <li>Most hooks can be called with an in-line block, or by passing a method
1732
+ name (e.g. <code class="language-plaintext highlighter-rouge">before_attributes :do_something</code>). The exception is
1733
+ <code class="language-plaintext highlighter-rouge">around_*</code> hooks, which <em>must</em> be called with a method name.</li>
1734
+ </ul>
1735
+
1736
+ <p>When persisting multiple objects at once, we’ll open a database
1737
+ transaction, process each model individually, ensure all models pass
1738
+ validation, then close the transaction. This means that if you raise an
1739
+ error at any point, or any model does not pass validations, the
1740
+ transaction will be rolled back.</p>
1741
+
1742
+ <p>You may want to perform an operation after all models have been
1743
+ processed and validated, but before the transaction is closed. One
1744
+ example is sending an email - you don’t want to send if the models were
1745
+ invalid, so <code class="language-plaintext highlighter-rouge">after_save</code> wouldn’t work. And you still want to do it
1746
+ <em>within</em> the transaction, so if your email server is down and an error
1747
+ is raised the transaction gets rolled back.</p>
1748
+
1749
+ <p>For this scenario, use <code class="language-plaintext highlighter-rouge">before_commit</code>:</p>
1750
+
1751
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">before_commit</span> <span class="k">do</span> <span class="o">|</span><span class="n">model</span><span class="o">|</span>
1752
+ <span class="no">PostMailer</span><span class="p">.</span><span class="nf">with</span><span class="p">(</span><span class="ss">post: </span><span class="n">model</span><span class="p">).</span><span class="nf">some_email</span><span class="p">.</span><span class="nf">deliver</span>
1753
+ <span class="k">end</span></code></pre></figure>
1754
+
1755
+ <a class="anchor" id="sideposting" />
1756
+ <a class="header" href="#sideposting">
1757
+ <h3>
1758
+ 7.2 Sideposting
1759
+ </h3>
1760
+ </a>
1761
+
1762
+ <p>The act of persisting multiple Resources in a single request is called
1763
+ <strong>Sideposting</strong>. The payload mirrors the <strong>sideloading</strong> payload for
1764
+ read operations, with minor additions.</p>
1765
+
1766
+ <p>Let’s create a Post and associate it to an existing Blog in a single
1767
+ request:</p>
1768
+
1769
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># POST /api/v1/posts</span>
1770
+ <span class="p">{</span>
1771
+ <span class="ss">type: </span><span class="s1">'posts'</span><span class="p">,</span>
1772
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">title: </span><span class="s1">'My post'</span> <span class="p">},</span>
1773
+ <span class="ss">relationships: </span><span class="p">{</span>
1774
+ <span class="ss">blog: </span><span class="p">{</span>
1775
+ <span class="ss">data: </span><span class="p">{</span>
1776
+ <span class="ss">id: </span><span class="s1">'1'</span><span class="p">,</span>
1777
+ <span class="ss">type: </span><span class="s1">'blogs'</span><span class="p">,</span>
1778
+ <span class="ss">method: </span><span class="s1">'update'</span>
1779
+ <span class="p">}</span>
1780
+ <span class="p">}</span>
1781
+ <span class="p">}</span>
1782
+ <span class="p">}</span></code></pre></figure>
1783
+
1784
+ <p>The critical addition here is the <code class="language-plaintext highlighter-rouge">method</code> key. When we persist RESTful
1785
+ Resources, we send a corresponding HTTP verb. This follows the same
1786
+ pattern, adding a verb for each Resource in the graph. <code class="language-plaintext highlighter-rouge">method</code> can be
1787
+ one of:</p>
1788
+
1789
+ <ul>
1790
+ <li><code class="language-plaintext highlighter-rouge">create</code></li>
1791
+ <li><code class="language-plaintext highlighter-rouge">update</code></li>
1792
+ <li><code class="language-plaintext highlighter-rouge">destroy</code></li>
1793
+ <li><code class="language-plaintext highlighter-rouge">disassociate</code> (e.g. <code class="language-plaintext highlighter-rouge">null</code> foreign key)</li>
1794
+ </ul>
1795
+
1796
+ <p>When we sidepost, all objects will be persisted within the same database
1797
+ transaction, which rolls back if an error is raised or any objects are invalid.</p>
1798
+
1799
+ <a class="anchor" id="create" />
1800
+ <a class="header" href="#create">
1801
+ <h4>
1802
+ 7.2.1 Create
1803
+ </h4>
1804
+ </a>
1805
+
1806
+ <p>Let’s say we want to create a Post and its Blog in a single request.
1807
+ You’ll note that we don’t have the <code class="language-plaintext highlighter-rouge">id</code> key to generate a <a href="http://jsonapi.org/format/#document-resource-identifier-objects">Resource
1808
+ Identifier</a> (combination of <code class="language-plaintext highlighter-rouge">id</code> and <code class="language-plaintext highlighter-rouge">type</code>
1809
+ that uniquely identifies a Resource).</p>
1810
+
1811
+ <p>To accomodate this, send an ephemeral <code class="language-plaintext highlighter-rouge">temp-id</code> (any UUID):</p>
1812
+
1813
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
1814
+ <span class="c1"># POST /api/v1/posts</span>
1815
+ <span class="p">{</span>
1816
+ <span class="ss">type: </span><span class="s1">'posts'</span><span class="p">,</span>
1817
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">title: </span><span class="s1">'My post'</span> <span class="p">},</span>
1818
+ <span class="ss">relationships: </span><span class="p">{</span>
1819
+ <span class="ss">blog: </span><span class="p">{</span>
1820
+ <span class="ss">data: </span><span class="p">{</span>
1821
+ <span class="ss">:'temp-id'</span> <span class="o">=&gt;</span> <span class="s1">'abc123'</span><span class="p">,</span>
1822
+ <span class="ss">type: </span><span class="s1">'blogs'</span><span class="p">,</span>
1823
+ <span class="ss">method: </span><span class="s1">'create'</span>
1824
+ <span class="p">}</span>
1825
+ <span class="p">}</span>
1826
+ <span class="p">},</span>
1827
+ <span class="ss">included: </span><span class="p">[</span>
1828
+ <span class="p">{</span>
1829
+ <span class="ss">:'temp-id'</span> <span class="o">=&gt;</span> <span class="s1">'abc123'</span>
1830
+ <span class="ss">type: </span><span class="s1">'blogs'</span><span class="p">,</span>
1831
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">name: </span><span class="s1">'New Blog'</span> <span class="p">}</span>
1832
+ <span class="p">}</span>
1833
+ <span class="p">]</span>
1834
+ <span class="p">}</span>
1835
+ <span class="p">}</span></code></pre></figure>
1836
+
1837
+ <p>This random UUID:</p>
1838
+
1839
+ <ul>
1840
+ <li>Connects relevant sections of the payload.</li>
1841
+ <li>Tells clients how to associate their in-memory objects with the ids returned from the server.</li>
1842
+ </ul>
1843
+
1844
+ <a class="anchor" id="expanded-example" />
1845
+ <a class="header" href="#expanded-example">
1846
+ <h4>
1847
+ 7.2.2 Expanded Example
1848
+ </h4>
1849
+ </a>
1850
+
1851
+ <p>Here we’re updating a Post, changing the name of its associated Blog, creating a Tag, deleting one Comment, and disassociating (<code class="language-plaintext highlighter-rouge">null</code> foreign key) a different Comment, all in a single request:</p>
1852
+
1853
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
1854
+ <span class="ss">data: </span><span class="p">{</span>
1855
+ <span class="ss">type: </span><span class="s1">'posts'</span><span class="p">,</span>
1856
+ <span class="ss">id: </span><span class="mi">123</span><span class="p">,</span>
1857
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">title: </span><span class="s1">'Updated!'</span> <span class="p">},</span>
1858
+ <span class="ss">relationships: </span><span class="p">{</span>
1859
+ <span class="ss">blog: </span><span class="p">{</span>
1860
+ <span class="ss">data: </span><span class="p">{</span>
1861
+ <span class="ss">type: </span><span class="s1">'blogs'</span><span class="p">,</span>
1862
+ <span class="ss">id: </span><span class="mi">123</span><span class="p">,</span>
1863
+ <span class="ss">method: </span><span class="s1">'update'</span>
1864
+ <span class="p">}</span>
1865
+ <span class="p">},</span>
1866
+ <span class="ss">tags: </span><span class="p">{</span>
1867
+ <span class="ss">data: </span><span class="p">[{</span>
1868
+ <span class="ss">type: </span><span class="s1">'tags'</span><span class="p">,</span>
1869
+ <span class="n">temp</span><span class="o">-</span><span class="ss">id: </span><span class="s1">'s0m3uu1d'</span><span class="p">,</span>
1870
+ <span class="ss">method: </span><span class="s1">'create'</span>
1871
+ <span class="p">}]</span>
1872
+ <span class="p">},</span>
1873
+ <span class="ss">comments: </span><span class="p">{</span>
1874
+ <span class="ss">data: </span><span class="p">[</span>
1875
+ <span class="p">{</span>
1876
+ <span class="ss">type: </span><span class="s1">'comments'</span><span class="p">,</span>
1877
+ <span class="ss">id: </span><span class="s1">'123'</span><span class="p">,</span>
1878
+ <span class="ss">method: </span><span class="s1">'destroy'</span>
1879
+ <span class="p">},</span>
1880
+ <span class="p">{</span>
1881
+ <span class="ss">type: </span><span class="s1">'comments'</span><span class="p">,</span>
1882
+ <span class="ss">id: </span><span class="s1">'456'</span><span class="p">,</span>
1883
+ <span class="ss">method: </span><span class="s1">'disassociate'</span>
1884
+ <span class="p">}</span>
1885
+ <span class="p">]</span>
1886
+ <span class="p">}</span>
1887
+ <span class="p">}</span>
1888
+ <span class="p">},</span>
1889
+ <span class="ss">included: </span><span class="p">[</span>
1890
+ <span class="p">{</span>
1891
+ <span class="ss">type: </span><span class="s1">'tags'</span><span class="p">,</span>
1892
+ <span class="ss">:'temp-id'</span> <span class="o">=&gt;</span> <span class="s1">'s0m3uu1d'</span><span class="p">,</span>
1893
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">name: </span><span class="s1">'Important'</span> <span class="p">}</span>
1894
+ <span class="p">},</span>
1895
+ <span class="p">{</span>
1896
+ <span class="ss">type: </span><span class="s1">'blogs'</span><span class="p">,</span>
1897
+ <span class="ss">id: </span><span class="o">=&gt;</span> <span class="s1">'123'</span><span class="p">,</span>
1898
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="ss">name: </span><span class="s1">'Updated!'</span> <span class="p">}</span>
1899
+ <span class="p">}</span>
1900
+ <span class="p">]</span>
1901
+ <span class="p">}</span></code></pre></figure>
1902
+
1903
+ <a class="anchor" id="validation-errors" />
1904
+ <a class="header" href="#validation-errors">
1905
+ <h3>
1906
+ 7.3 Validation Errors
1907
+ </h3>
1908
+ </a>
1909
+
1910
+ <p>When a persistence operation is attempted but the corresponding Resource
1911
+ is invalid, the transaction will be rolled back and an <a href="http://jsonapi.org/format/#errors">errors payload</a> will be returned
1912
+ with a <code class="language-plaintext highlighter-rouge">422</code> response code:</p>
1913
+
1914
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
1915
+ <span class="ss">errors: </span><span class="p">[{</span>
1916
+ <span class="ss">code: </span><span class="s1">'unprocessable_entity'</span><span class="p">,</span>
1917
+ <span class="ss">status: </span><span class="s1">'422'</span><span class="p">,</span>
1918
+ <span class="ss">title: </span><span class="s2">"Validation Error"</span><span class="p">,</span>
1919
+ <span class="ss">detail: </span><span class="s2">"Title can't be blank"</span><span class="p">,</span>
1920
+ <span class="ss">source: </span><span class="p">{</span> <span class="ss">pointer: </span><span class="s1">'/data/attributes/title'</span> <span class="p">},</span>
1921
+ <span class="ss">meta: </span><span class="p">{</span>
1922
+ <span class="ss">attribute: :title</span><span class="p">,</span>
1923
+ <span class="ss">message: </span><span class="s2">"can't be blank"</span><span class="p">,</span>
1924
+ <span class="ss">code: :blank</span>
1925
+ <span class="p">}</span>
1926
+ <span class="p">}]</span>
1927
+ <span class="p">}</span></code></pre></figure>
1928
+
1929
+ <p>To get this functionality, your Model must adhere to the
1930
+ <a href="https://api.rubyonrails.org/classes/ActiveModel/Validations.html">ActiveModel::Validations API</a>.</p>
1931
+
1932
+ <p>You get this for free with ActiveRecord, or it can be mixed in to any
1933
+ PORO:</p>
1934
+
1935
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Post</span>
1936
+ <span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Validations</span>
1937
+ <span class="n">validates</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">presence: </span><span class="kp">true</span>
1938
+ <span class="k">end</span></code></pre></figure>
1939
+
1940
+ <p>Errors on associations will have a slightly expanded payload:</p>
1941
+
1942
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
1943
+ <span class="ss">errors: </span><span class="p">[{</span>
1944
+ <span class="ss">code: </span><span class="s1">'unprocessable_entity'</span><span class="p">,</span>
1945
+ <span class="ss">status: </span><span class="s1">'422'</span><span class="p">,</span>
1946
+ <span class="ss">title: </span><span class="s1">'Validation Error'</span><span class="p">,</span>
1947
+ <span class="ss">detail: </span><span class="s2">"Name can't be blank"</span><span class="p">,</span>
1948
+ <span class="ss">source: </span><span class="p">{</span> <span class="ss">pointer: </span><span class="s1">'/data/attributes/name'</span> <span class="p">},</span>
1949
+ <span class="ss">meta: </span><span class="p">{</span>
1950
+ <span class="ss">relationship: </span><span class="p">{</span>
1951
+ <span class="ss">attribute: :name</span><span class="p">,</span>
1952
+ <span class="ss">message: </span><span class="s2">"can't be blank"</span><span class="p">,</span>
1953
+ <span class="ss">code: :blank</span><span class="p">,</span>
1954
+ <span class="ss">name: :pets</span><span class="p">,</span>
1955
+ <span class="ss">id: </span><span class="s1">'444'</span><span class="p">,</span>
1956
+ <span class="ss">type: </span><span class="s1">'pets'</span>
1957
+ <span class="p">}</span>
1958
+ <span class="p">}</span>
1959
+ <span class="p">}]</span>
1960
+ <span class="p">}</span></code></pre></figure>
1961
+
1962
+ <p>When <a href="#sideposting">Sideposting</a>, the errors payload will contain all
1963
+ invalid Resources in the graph.</p>
1964
+
1965
+ <a class="anchor" id="read-on-write" />
1966
+ <a class="header" href="#read-on-write">
1967
+ <h2>
1968
+ 7.4 Read on Write
1969
+ </h2>
1970
+ </a>
1971
+
1972
+ <p>By default, the response of a persistence operation will mirror your
1973
+ request. But sometimes you need control over the response. The most
1974
+ common scenario is sideloading an additional entity - imagine creating
1975
+ an order, and wanting the order’s shipping information to come back in
1976
+ the response.</p>
1977
+
1978
+ <p>You can do this by POSTing the payload as normal, but adding query
1979
+ parameters to the URL:</p>
1980
+
1981
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># POST /api/v1/orders?include=shipping_information</span>
1982
+
1983
+ <span class="p">{</span>
1984
+ <span class="ss">type: </span><span class="s1">'orders'</span><span class="p">,</span>
1985
+ <span class="ss">attributes: </span><span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
1986
+ <span class="p">}</span></code></pre></figure>
1987
+
1988
+ <p>This will sideload the shipping information in the response. When using
1989
+ <a href="/1.13/js/index">Spraypaint</a>, do this with:</p>
1990
+
1991
+ <figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nx">order</span><span class="p">.</span><span class="nf">save</span><span class="p">({</span> <span class="na">returnScope</span><span class="p">:</span> <span class="nx">Order</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">shipping_information</span><span class="dl">'</span><span class="p">)</span> <span class="p">})</span></code></pre></figure>
1992
+
1993
+ <a class="anchor" id="context" />
1994
+ <a class="header" href="#context">
1995
+ <h2>
1996
+ 8 Context
1997
+ </h2>
1998
+ </a>
1999
+
2000
+ <p>All resources have access to <code class="language-plaintext highlighter-rouge">#context</code>. If you’re using Rails,
2001
+ <code class="language-plaintext highlighter-rouge">context</code> is the controller instance processing the request.</p>
2002
+
2003
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/post_resource.rb</span>
2004
+ <span class="n">attribute</span> <span class="ss">:active</span><span class="p">,</span> <span class="ss">:boolean</span><span class="p">,</span> <span class="ss">writable: :admin?</span>
2005
+
2006
+ <span class="k">def</span> <span class="nf">admin?</span>
2007
+ <span class="n">context</span><span class="p">.</span><span class="nf">current_user</span><span class="p">.</span><span class="nf">admin?</span>
2008
+ <span class="k">end</span></code></pre></figure>
2009
+
2010
+ <p>Because <code class="language-plaintext highlighter-rouge">current_user</code> is so common, we recommend putting this in
2011
+ <code class="language-plaintext highlighter-rouge">ApplicationResource</code>:</p>
2012
+
2013
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/application_resource.rb</span>
2014
+ <span class="k">class</span> <span class="nc">ApplicationResource</span> <span class="o">&lt;</span> <span class="no">Graphiti</span><span class="o">::</span><span class="no">Resource</span>
2015
+ <span class="c1"># ... code ...</span>
2016
+ <span class="k">def</span> <span class="nf">current_user</span>
2017
+ <span class="n">context</span><span class="p">.</span><span class="nf">current_user</span>
2018
+ <span class="k">end</span>
2019
+ <span class="k">end</span>
2020
+
2021
+ <span class="c1"># app/resources/post_resource.rb</span>
2022
+ <span class="k">class</span> <span class="nc">PostResource</span> <span class="o">&lt;</span> <span class="no">ApplicationResource</span>
2023
+ <span class="c1"># ... code ...</span>
2024
+ <span class="k">def</span> <span class="nf">admin?</span>
2025
+ <span class="n">current_user</span><span class="p">.</span><span class="nf">admin?</span>
2026
+ <span class="k">end</span>
2027
+ <span class="k">end</span></code></pre></figure>
2028
+
2029
+ <p>You can manually set context with <code class="language-plaintext highlighter-rouge">with_context</code>:</p>
2030
+
2031
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">ctx</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">current_user: </span><span class="no">User</span><span class="p">.</span><span class="nf">first</span><span class="p">)</span>
2032
+ <span class="no">Graphiti</span><span class="p">.</span><span class="nf">with_context</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span> <span class="k">do</span>
2033
+ <span class="c1"># current_user == ctx.current_user</span>
2034
+ <span class="no">PostResource</span><span class="p">.</span><span class="nf">all</span>
2035
+ <span class="k">end</span></code></pre></figure>
2036
+
2037
+ <a class="anchor" id="concurrency" />
2038
+ <a class="header" href="#concurrency">
2039
+ <h2>
2040
+ 9 Concurrency
2041
+ </h2>
2042
+ </a>
2043
+
2044
+ <p>By default when using Rails, Graphiti will turn on concurrency when <code class="language-plaintext highlighter-rouge">::Rails.application.config.cache_classes</code> is <code class="language-plaintext highlighter-rouge">true</code> (the default for staging and production environments). This will cause sibling sideloads to load concurrently. If a <code class="language-plaintext highlighter-rouge">Post</code> is sideloading <code class="language-plaintext highlighter-rouge">Comments</code> and <code class="language-plaintext highlighter-rouge">Author</code>, we’ll load both of those at the same time.</p>
2045
+
2046
+ <p>You can turn on/off this behavior explicitly:</p>
2047
+
2048
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="c1"># config/intializers/graphiti.rb</span>
2049
+ <span class="no">Graphiti</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span>
2050
+ <span class="n">c</span><span class="p">.</span><span class="nf">concurrency</span> <span class="o">=</span> <span class="kp">false</span>
2051
+ <span class="k">end</span></code></pre></figure>
2052
+
2053
+ <p><strong>NOTE</strong>: Since this kicks off a new Thread, thread locals will be dropped. So if your code refers to <code class="language-plaintext highlighter-rouge">Thread.current[:foo]</code> you should set and get that on <code class="language-plaintext highlighter-rouge">Graphiti.context</code>:</p>
2054
+
2055
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="c1"># BAD:</span>
2056
+ <span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="ss">:foo</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"bar"</span>
2057
+ <span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="ss">:foo</span><span class="p">]</span> <span class="c1"># =&gt; will be nil when sideloading!</span>
2058
+
2059
+ <span class="c1"># GOOD:</span>
2060
+ <span class="no">Graphiti</span><span class="p">.</span><span class="nf">context</span><span class="p">[</span><span class="ss">:foo</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"bar"</span>
2061
+ <span class="no">Graphiti</span><span class="p">.</span><span class="nf">context</span><span class="p">[</span><span class="ss">:foo</span><span class="p">]</span> <span class="c1"># =&gt; "bar", even when sideloading</span></code></pre></figure>
2062
+
2063
+ <a class="anchor" id="adapters" />
2064
+ <a class="header" href="#adapters">
2065
+ <h2>
2066
+ 10 Adapters
2067
+ </h2>
2068
+ </a>
2069
+
2070
+ <p>Common resource overrides can be packaged into an Adapter for code
2071
+ re-use. The most common example is using a different client/datastore
2072
+ than ActiveRecord/RelationalDB.</p>
2073
+
2074
+ <p><a href="/1.13/cookbooks/without-activerecord">Adapters are best explained in our ‘Without ActiveRecord’
2075
+ Cookbook</a>.</p>
2076
+
2077
+ <p><br />
2078
+ <br /></p>
2079
+
2080
+ </div>
2081
+
2082
+ </div>
2083
+ </div>
2084
+ </main>
2085
+ <div class="main-footer main-footer--dark">
2086
+ <div class="container">
2087
+ <div class="row">
2088
+ <div class="col-sm-4 menu">
2089
+ <h3>Overview</h3>
2090
+ <ul>
2091
+ <li>
2092
+ <a href="/1.13/quickstart">Quickstart</a>
2093
+ </li>
2094
+ <li>
2095
+ <a href="/1.13/tutorial">Tutorial</a>
2096
+ </li>
2097
+ <li>
2098
+ <a href="/1.13/guides">Guides</a>
2099
+ </li>
2100
+ </ul>
2101
+ </div>
2102
+ <div class="col-sm-4 menu">
2103
+ <h3>Contact</h3>
2104
+ <ul>
2105
+ <li>
2106
+ <a target="_blank" href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>
2107
+ </li>
2108
+ <li>
2109
+ <a href="mailto:richmolj@gmail.com">Email</a>
2110
+ </li>
2111
+ </ul>
2112
+ </div>
2113
+ <div class="col-sm-4 menu">
2114
+ <h3>Related</h3>
2115
+ <ul>
2116
+ <li>
2117
+ <a target="_blank" href="http://jsonapi.org">JSONAPI Spec</a>
2118
+ </li>
2119
+ <li>
2120
+ <a target="_blank" href="http://jsonapi-rb.org">jsonapi-rb</a>
2121
+ </li>
2122
+ <li>
2123
+ <a target="_blank" href="https://vuejs.org/">VueJS</a>
2124
+ </li>
2125
+ </ul>
2126
+ </div>
2127
+ </div>
2128
+ </div>
2129
+ </div>
2130
+
2131
+ <script type="text/javascript">
2132
+ $(function () {
2133
+
2134
+ var flipTabs = function() {
2135
+ var isTS = true;
2136
+ if (localStorage.getItem('js-lang') === 'javascript') {
2137
+ isTS = false;
2138
+ }
2139
+
2140
+ $('.code-tabs').each(function(index, el) {
2141
+ if (isTS) {
2142
+ console.log('hiding js');
2143
+ $($(el).children()[1]).hide();
2144
+ $($(el).children()[0]).show();
2145
+ } else {
2146
+ console.log('hiding ts');
2147
+ $($(el).children()[0]).hide();
2148
+ $($(el).children()[1]).show();
2149
+ }
2150
+ });
2151
+
2152
+ if (isTS) {
2153
+ $('.tab.typescript').addClass('active');
2154
+ $('.tab.javascript').removeClass('active');
2155
+ } else {
2156
+ $('.tab.typescript').removeClass('active');
2157
+ $('.tab.javascript').addClass('active');
2158
+ }
2159
+ }
2160
+
2161
+ $('.tab').click(function() {
2162
+ if ($(this).hasClass('typescript')) {
2163
+ localStorage.setItem('js-lang', 'typescript');
2164
+ } else {
2165
+ localStorage.setItem('js-lang', 'javascript');
2166
+ }
2167
+
2168
+ flipTabs();
2169
+ });
2170
+
2171
+ flipTabs();
2172
+ })
2173
+ </script>
2174
+
2175
+ </body>
2176
+ </html>