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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -86
- data/.github/workflows/docs.yml +60 -0
- data/.github/workflows/release.yml +8 -8
- data/.gitignore +7 -0
- data/.npmrc +9 -0
- data/.standard.yml +4 -1
- data/Appraisals +33 -32
- data/CHANGELOG.md +41 -0
- data/README.md +13 -2
- data/UPGRADING.md +2 -68
- data/docs/concepts/backends-and-models.md +122 -0
- data/docs/concepts/endpoints.md +183 -0
- data/docs/concepts/links.md +212 -0
- data/docs/concepts/overview.md +80 -0
- data/docs/concepts/persisting.md +376 -0
- data/docs/concepts/relationships.md +527 -0
- data/docs/concepts/resources.md +677 -0
- data/docs/getting-started/first-api.md +289 -0
- data/docs/getting-started/installation.md +185 -0
- data/docs/intro.md +307 -0
- data/docs/js/authentication.md +63 -0
- data/docs/js/ddau.md +20 -0
- data/docs/js/extra-params.md +41 -0
- data/docs/js/index.md +112 -0
- data/docs/js/installation.md +120 -0
- data/docs/js/middleware.md +72 -0
- data/docs/js/models.md +202 -0
- data/docs/js/reads.md +494 -0
- data/docs/js/state-syncing.md +100 -0
- data/docs/js/writes.md +373 -0
- data/docs/reference/vandal.md +63 -0
- data/docs/reference/why.md +13 -0
- data/docs/topics/authorization.md +155 -0
- data/docs/topics/caching.md +55 -0
- data/docs/topics/customizing-sideloads.md +156 -0
- data/docs/topics/debugging.md +216 -0
- data/docs/topics/error-handling.md +210 -0
- data/docs/topics/etags.md +46 -0
- data/docs/topics/hopping-relationships.md +149 -0
- data/docs/topics/json-attributes.md +77 -0
- data/docs/topics/openstruct-models.md +50 -0
- data/docs/topics/remote-resources.md +291 -0
- data/docs/topics/testing.md +894 -0
- data/docs/topics/without-activerecord.md +324 -0
- data/docs/tutorial/index.md +58 -0
- data/docs/tutorial/step_0.md +107 -0
- data/docs/tutorial/step_1.md +199 -0
- data/docs/tutorial/step_2.md +312 -0
- data/docs/tutorial/step_3.md +142 -0
- data/docs/tutorial/step_4.md +135 -0
- data/docs/tutorial/step_5.md +69 -0
- data/docs/tutorial/step_6.md +82 -0
- data/docs/tutorial/step_7.md +205 -0
- data/docs/tutorial/step_8.md +128 -0
- data/docs/tutorial/step_9.md +171 -0
- data/docs/upgrading.md +265 -0
- data/gemfiles/rails_7_1.gemfile +4 -3
- data/gemfiles/{rails_7_2_graphiti_rails.gemfile → rails_7_2.gemfile} +3 -3
- data/gemfiles/{rails_8_1_graphiti_rails.gemfile → rails_8_0.gemfile} +3 -3
- data/gemfiles/{rails_8_0_graphiti_rails.gemfile → rails_8_1.gemfile} +3 -3
- data/graphiti.gemspec +7 -5
- data/{deprecated_generators → lib/generators}/graphiti/api_test_generator.rb +7 -1
- data/{deprecated_generators → lib/generators}/graphiti/generator_mixin.rb +14 -1
- data/{deprecated_generators → lib/generators}/graphiti/install_generator.rb +19 -13
- data/{deprecated_generators → lib/generators}/graphiti/resource_generator.rb +43 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/index_request_spec.rb.erb +1 -1
- data/{deprecated_generators → lib/generators}/graphiti/templates/resource_reads_spec.rb.erb +6 -6
- data/{deprecated_generators → lib/generators}/graphiti/templates/show_request_spec.rb.erb +1 -1
- data/lib/graphiti/configuration.rb +2 -2
- data/lib/graphiti/error_serializers/conflict_request.rb +19 -0
- data/lib/graphiti/error_serializers/deprecated_constants.rb +48 -0
- data/lib/graphiti/error_serializers/invalid_request.rb +56 -0
- data/lib/graphiti/error_serializers/validation.rb +143 -0
- data/lib/graphiti/errors.rb +4 -23
- data/lib/graphiti/query.rb +1 -1
- data/lib/graphiti/rails/context.rb +33 -0
- data/lib/graphiti/rails/controller.rb +41 -0
- data/lib/graphiti/rails/debugging.rb +18 -0
- data/lib/graphiti/rails/exception_handlers.rb +77 -0
- data/lib/graphiti/rails/railtie.rb +139 -0
- data/lib/graphiti/rails/responders.rb +21 -0
- data/lib/graphiti/rails/test_helpers.rb +22 -0
- data/lib/graphiti/rails.rb +47 -29
- data/lib/graphiti/resource/configuration.rb +1 -0
- data/lib/graphiti/resource/interface.rb +2 -2
- data/lib/graphiti/resource/persistence.rb +14 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +1 -1
- data/lib/graphiti/resource.rb +13 -1
- data/lib/graphiti/responders.rb +7 -20
- data/lib/graphiti/schema.rb +5 -1
- data/lib/graphiti/schema_diff.rb +4 -0
- data/lib/graphiti/scope.rb +45 -37
- data/lib/graphiti/serializer.rb +6 -0
- data/lib/graphiti/sideload/belongs_to.rb +38 -5
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +27 -23
- data/lib/graphiti/sideload.rb +54 -35
- data/lib/graphiti/spec_helpers/errors.rb +73 -0
- data/lib/graphiti/spec_helpers/errors_proxy.rb +75 -0
- data/lib/graphiti/spec_helpers/helpers.rb +107 -0
- data/lib/graphiti/spec_helpers/node.rb +88 -0
- data/lib/graphiti/spec_helpers/rspec.rb +147 -0
- data/lib/graphiti/spec_helpers.rb +53 -0
- data/lib/graphiti/util/include_params.rb +2 -2
- data/lib/graphiti/util/persistence.rb +10 -11
- data/lib/graphiti/util/serializer_relationships.rb +41 -5
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti-rails.rb +11 -0
- data/lib/graphiti.rb +34 -10
- data/lib/graphiti_errors.rb +11 -0
- data/lib/graphiti_spec_helpers/rspec.rb +3 -0
- data/lib/graphiti_spec_helpers.rb +11 -0
- data/lib/{graphiti/deprecated_tasks.rb → tasks/graphiti.rake} +6 -1
- data/package-lock.json +6199 -0
- data/package.json +5 -4
- data/website/.gitignore +20 -0
- data/website/README.md +43 -0
- data/website/docusaurus.config.js +141 -0
- data/website/package-lock.json +19474 -0
- data/website/package.json +46 -0
- data/website/sidebars.js +82 -0
- data/website/src/css/custom.css +58 -0
- data/website/src/pages/markdown-page.mdx +7 -0
- data/website/static/.nojekyll +0 -0
- data/website/static/1.13/2019/03/31/graphiti-1-0.html +205 -0
- data/website/static/1.13/2019/05/08/graphiti-1-1.html +212 -0
- data/website/static/1.13/2019/05/20/graphiti-1-2.html +214 -0
- data/website/static/1.13/2019/10/14/tutorial.html +198 -0
- data/website/static/1.13/CNAME +1 -0
- data/website/static/1.13/README.md +16 -0
- data/website/static/1.13/assets/css/syntax.css +60 -0
- data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
- data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
- data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
- data/website/static/1.13/assets/favicons/browserconfig.xml +9 -0
- data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
- data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
- data/website/static/1.13/assets/favicons/favicon.ico +0 -0
- data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
- data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +1 -0
- data/website/static/1.13/assets/favicons/site.webmanifest +19 -0
- data/website/static/1.13/assets/img/backend.gif +0 -0
- data/website/static/1.13/assets/img/conformity.png +0 -0
- data/website/static/1.13/assets/img/error_payload.png +0 -0
- data/website/static/1.13/assets/img/gh.png +0 -0
- data/website/static/1.13/assets/img/lifecycle.gif +0 -0
- data/website/static/1.13/assets/img/logo-500.png +0 -0
- data/website/static/1.13/assets/img/logo.png +0 -0
- data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/meta_total_count.png +0 -0
- data/website/static/1.13/assets/img/persist.jpg +0 -0
- data/website/static/1.13/assets/img/resource.gif +0 -0
- data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/rest1.gif +0 -0
- data/website/static/1.13/assets/img/rest2.gif +0 -0
- data/website/static/1.13/assets/img/rest3.gif +0 -0
- data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
- data/website/static/1.13/assets/img/why.png +0 -0
- data/website/static/1.13/assets/js/highlight.pack.js +2 -0
- data/website/static/1.13/assets/main.css +15518 -0
- data/website/static/1.13/assets/main.css.map +1 -0
- data/website/static/1.13/bin/bundle +109 -0
- data/website/static/1.13/bin/jekyll +27 -0
- data/website/static/1.13/bin/kramdown +27 -0
- data/website/static/1.13/bin/listen +27 -0
- data/website/static/1.13/bin/rake +27 -0
- data/website/static/1.13/bin/rougify +27 -0
- data/website/static/1.13/bin/safe_yaml +27 -0
- data/website/static/1.13/bin/sass +27 -0
- data/website/static/1.13/bin/sass-convert +27 -0
- data/website/static/1.13/bin/scss +27 -0
- data/website/static/1.13/blog.html +259 -0
- data/website/static/1.13/cheatsheet.html +316 -0
- data/website/static/1.13/cookbooks/authorization.md +0 -0
- data/website/static/1.13/cookbooks/caching.md +0 -0
- data/website/static/1.13/cookbooks/customizing-sideloads.html +325 -0
- data/website/static/1.13/cookbooks/etags.md +0 -0
- data/website/static/1.13/cookbooks/hopping-relationships.html +324 -0
- data/website/static/1.13/cookbooks/json_attributes.md +0 -0
- data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
- data/website/static/1.13/cookbooks/remote-resources.md +0 -0
- data/website/static/1.13/cookbooks/without-activerecord.html +510 -0
- data/website/static/1.13/features.html +249 -0
- data/website/static/1.13/feed.xml +106 -0
- data/website/static/1.13/guides/concepts/backends-and-models.html +467 -0
- data/website/static/1.13/guides/concepts/debugging.html +440 -0
- data/website/static/1.13/guides/concepts/endpoints.html +432 -0
- data/website/static/1.13/guides/concepts/error-handling.html +396 -0
- data/website/static/1.13/guides/concepts/links.html +501 -0
- data/website/static/1.13/guides/concepts/remote-resources.html +536 -0
- data/website/static/1.13/guides/concepts/resources.html +2176 -0
- data/website/static/1.13/guides/concepts/testing.html +1469 -0
- data/website/static/1.13/guides/getting-started/installation.html +420 -0
- data/website/static/1.13/guides/graphiti-rails-migration.html +242 -0
- data/website/static/1.13/guides/index.html +269 -0
- data/website/static/1.13/guides/overview.html +325 -0
- data/website/static/1.13/guides/upgrading-2-0.html +193 -0
- data/website/static/1.13/guides/upgrading.html +314 -0
- data/website/static/1.13/guides/vandal.html +282 -0
- data/website/static/1.13/guides/why.html +1121 -0
- data/website/static/1.13/index.html +72 -0
- data/website/static/1.13/js/authentication.html +295 -0
- data/website/static/1.13/js/ddau.html +238 -0
- data/website/static/1.13/js/extra-params.html +270 -0
- data/website/static/1.13/js/index.html +321 -0
- data/website/static/1.13/js/installation.html +637 -0
- data/website/static/1.13/js/introduction.html +257 -0
- data/website/static/1.13/js/middleware.html +318 -0
- data/website/static/1.13/js/reads/fieldsets.html +271 -0
- data/website/static/1.13/js/reads/filtering.html +289 -0
- data/website/static/1.13/js/reads/includes.html +260 -0
- data/website/static/1.13/js/reads/index.html +497 -0
- data/website/static/1.13/js/reads/nested-queries.html +353 -0
- data/website/static/1.13/js/reads/pagination.html +260 -0
- data/website/static/1.13/js/reads/sorting.html +265 -0
- data/website/static/1.13/js/reads/statistics.html +289 -0
- data/website/static/1.13/js/state-syncing.html +340 -0
- data/website/static/1.13/js/writes/deferred.html +296 -0
- data/website/static/1.13/js/writes/dirty-tracking.html +399 -0
- data/website/static/1.13/js/writes/index.html +391 -0
- data/website/static/1.13/js/writes/nested.html +330 -0
- data/website/static/1.13/js/writes/validations.html +272 -0
- data/website/static/1.13/quickstart.html +660 -0
- data/website/static/1.13/template +161 -0
- data/website/static/1.13/tutorial/index.html +250 -0
- data/website/static/1.13/tutorial/step_0.html +292 -0
- data/website/static/1.13/tutorial/step_1.html +517 -0
- data/website/static/1.13/tutorial/step_2.html +481 -0
- data/website/static/1.13/tutorial/step_3.html +323 -0
- data/website/static/1.13/tutorial/step_4.html +318 -0
- data/website/static/1.13/tutorial/step_5.html +265 -0
- data/website/static/1.13/tutorial/step_6.html +276 -0
- data/website/static/1.13/tutorial/step_7.html +390 -0
- data/website/static/1.13/tutorial/step_8.html +316 -0
- data/website/static/1.13/tutorial/step_9.html +365 -0
- data/website/static/assets/img/error_payload.png +0 -0
- data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
- data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
- data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
- data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
- data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
- data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
- data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
- data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
- data/website/static/assets/img/meta_total_count.png +0 -0
- data/website/static/img/docusaurus-social-card.jpg +0 -0
- data/website/static/img/docusaurus.png +0 -0
- data/website/static/img/favicon.ico +0 -0
- data/website/static/img/logo.png +0 -0
- data/website/static/img/logo.svg +1 -0
- data/website/static/img/undraw_docusaurus_mountain.svg +171 -0
- data/website/static/img/undraw_docusaurus_react.svg +170 -0
- data/website/static/img/undraw_docusaurus_tree.svg +40 -0
- metadata +245 -46
- data/gemfiles/rails_6.gemfile +0 -18
- data/gemfiles/rails_6_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7.gemfile +0 -18
- data/gemfiles/rails_7_1_graphiti_rails.gemfile +0 -19
- data/gemfiles/rails_7_graphiti_rails.gemfile +0 -19
- data/lib/graphiti/railtie.rb +0 -121
- /data/{deprecated_generators → lib/generators}/graphiti/resource_test_generator.rb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/application_resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/controller.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/create_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/destroy_request_spec.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource.rb.erb +0 -0
- /data/{deprecated_generators → lib/generators}/graphiti/templates/resource_writes_spec.rb.erb +0 -0
- /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&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 > 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 >= 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 < 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 <= 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&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&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>
|