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,660 @@
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
+ <h1 id="quickstart">Quickstart</h1>
84
+
85
+ <a class="anchor" id="zero" />
86
+ <a class="header" href="#zero">
87
+ <h5>
88
+ Zero to API in 5 minutes
89
+ </h5>
90
+ </a>
91
+
92
+ <p>This quickstart will use Rails with ActiveRecord to give an overview of
93
+ Graphiti functionality on familiar ground. For a more in-depth breakdown, head to the
94
+ <a href="/1.13/guides"><strong>Guides</strong></a>.</p>
95
+
96
+ <p>If the below seems too “magical”, don’t worry - we’re just applying some
97
+ sensible defaults to get started quickly.</p>
98
+
99
+ <a class="anchor" id="installation" />
100
+ <a class="header" href="#installation">
101
+ <h2>
102
+ Installation
103
+ </h2>
104
+ </a>
105
+
106
+ <p>Let’s start with a classic Rails blog. We’ll use a <a href="http://guides.rubyonrails.org/rails_application_templates.html">template</a> to handle some of the boilerplate. Just run this command and accept all the defaults for now:</p>
107
+
108
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>rails new blog <span class="nt">--api</span> <span class="nt">-m</span> /1.13/template</code></pre></figure>
109
+
110
+ <p>Feel free to run <code class="language-plaintext highlighter-rouge">git diff</code> if you’re interested in the
111
+ particulars; this is mostly just installing gems and including modules.</p>
112
+
113
+ <blockquote>
114
+ <p>Note: if a network issue prevents you from pointing to this URL
115
+ directly, you can download the file and and run this command as <code class="language-plaintext highlighter-rouge">-m
116
+ /path/to/template</code></p>
117
+ </blockquote>
118
+
119
+ <p>Alternatively, you can <a href="/1.13/guides/getting-started/installation#adding-to-an-existing-app"><strong>add to an existing project</strong></a>.</p>
120
+
121
+ <a class="anchor" id="defining-a-resource" />
122
+ <a class="header" href="#defining-a-resource">
123
+ <h2>
124
+ Defining a Resource
125
+ </h2>
126
+ </a>
127
+
128
+ <p>A <a href="/1.13/guides/concepts/resources"><strong>Resource</strong></a> defines how to query and persist your <a href="/1.13/guides/concepts/backends-and-models"><strong>Model</strong></a>. In other
129
+ words: a Model is to the database as Resource is to the API. So
130
+ first, let’s define our Model:</p>
131
+
132
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails generate model Post title:string upvotes:integer active:boolean
133
+ <span class="nv">$ </span>bundle <span class="nb">exec </span>rails db:migrate</code></pre></figure>
134
+
135
+ <p>Now we can use the built-in <a href="/1.13/guides/concepts/resources#generators">generator</a> to define our Resource,
136
+ corresponding <a href="/1.13/guides/concepts/endpoints"><strong>Endpoint</strong></a>, and
137
+ <a href="/1.13/guides/concepts/testing"><strong>Integration Tests</strong></a>.</p>
138
+
139
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails g graphiti:resource Post title:string upvotes:integer active:boolean</code></pre></figure>
140
+
141
+ <p>You’ll see a number of files created. Now run your app!:</p>
142
+
143
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails s</code></pre></figure>
144
+
145
+ <p>Verify <code class="language-plaintext highlighter-rouge">http://localhost:3000/api/v1/posts</code> renders JSON correctly.
146
+ Now we just need data.</p>
147
+
148
+ <a class="anchor" id="seeding-data" />
149
+ <a class="header" href="#seeding-data">
150
+ <h5>
151
+ Seeding Data
152
+ </h5>
153
+ </a>
154
+
155
+ <p>Edit <code class="language-plaintext highlighter-rouge">db/seeds.rb</code> to create a few <code class="language-plaintext highlighter-rouge">Post</code>s:</p>
156
+
157
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Post</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">title: </span><span class="s1">'My title'</span><span class="p">,</span> <span class="ss">upvotes: </span><span class="mi">10</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">true</span><span class="p">)</span>
158
+ <span class="no">Post</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">title: </span><span class="s1">'Another title'</span><span class="p">,</span> <span class="ss">upvotes: </span><span class="mi">20</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">false</span><span class="p">)</span>
159
+ <span class="no">Post</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">title: </span><span class="s1">'OMG! A title'</span><span class="p">,</span> <span class="ss">upvotes: </span><span class="mi">30</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">true</span><span class="p">)</span></code></pre></figure>
160
+
161
+ <p>And run the script:</p>
162
+
163
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails db:seed</code></pre></figure>
164
+
165
+ <p>Now load <code class="language-plaintext highlighter-rouge">http://localhost:3000/api/v1/posts</code>. You should have 3 <code class="language-plaintext highlighter-rouge">Post</code>s in
166
+ your database!</p>
167
+
168
+ <hr />
169
+
170
+ <a class="anchor" id="querying" />
171
+ <a class="header" href="#querying">
172
+ <h2>
173
+ Querying
174
+ </h2>
175
+ </a>
176
+
177
+ <p>Now that we’ve defined our Resource and seeded some data, let’s see
178
+ what query functionality we have. We’ve listed all <code class="language-plaintext highlighter-rouge">Post</code>s at
179
+ <code class="language-plaintext highlighter-rouge">http://localhost:3000/api/v1/posts</code>. Let’s see what we can do:</p>
180
+
181
+ <ul>
182
+ <li><strong>Sort</strong>
183
+ <ul>
184
+ <li>By title, ascending:
185
+ <ul>
186
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?sort=title</code></li>
187
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts ORDER BY title ASC</code></li>
188
+ </ul>
189
+ </li>
190
+ <li>By title, descending:
191
+ <ul>
192
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?sort=-title</code></li>
193
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts ORDER BY title DESC</code></li>
194
+ </ul>
195
+ </li>
196
+ </ul>
197
+ </li>
198
+ <li><strong>Paginate</strong>:
199
+ <ul>
200
+ <li>2 Per page:
201
+ <ul>
202
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?page[size]=2</code></li>
203
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts LIMIT 2</code></li>
204
+ </ul>
205
+ </li>
206
+ <li>2 Per page, second page:
207
+ <ul>
208
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?page[size]=2&amp;page[number]=2</code></li>
209
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts LIMIT 2 OFFSET 2</code></li>
210
+ </ul>
211
+ </li>
212
+ </ul>
213
+ </li>
214
+ <li><strong>Sparse Fieldsets</strong>:
215
+ <ul>
216
+ <li>Only render <code class="language-plaintext highlighter-rouge">title</code>, not <code class="language-plaintext highlighter-rouge">active</code>:
217
+ <ul>
218
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?fields[posts]=title</code></li>
219
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * from posts</code> (<em>optimizing this query is on the roadmap</em>)</li>
220
+ </ul>
221
+ </li>
222
+ </ul>
223
+ </li>
224
+ <li><strong>Filter</strong>:
225
+ <ul>
226
+ <li>Simple:
227
+ <ul>
228
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[title]=my title</code></li>
229
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE title = "My title!"</code></li>
230
+ </ul>
231
+ </li>
232
+ <li>Case Insensitive:
233
+ <ul>
234
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[title][eql]=My title</code></li>
235
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE lower(title) = "my title!"</code></li>
236
+ </ul>
237
+ </li>
238
+ <li>Prefix:
239
+ <ul>
240
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[title][prefix]=my</code></li>
241
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE lower(title) LIKE 'my%'</code></li>
242
+ </ul>
243
+ </li>
244
+ <li>Suffix:
245
+ <ul>
246
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[title][suffix]=title</code></li>
247
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE lower(title) LIKE '%title!'</code></li>
248
+ </ul>
249
+ </li>
250
+ <li>Contains:
251
+ <ul>
252
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[title][match]=itl</code></li>
253
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE lower(title) LIKE '%itl%'</code></li>
254
+ </ul>
255
+ </li>
256
+ <li>Greater Than:
257
+ <ul>
258
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[upvotes][gt]=20</code></li>
259
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE upvotes &gt; 20</code></li>
260
+ </ul>
261
+ </li>
262
+ <li>Greater Than or Equal To:
263
+ <ul>
264
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[upvotes][gte]=20</code></li>
265
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE upvotes &gt;= 20</code></li>
266
+ </ul>
267
+ </li>
268
+ <li>Less Than:
269
+ <ul>
270
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[upvotes][lt]=20</code></li>
271
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE upvotes &lt; 20</code></li>
272
+ </ul>
273
+ </li>
274
+ <li>Less Than or Equal To:
275
+ <ul>
276
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?filter[upvotes][lte]=20</code></li>
277
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts WHERE upvotes &lt;= 20</code></li>
278
+ </ul>
279
+ </li>
280
+ <li>Any filter not whitelisted will raise <code class="language-plaintext highlighter-rouge">JsonapiCompliable::BadFilter</code>
281
+ error.</li>
282
+ <li><a href="/1.13/guides/concepts/resources#filter">All filter logic can be customized</a></li>
283
+ <li>Customizations can be DRYed up and packaged into <strong>Adapters</strong>.</li>
284
+ </ul>
285
+ </li>
286
+ <li><strong>Extra Fields</strong>:
287
+ <ul>
288
+ <li>Sometimes you want to request additional fields not part of a normal
289
+ response (perhaps they are computationally expensive).</li>
290
+ <li>This can be done like so:</li>
291
+ </ul>
292
+ </li>
293
+ </ul>
294
+
295
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/post_resource.rb</span>
296
+ <span class="n">extra_attribute</span> <span class="ss">:description</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
297
+ <span class="vi">@object</span><span class="p">.</span><span class="nf">active?</span> <span class="p">?</span> <span class="s1">'Active Post'</span> <span class="p">:</span> <span class="s1">'Inactive Post'</span>
298
+ <span class="k">end</span></code></pre></figure>
299
+
300
+ <ul>
301
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?extra_fields[posts]=description</code></li>
302
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM posts</code></li>
303
+ <li>
304
+ <p>You can conditionally eager load data or further customize this
305
+ logic.</p>
306
+ </li>
307
+ <li><strong>Statistics</strong>:
308
+ <ul>
309
+ <li>Useful for search grids - “Find me the first 10 active posts, and
310
+ the total count of all posts”.</li>
311
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?stats[total]=count</code></li>
312
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT count(*) from posts</code></li>
313
+ <li>Combine with filters and the count will adjust accordingly.</li>
314
+ <li>There are a number of built-in stats, you can also add your own.</li>
315
+ <li>
316
+ <p>This is rendered in the <code class="language-plaintext highlighter-rouge">meta</code> section of the response:</p>
317
+
318
+ <p><img src="/1.13/assets/img/meta_total_count.png" alt="meta_total_count" /></p>
319
+ </li>
320
+ <li><a href="/1.13/guides/concepts/resources#statistics">View Documentation</a></li>
321
+ </ul>
322
+ </li>
323
+ <li><strong>Error Handling</strong>:
324
+ <ul>
325
+ <li>Your app will always render a JSONAPI-compliable error response.</li>
326
+ <li>Cause an error:</li>
327
+ </ul>
328
+ </li>
329
+ </ul>
330
+
331
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/controllers/posts_controller.rb</span>
332
+ <span class="k">def</span> <span class="nf">index</span>
333
+ <span class="k">raise</span> <span class="s1">'foo'</span>
334
+ <span class="k">end</span></code></pre></figure>
335
+
336
+ <ul>
337
+ <li>
338
+ <p>The default payload you’d see in production:</p>
339
+
340
+ <p><img src="/1.13/assets/img/error_payload.png" alt="error_payload" /></p>
341
+ </li>
342
+ <li>
343
+ <p>Different errors can be customized with different response codes,
344
+ JSON, and side-effects. See more in the <a href="/1.13/guides/concepts/error-handling">Error Handling
345
+ Guide</a>.</p>
346
+ </li>
347
+ </ul>
348
+
349
+ <a class="anchor" id="persisting" />
350
+ <a class="header" href="#persisting">
351
+ <h2>
352
+ Persisting
353
+ </h2>
354
+ </a>
355
+
356
+ <p>Resources can Create, Update, and Delete (and you can persist multiple
357
+ Resources in a single request). The best way to observe this behavior is
358
+ to take a look at the tests the generator created. One example:</p>
359
+
360
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># spec/api/v1/employees/create_spec.rb</span>
361
+ <span class="n">subject</span><span class="p">(</span><span class="ss">:make_request</span><span class="p">)</span> <span class="k">do</span>
362
+ <span class="n">jsonapi_post</span> <span class="s2">"/api/v1/employees"</span><span class="p">,</span> <span class="n">payload</span>
363
+ <span class="k">end</span>
364
+
365
+ <span class="n">describe</span> <span class="s1">'basic create'</span> <span class="k">do</span>
366
+ <span class="n">let</span><span class="p">(</span><span class="ss">:payload</span><span class="p">)</span> <span class="k">do</span>
367
+ <span class="p">{</span>
368
+ <span class="ss">data: </span><span class="p">{</span>
369
+ <span class="ss">type: </span><span class="s1">'employees'</span><span class="p">,</span>
370
+ <span class="ss">attributes: </span><span class="p">{</span>
371
+ <span class="ss">first_name: </span><span class="s1">'Jane'</span>
372
+ <span class="p">}</span>
373
+ <span class="p">}</span>
374
+ <span class="p">}</span>
375
+ <span class="k">end</span>
376
+
377
+ <span class="n">it</span> <span class="s1">'works'</span> <span class="k">do</span>
378
+ <span class="n">expect</span><span class="p">(</span><span class="no">EmployeeResource</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:build</span><span class="p">).</span><span class="nf">and_call_original</span>
379
+ <span class="n">expect</span> <span class="p">{</span>
380
+ <span class="n">make_request</span>
381
+ <span class="p">}.</span><span class="nf">to</span> <span class="n">change</span> <span class="p">{</span> <span class="no">Employee</span><span class="p">.</span><span class="nf">count</span> <span class="p">}.</span><span class="nf">by</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
382
+ <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">201</span><span class="p">)</span>
383
+ <span class="k">end</span>
384
+ <span class="k">end</span></code></pre></figure>
385
+
386
+ <p>Read more about <a href="/1.13/guides/concepts/resources#persisting">Persistence</a> and
387
+ <a href="/1.13/guides/concepts/testing#writes">Testing Persistence</a>.</p>
388
+
389
+ <a class="anchor" id="adding-relationships" />
390
+ <a class="header" href="#adding-relationships">
391
+ <h2>
392
+ Adding Relationships
393
+ </h2>
394
+ </a>
395
+
396
+ <p>Let’s start by defining our Model:</p>
397
+
398
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails g model Comment post_id:integer body:text active:boolean
399
+ <span class="nv">$ </span>bundle <span class="nb">exec </span>rails db:migrate</code></pre></figure>
400
+
401
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/models/post.rb</span>
402
+ <span class="n">has_many</span> <span class="ss">:comments</span>
403
+
404
+ <span class="c1"># app/models/comment.rb</span>
405
+ <span class="n">belongs_to</span> <span class="ss">:post</span></code></pre></figure>
406
+
407
+ <p>…and corresponding Resource object:</p>
408
+
409
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails g graphiti:resource Comment body:string active:boolean created_at:datetime</code></pre></figure>
410
+
411
+ <p>Configure the relationship in <code class="language-plaintext highlighter-rouge">PostResource</code>:</p>
412
+
413
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/post_resource.rb</span>
414
+ <span class="n">has_many</span> <span class="ss">:comments</span></code></pre></figure>
415
+
416
+ <p>And allow filtering Comments based on the Post <code class="language-plaintext highlighter-rouge">id</code>:</p>
417
+
418
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/comment_resource.rb</span>
419
+ <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></code></pre></figure>
420
+
421
+ <p>This code:</p>
422
+
423
+ <ul>
424
+ <li>Allows eager-loading the relationship.
425
+ <ul>
426
+ <li>URL: <code class="language-plaintext highlighter-rouge">/api/v1/posts?include=comments</code></li>
427
+ <li>SQL: <code class="language-plaintext highlighter-rouge">SELECT * FROM comments WHERE post_id = 123</code></li>
428
+ </ul>
429
+ </li>
430
+ <li>Generates a <a href="/1.13/guides/concepts/links"><strong>Link</strong></a> for
431
+ lazy-loading.</li>
432
+ <li>Will use <code class="language-plaintext highlighter-rouge">CommentResource</code> for querying logic (so we can <a href="/1.13/guides/concepts/resources#deep-queries">Deep
433
+ Query</a>, e.g.
434
+ “only return the latest 3 active comments”).</li>
435
+ <li>By default, this will generate the query <code class="language-plaintext highlighter-rouge">CommentResource.all(filter:
436
+ { post_id: 123 })</code>, but <a href="/1.13/guides/concepts/resources#relationships">relationships can be customized</a></li>
437
+ </ul>
438
+
439
+ <p>You should now be able to hit <code class="language-plaintext highlighter-rouge">/api/v1/comments</code> with all the same
440
+ functionality as before. We just need to seed data.</p>
441
+
442
+ <a class="anchor" id="seeding-relationships" />
443
+ <a class="header" href="#seeding-relationships">
444
+ <h4>
445
+ Seeding Relationships
446
+ </h4>
447
+ </a>
448
+
449
+ <p>Start by clearing out your database:</p>
450
+
451
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails db:migrate:reset</code></pre></figure>
452
+
453
+ <p>Replace your <code class="language-plaintext highlighter-rouge">db/seeds.rb</code> with this code to persist one <code class="language-plaintext highlighter-rouge">Post</code> and
454
+ three <code class="language-plaintext highlighter-rouge">Comment</code>s:</p>
455
+
456
+ <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">comment1</span> <span class="o">=</span> <span class="no">Comment</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">body: </span><span class="s1">'comment one'</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">true</span><span class="p">)</span>
457
+ <span class="n">comment2</span> <span class="o">=</span> <span class="no">Comment</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">body: </span><span class="s1">'comment two'</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">false</span><span class="p">)</span>
458
+ <span class="n">comment3</span> <span class="o">=</span> <span class="no">Comment</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">body: </span><span class="s1">'comment three'</span><span class="p">,</span> <span class="ss">active: </span><span class="kp">true</span><span class="p">)</span>
459
+
460
+ <span class="no">Post</span><span class="p">.</span><span class="nf">create!</span> <span class="p">\</span>
461
+ <span class="ss">title: </span><span class="s1">'My title!'</span><span class="p">,</span>
462
+ <span class="ss">active: </span><span class="kp">true</span><span class="p">,</span>
463
+ <span class="ss">comments: </span><span class="p">[</span><span class="n">comment1</span><span class="p">,</span> <span class="n">comment2</span><span class="p">,</span> <span class="n">comment3</span><span class="p">]</span></code></pre></figure>
464
+
465
+ <p>And run it:</p>
466
+
467
+ <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>bundle <span class="nb">exec </span>rails db:seed</code></pre></figure>
468
+
469
+ <a class="anchor" id="relationship-usage" />
470
+ <a class="header" href="#relationship-usage">
471
+ <h2>
472
+ Relationship Usage
473
+ </h2>
474
+ </a>
475
+
476
+ <p>Now let’s fetch a <code class="language-plaintext highlighter-rouge">Post</code> and filtered <code class="language-plaintext highlighter-rouge">Comment</code>s in a single request:</p>
477
+
478
+ <p><code class="language-plaintext highlighter-rouge">/api/v1/posts?include=comments</code></p>
479
+
480
+ <p>Any logic in <code class="language-plaintext highlighter-rouge">CommentResource</code> is available to us. Let’s sort the
481
+ comments by <code class="language-plaintext highlighter-rouge">created_at</code> descending:</p>
482
+
483
+ <p><code class="language-plaintext highlighter-rouge">/api/v1/posts?include=comments&amp;sort=-comments.created_at</code>.</p>
484
+
485
+ <p>Logic from <code class="language-plaintext highlighter-rouge">CommentResource</code> is accessible at the <code class="language-plaintext highlighter-rouge">/api/v1/comments</code>
486
+ endpoint, and reusable when eager-loading Comments at <code class="language-plaintext highlighter-rouge">/api/v1/posts:</code></p>
487
+
488
+ <ul>
489
+ <li><code class="language-plaintext highlighter-rouge">/api/v1/comments?filter[active]=true</code></li>
490
+ <li><code class="language-plaintext highlighter-rouge">/api/v1/posts?include=comments&amp;filter[comments.active]=true</code></li>
491
+ </ul>
492
+
493
+ <p>This is why Resource objects exist: they provide an interface to
494
+ reuse code across multiple Endpoints.</p>
495
+
496
+ <p>Also note: just as we can query a graph of Resources in a single
497
+ request, we can <em>persist</em> a graph of Resources in a single request. See
498
+ <a href="/1.13/guides/concepts/resources#sideposting">Sideposting</a>.</p>
499
+
500
+ <a class="anchor" id="exploring-with-vandal" />
501
+ <a class="header" href="#exploring-with-vandal">
502
+ <h2>
503
+ Exploring with Vandal
504
+ </h2>
505
+ </a>
506
+
507
+ <p align="center">
508
+ <img width="100%" src="https://user-images.githubusercontent.com/55264/50739987-cb5af780-11b5-11e9-9839-cdb913dafbd1.png" />
509
+ </p>
510
+
511
+ <p>Vandal is the Graphiti UI. It introspects your schema to make data
512
+ exploration a breeze. The above screenshot shows Vandal fetching posts
513
+ and comments from our blog.</p>
514
+
515
+ <p>To run Vandal, hit <code class="language-plaintext highlighter-rouge">http://localhost:3000/api/v1/vandal</code>. Click a
516
+ relationship once to include it in the response, click a second time to
517
+ edit deep query logic for the associated Resource.</p>
518
+
519
+ <p>If you’ve included an association, you can click a table row to view associated data.
520
+ The below screenshot is hitting <code class="language-plaintext highlighter-rouge">/posts</code>, and including only comments
521
+ with the text “two”:</p>
522
+
523
+ <p align="center">
524
+ <img width="100%" src="https://user-images.githubusercontent.com/55264/50739988-cb5af780-11b5-11e9-81ad-2e28f6fa5ac2.png" />
525
+ </p>
526
+
527
+ <p><a href="https://jsonapi-employee-directory.herokuapp.com/vandal">Click here</a> to play with Vandal hitting our <a href="/1.13/tutorial">Employee Directory Tutorial</a>. Initial load might take longer as it runs on a free Heroku instance.</p>
528
+
529
+ <a class="anchor" id="whats-next" />
530
+ <a class="header" href="#whats-next">
531
+ <h2>
532
+ What's next
533
+ </h2>
534
+ </a>
535
+
536
+ <p>We have a full CRUD API with robust querying functionality, and the
537
+ ability to combine relationships for both reads and writes. But what
538
+ happens when you need to customize the sorting logic? What about replacing
539
+ <code class="language-plaintext highlighter-rouge">ActiveRecord</code> with an alternate persistence layer, or avoiding Rails
540
+ altogether?</p>
541
+
542
+ <p>These are important topics that Graphiti was built to address. To
543
+ learn more about advanced usage and customization, we suggest following
544
+ the <a href="/1.13/tutorial">Tutorial</a> and reading through the
545
+ <a href="/1.13/guides">Guides</a>.</p>
546
+
547
+ <p>For help with specific use cases, <a href="https://discord.gg/wgqkMBsSRV">join our Discord chat</a>!</p>
548
+
549
+ <a class="anchor" id="testing" />
550
+ <a class="header" href="#testing">
551
+ <h2>
552
+ Testing
553
+ </h2>
554
+ </a>
555
+
556
+ <p>This Quickstart is meant to get you up-and-running quickly, so we didn’t
557
+ write tests. But in Graphiti <strong>testing is the easiest, most pleasant way
558
+ to develop your application</strong>.</p>
559
+
560
+ <p>Even if you’re not normally a TDDer, we highly recommend reading through
561
+ our <a href="/1.13/guides/concepts/testing">Integration Testing Guide</a>.</p>
562
+
563
+ <p><br />
564
+ <br /></p>
565
+
566
+ </div>
567
+ </div>
568
+ </main>
569
+ <div class="main-footer main-footer--dark">
570
+ <div class="container">
571
+ <div class="row">
572
+ <div class="col-sm-4 menu">
573
+ <h3>Overview</h3>
574
+ <ul>
575
+ <li>
576
+ <a href="/1.13/quickstart">Quickstart</a>
577
+ </li>
578
+ <li>
579
+ <a href="/1.13/tutorial">Tutorial</a>
580
+ </li>
581
+ <li>
582
+ <a href="/1.13/guides">Guides</a>
583
+ </li>
584
+ </ul>
585
+ </div>
586
+ <div class="col-sm-4 menu">
587
+ <h3>Contact</h3>
588
+ <ul>
589
+ <li>
590
+ <a target="_blank" href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>
591
+ </li>
592
+ <li>
593
+ <a href="mailto:richmolj@gmail.com">Email</a>
594
+ </li>
595
+ </ul>
596
+ </div>
597
+ <div class="col-sm-4 menu">
598
+ <h3>Related</h3>
599
+ <ul>
600
+ <li>
601
+ <a target="_blank" href="http://jsonapi.org">JSONAPI Spec</a>
602
+ </li>
603
+ <li>
604
+ <a target="_blank" href="http://jsonapi-rb.org">jsonapi-rb</a>
605
+ </li>
606
+ <li>
607
+ <a target="_blank" href="https://vuejs.org/">VueJS</a>
608
+ </li>
609
+ </ul>
610
+ </div>
611
+ </div>
612
+ </div>
613
+ </div>
614
+
615
+ <script type="text/javascript">
616
+ $(function () {
617
+
618
+ var flipTabs = function() {
619
+ var isTS = true;
620
+ if (localStorage.getItem('js-lang') === 'javascript') {
621
+ isTS = false;
622
+ }
623
+
624
+ $('.code-tabs').each(function(index, el) {
625
+ if (isTS) {
626
+ console.log('hiding js');
627
+ $($(el).children()[1]).hide();
628
+ $($(el).children()[0]).show();
629
+ } else {
630
+ console.log('hiding ts');
631
+ $($(el).children()[0]).hide();
632
+ $($(el).children()[1]).show();
633
+ }
634
+ });
635
+
636
+ if (isTS) {
637
+ $('.tab.typescript').addClass('active');
638
+ $('.tab.javascript').removeClass('active');
639
+ } else {
640
+ $('.tab.typescript').removeClass('active');
641
+ $('.tab.javascript').addClass('active');
642
+ }
643
+ }
644
+
645
+ $('.tab').click(function() {
646
+ if ($(this).hasClass('typescript')) {
647
+ localStorage.setItem('js-lang', 'typescript');
648
+ } else {
649
+ localStorage.setItem('js-lang', 'javascript');
650
+ }
651
+
652
+ flipTabs();
653
+ });
654
+
655
+ flipTabs();
656
+ })
657
+ </script>
658
+
659
+ </body>
660
+ </html>