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,536 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css?family=Boogaloo" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/1.13/assets/favicons/apple-touch-icon.png">
|
|
12
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/1.13/assets/favicons/favicon-32x32.png">
|
|
13
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/1.13/assets/favicons/favicon-16x16.png">
|
|
14
|
+
<link rel="manifest" href="/1.13/assets/favicons/site.webmanifest">
|
|
15
|
+
<link rel="mask-icon" href="/1.13/assets/favicons/safari-pinned-tab.svg" color="#F86DA7">
|
|
16
|
+
<link rel="shortcut icon" href="/1.13/assets/favicons/favicon.ico">
|
|
17
|
+
<meta name="msapplication-TileColor" content="#F86DA7">
|
|
18
|
+
<meta name="msapplication-config" content="/1.13/assets/favicons/browserconfig.xml">
|
|
19
|
+
<meta name="theme-color" content="#F86DA7">
|
|
20
|
+
|
|
21
|
+
<title>Graphiti</title>
|
|
22
|
+
<meta name="description" content="Stylish Graph APIs">
|
|
23
|
+
|
|
24
|
+
<link rel="stylesheet" href="/1.13/assets/main.css?ref=wh4t3v45">
|
|
25
|
+
<link rel="alternate" type="application/rss+xml" title="Graphiti" href="/1.13/feed.xml">
|
|
26
|
+
|
|
27
|
+
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
28
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127904727-1"></script>
|
|
29
|
+
<script>
|
|
30
|
+
window.dataLayer = window.dataLayer || [];
|
|
31
|
+
function gtag(){dataLayer.push(arguments);}
|
|
32
|
+
gtag('js', new Date());
|
|
33
|
+
|
|
34
|
+
gtag('config', 'UA-127904727-1');
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<!-- javascript -->
|
|
38
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
|
39
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
</head>
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
<body>
|
|
46
|
+
<main class="page-content" aria-label="Content">
|
|
47
|
+
<div class="wrapper">
|
|
48
|
+
<header class="navbar navbar-inverse normal" role="banner">
|
|
49
|
+
<div class="container">
|
|
50
|
+
<div class="navbar-header">
|
|
51
|
+
<a href="/1.13/1.13" class="navbar-brand">
|
|
52
|
+
<img alt="logo" src="/1.13/assets/img/logo.png">
|
|
53
|
+
</a>
|
|
54
|
+
</div>
|
|
55
|
+
<nav class="" role="navigation">
|
|
56
|
+
<ul class="nav navbar-nav nav-links">
|
|
57
|
+
<li>
|
|
58
|
+
<a class="quickstart nav-link" href="/1.13/quickstart">Quickstart</a>
|
|
59
|
+
</li>
|
|
60
|
+
<li>
|
|
61
|
+
<a class="guides nav-link" href="/1.13/guides">Guides</a>
|
|
62
|
+
</li>
|
|
63
|
+
<li>
|
|
64
|
+
<a class="tutorial nav-link" href="/1.13/tutorial">Tutorial</a>
|
|
65
|
+
</li>
|
|
66
|
+
<li>
|
|
67
|
+
<a class="spraypaint nav-link" href="/1.13/js">Spraypaint</a>
|
|
68
|
+
</li>
|
|
69
|
+
</ul>
|
|
70
|
+
<ul class="nav gh navbar-nav navbar-right visible-lg visible-md">
|
|
71
|
+
<li>
|
|
72
|
+
<span class="star">⭐</span>
|
|
73
|
+
<a href="https://github.com/graphiti-api/graphiti">
|
|
74
|
+
<img alt="github-star" style="margin-right: 65px;margin-top: -20px" src="/1.13/assets/img/gh.png">
|
|
75
|
+
</a>
|
|
76
|
+
</li>
|
|
77
|
+
</ul>
|
|
78
|
+
</nav>
|
|
79
|
+
</div>
|
|
80
|
+
</header>
|
|
81
|
+
|
|
82
|
+
<div class="container">
|
|
83
|
+
<div class="toc col-md-3">
|
|
84
|
+
<h1 id="remote-resources">Remote Resources</h1>
|
|
85
|
+
|
|
86
|
+
<ul>
|
|
87
|
+
<li>1 <a href="#overview">Overview</a>
|
|
88
|
+
<ul>
|
|
89
|
+
<li><a href="#how-it-works">How It Works</a></li>
|
|
90
|
+
<li><a href="#customizing">Customizing</a></li>
|
|
91
|
+
<li><a href="#error-handling">Error Handling</a></li>
|
|
92
|
+
</ul>
|
|
93
|
+
</li>
|
|
94
|
+
<li>2 <a href="#testing">Testing</a></li>
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="col-md-8">
|
|
99
|
+
|
|
100
|
+
<a class="anchor" id="overview" />
|
|
101
|
+
<a class="header" href="#overview">
|
|
102
|
+
<h2>
|
|
103
|
+
1 Overview
|
|
104
|
+
</h2>
|
|
105
|
+
</a>
|
|
106
|
+
|
|
107
|
+
<p>Too often it seems the thinking is, “🤔 <em>We want separate services,
|
|
108
|
+
we’ll figure out our API contract along the way</em>”. Graphiti approaches
|
|
109
|
+
from the opposite angle - figure out the API contract, and you get separate services as a side-effect 😃💡</p>
|
|
110
|
+
|
|
111
|
+
<p align="center">
|
|
112
|
+
<img width="100%" src="https://labs.sogeti.com/wp-content/uploads/2016/11/microservices.gif" />
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
<p>Resources have a defined query contract, and connect together with <a href="/1.13/guides/concepts/links">Links</a>. Put two and two together, and you’ll see a Resource doesn’t need to be local to a single application. We can have Remote Resources as well:</p>
|
|
116
|
+
|
|
117
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">has_many</span> <span class="ss">:comments</span><span class="p">,</span>
|
|
118
|
+
<span class="ss">remote: </span><span class="s1">'http://blog-api.com/api/v1/comments'</span></code></pre></figure>
|
|
119
|
+
|
|
120
|
+
<p>You might want separate applications that are independently deployable,
|
|
121
|
+
or you might want to break apart that slow test suite. The draw of
|
|
122
|
+
isolated services is clear. Though you should be aware of the tradeoffs
|
|
123
|
+
when breaking apart a <a href="https://m.signalvnoise.com/the-majestic-monolith">Majestic Monolith</a>,
|
|
124
|
+
Graphiti helps you <strong><em>lessen</em></strong> those tradeoffs.</p>
|
|
125
|
+
|
|
126
|
+
<p>The most common service problem I see is a <strong>breakdown in cross-service
|
|
127
|
+
communication</strong>:</p>
|
|
128
|
+
|
|
129
|
+
<ul>
|
|
130
|
+
<li>🚫 No consistent or flexible query interface</li>
|
|
131
|
+
<li>🚫 No consistent error handling</li>
|
|
132
|
+
<li>🚫 No clear patterns on when and why to separate services</li>
|
|
133
|
+
<li>🚫 No types or backwards-compatibility checks (unless GraphQL)</li>
|
|
134
|
+
<li>🚫 A fair amount of <a href="https://www.apollographql.com/docs/graphql-tools/schema-stitching.html">glue code</a> required</li>
|
|
135
|
+
</ul>
|
|
136
|
+
|
|
137
|
+
<p>Graphiti was built from the ground up to address all these points. We
|
|
138
|
+
have a defined query contract, errors payload, a schema with types and
|
|
139
|
+
backwards-compatibility checks, and organize code into RESTful
|
|
140
|
+
Resources. Not only can we <strong><em>facilitate</em></strong> cross-service communication,
|
|
141
|
+
we can <strong><em>automate</em></strong> it.</p>
|
|
142
|
+
|
|
143
|
+
<blockquote>
|
|
144
|
+
<p>Note: Remote Resources are for <strong>read</strong> operations only. The exception
|
|
145
|
+
is associating to an existing <code class="language-plaintext highlighter-rouge">belongs_to</code> remote entity.</p>
|
|
146
|
+
</blockquote>
|
|
147
|
+
|
|
148
|
+
<blockquote>
|
|
149
|
+
<p>Note: We use <a href="https://github.com/lostisland/faraday">Faraday</a> to hit
|
|
150
|
+
the remote API. You must add <code class="language-plaintext highlighter-rouge">faraday</code> to your Gemfile to enable
|
|
151
|
+
remote resources.</p>
|
|
152
|
+
</blockquote>
|
|
153
|
+
|
|
154
|
+
<a class="anchor" id="how-it-works" />
|
|
155
|
+
<a class="header" href="#how-it-works">
|
|
156
|
+
<h3>
|
|
157
|
+
How it Works
|
|
158
|
+
</h3>
|
|
159
|
+
</a>
|
|
160
|
+
|
|
161
|
+
<p>Let’s take a simple association:</p>
|
|
162
|
+
|
|
163
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
164
|
+
<span class="n">has_many</span> <span class="ss">:comments</span>
|
|
165
|
+
<span class="k">end</span></code></pre></figure>
|
|
166
|
+
|
|
167
|
+
<p>This would generate a <a href="/1.13/guides/concepts/links">Link</a> for
|
|
168
|
+
lazy-loading comments:</p>
|
|
169
|
+
|
|
170
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
|
|
171
|
+
<span class="ss">related: </span><span class="s2">"http://my-api.com/api/v1/comments?filter[post_id]=123"</span>
|
|
172
|
+
<span class="p">}</span></code></pre></figure>
|
|
173
|
+
|
|
174
|
+
<p>Critically, <strong>those same lazy-loading parameters are used when
|
|
175
|
+
eager-loading</strong>:</p>
|
|
176
|
+
|
|
177
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># under the hood</span>
|
|
178
|
+
<span class="n">posts</span> <span class="o">=</span> <span class="no">PostResource</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">data</span>
|
|
179
|
+
<span class="no">CommentResource</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="ss">filter: </span><span class="p">{</span> <span class="ss">post_id: </span><span class="mi">123</span> <span class="p">})</span></code></pre></figure>
|
|
180
|
+
|
|
181
|
+
<p>OK, and we also know Resources support <a href="/1.13/guides/concepts/backends-and-models">any backend</a>, and we can build an <a href="/1.13/cookbooks/without-activerecord#adapters">Adapter</a> if our backend supports common operations like filtering, sorting, and pagination.</p>
|
|
182
|
+
|
|
183
|
+
<p>So, that means we can build an Adapter that makes an HTTP request to another Graphiti Resource that lives in a separate API. That adapter is built into Graphiti and comes out-of-the-box: <code class="language-plaintext highlighter-rouge">Graphiti::Adapters::GraphitiAPI</code></p>
|
|
184
|
+
|
|
185
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CommentResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
186
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">remote</span> <span class="o">=</span> <span class="s2">"http://my-api.com/api/v1/comments"</span>
|
|
187
|
+
<span class="c1"># under-the-hood, this sets:</span>
|
|
188
|
+
<span class="c1"># self.adapter = Graphiti::Adapters::GraphitiAPI</span>
|
|
189
|
+
<span class="k">end</span></code></pre></figure>
|
|
190
|
+
|
|
191
|
+
<p>This Resource works as normal. We can execute queries:</p>
|
|
192
|
+
|
|
193
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">comments</span> <span class="o">=</span> <span class="no">CommentResource</span><span class="p">.</span><span class="nf">all</span><span class="p">({</span>
|
|
194
|
+
<span class="ss">sort: </span><span class="s1">'-id'</span><span class="p">,</span>
|
|
195
|
+
<span class="ss">filter: </span><span class="p">{</span> <span class="ss">active: </span><span class="kp">true</span> <span class="p">}</span>
|
|
196
|
+
<span class="p">})</span>
|
|
197
|
+
|
|
198
|
+
<span class="c1"># The model instances are OpenStructs</span>
|
|
199
|
+
<span class="n">comments</span><span class="p">.</span><span class="nf">data</span> <span class="c1"># => [#<OpenStruct>, #<OpenStruct>, ...]</span>
|
|
200
|
+
|
|
201
|
+
<span class="c1"># Those models reflect all the properties returned from the API:</span>
|
|
202
|
+
<span class="n">comments</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:author</span><span class="p">)</span> <span class="c1"># => ["Jane Doe", "John Doe", ...]</span></code></pre></figure>
|
|
203
|
+
|
|
204
|
+
<p>And we can sideload just like we always do:</p>
|
|
205
|
+
|
|
206
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
207
|
+
<span class="c1"># Nothing to see here!</span>
|
|
208
|
+
<span class="n">has_many</span> <span class="ss">:comments</span>
|
|
209
|
+
<span class="k">end</span></code></pre></figure>
|
|
210
|
+
|
|
211
|
+
<p>We’ll still support Deep Querying - let’s fetch the Post and its
|
|
212
|
+
active comments, ordered by <code class="language-plaintext highlighter-rouge">created_at</code>:</p>
|
|
213
|
+
|
|
214
|
+
<p><code class="language-plaintext highlighter-rouge">/posts?include=comments&sort=comments.created_at&filter[active]=true</code></p>
|
|
215
|
+
|
|
216
|
+
<p>Let’s say <code class="language-plaintext highlighter-rouge">CommentResource</code> has an association to <code class="language-plaintext highlighter-rouge">Author</code>. If
|
|
217
|
+
<code class="language-plaintext highlighter-rouge">AuthorResource</code> is defined in the remote API, we can fetch it as
|
|
218
|
+
normal - no special configuration needed to fetch the <code class="language-plaintext highlighter-rouge">Post</code>, <code class="language-plaintext highlighter-rouge">Comment</code>s
|
|
219
|
+
and <code class="language-plaintext highlighter-rouge">Author</code>s in a single request.</p>
|
|
220
|
+
|
|
221
|
+
<p>But maybe only <code class="language-plaintext highlighter-rouge">CommentResource</code> is remote, and <code class="language-plaintext highlighter-rouge">Authors</code> are local.
|
|
222
|
+
We need only define the association locally:</p>
|
|
223
|
+
|
|
224
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CommentResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
225
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">remote</span> <span class="o">=</span> <span class="s2">"http://my-api.com/api/v1/comments"</span>
|
|
226
|
+
|
|
227
|
+
<span class="n">belongs_to</span> <span class="ss">:author</span>
|
|
228
|
+
<span class="k">end</span></code></pre></figure>
|
|
229
|
+
|
|
230
|
+
<p>Let’s say we need to tweak the display of a property coming from the
|
|
231
|
+
remote API. Again, works just like normal:</p>
|
|
232
|
+
|
|
233
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CommentResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
234
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">remote</span> <span class="o">=</span> <span class="s2">"http://my-api.com/api/v1/comments"</span>
|
|
235
|
+
|
|
236
|
+
<span class="n">attribute</span> <span class="ss">:body</span><span class="p">,</span> <span class="ss">:string</span> <span class="k">do</span>
|
|
237
|
+
<span class="vi">@object</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">truncate</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
|
|
238
|
+
<span class="k">end</span>
|
|
239
|
+
<span class="k">end</span></code></pre></figure>
|
|
240
|
+
|
|
241
|
+
<p>You only need to define attributes when overriding this logic -
|
|
242
|
+
otherwise we’ll take them directly from the API response. This means you
|
|
243
|
+
don’t have to update two repos and coordinate deploys - as soon as you
|
|
244
|
+
add a property to the remote API and deploy it, it will be reflected in
|
|
245
|
+
the local API response.</p>
|
|
246
|
+
|
|
247
|
+
<p>For the typical use case, we don’t even <em>need</em> to create this Resource
|
|
248
|
+
class. The sideload definition accepts a <code class="language-plaintext highlighter-rouge">remote:</code> option, which will
|
|
249
|
+
create a Remote Resource under-the-hood:</p>
|
|
250
|
+
|
|
251
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">PostResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
252
|
+
<span class="n">has_many</span> <span class="ss">:comments</span><span class="p">,</span> <span class="ss">remote: </span><span class="s1">'http://my-api.com/api/v1/comments'</span>
|
|
253
|
+
<span class="k">end</span>
|
|
254
|
+
|
|
255
|
+
<span class="c1"># Equivalent to:</span>
|
|
256
|
+
<span class="c1">#</span>
|
|
257
|
+
<span class="c1"># class PostResource < ApplicationResource</span>
|
|
258
|
+
<span class="c1"># has_many :comments</span>
|
|
259
|
+
<span class="c1"># end</span>
|
|
260
|
+
<span class="c1">#</span>
|
|
261
|
+
<span class="c1"># class CommentResource < ApplicationResource</span>
|
|
262
|
+
<span class="c1"># self.remote = 'http://my-api.com/api/v1/comments'</span>
|
|
263
|
+
<span class="c1"># end</span></code></pre></figure>
|
|
264
|
+
|
|
265
|
+
<blockquote>
|
|
266
|
+
<p>NOTE: When sending a request to a remote API, we request page size
|
|
267
|
+
<code class="language-plaintext highlighter-rouge">999</code> so results don’t get accidentally cut off. If you need
|
|
268
|
+
successive requests, please <a href="https://github.com/graphiti-api/graphiti/issues">submit an issue</a>.</p>
|
|
269
|
+
</blockquote>
|
|
270
|
+
|
|
271
|
+
<a class="anchor" id="customizing" />
|
|
272
|
+
<a class="header" href="#customizing">
|
|
273
|
+
<h3>
|
|
274
|
+
Customizing
|
|
275
|
+
</h3>
|
|
276
|
+
</a>
|
|
277
|
+
|
|
278
|
+
<p>We use <a href="https://github.com/lostisland/faraday">Faraday</a> under-the-hood,
|
|
279
|
+
which allows for various adapters and middleware. In addition:</p>
|
|
280
|
+
|
|
281
|
+
<a class="anchor" id="configure-timeout" />
|
|
282
|
+
<a class="header" href="#configure-timeout">
|
|
283
|
+
<h4>
|
|
284
|
+
Configure Timeout
|
|
285
|
+
</h4>
|
|
286
|
+
</a>
|
|
287
|
+
|
|
288
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CommentResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
289
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">remote</span> <span class="o">=</span> <span class="s2">"..."</span>
|
|
290
|
+
|
|
291
|
+
<span class="c1"># Customize faraday timeout</span>
|
|
292
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">timeout</span> <span class="o">=</span> <span class="mi">10</span>
|
|
293
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">open_timeout</span> <span class="o">=</span> <span class="mi">20</span>
|
|
294
|
+
<span class="k">end</span></code></pre></figure>
|
|
295
|
+
|
|
296
|
+
<a class="anchor" id="configure-request" />
|
|
297
|
+
<a class="header" href="#configure-request">
|
|
298
|
+
<h4>
|
|
299
|
+
Configure Request
|
|
300
|
+
</h4>
|
|
301
|
+
</a>
|
|
302
|
+
|
|
303
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CommentResource</span> <span class="o"><</span> <span class="no">ApplicationResource</span>
|
|
304
|
+
<span class="nb">self</span><span class="p">.</span><span class="nf">remote</span> <span class="o">=</span> <span class="s2">"..."</span>
|
|
305
|
+
|
|
306
|
+
<span class="k">def</span> <span class="nf">make_request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
|
|
307
|
+
<span class="c1"># request here is from Faraday:</span>
|
|
308
|
+
<span class="c1">#</span>
|
|
309
|
+
<span class="c1"># conn.get do |req|</span>
|
|
310
|
+
<span class="c1"># yield req</span>
|
|
311
|
+
<span class="c1"># end</span>
|
|
312
|
+
<span class="c1">#</span>
|
|
313
|
+
<span class="k">super</span> <span class="k">do</span> <span class="o">|</span><span class="n">request</span><span class="o">|</span>
|
|
314
|
+
<span class="n">request</span><span class="p">.</span><span class="nf">headers</span><span class="p">[</span><span class="s2">"Custom"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Header"</span>
|
|
315
|
+
<span class="k">end</span>
|
|
316
|
+
<span class="k">end</span>
|
|
317
|
+
<span class="k">end</span></code></pre></figure>
|
|
318
|
+
|
|
319
|
+
<a class="anchor" id="configure-headers" />
|
|
320
|
+
<a class="header" href="#configure-headers">
|
|
321
|
+
<h4>
|
|
322
|
+
Configure Headers
|
|
323
|
+
</h4>
|
|
324
|
+
</a>
|
|
325
|
+
|
|
326
|
+
<p>By default we’re going to <em>forward</em> the <code class="language-plaintext highlighter-rouge">Authorization</code> header of the request to the remote API. To override the default headers sent:</p>
|
|
327
|
+
|
|
328
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># app/resources/comment_resource.rb</span>
|
|
329
|
+
<span class="k">def</span> <span class="nf">request_headers</span>
|
|
330
|
+
<span class="p">{</span> <span class="s2">"Some-Foo"</span> <span class="o">=></span> <span class="s2">"bar"</span> <span class="p">}</span>
|
|
331
|
+
<span class="k">end</span></code></pre></figure>
|
|
332
|
+
|
|
333
|
+
<a class="anchor" id="error-handling" />
|
|
334
|
+
<a class="header" href="#error-handling">
|
|
335
|
+
<h3>
|
|
336
|
+
Error Handling
|
|
337
|
+
</h3>
|
|
338
|
+
</a>
|
|
339
|
+
|
|
340
|
+
<p>If the remote API has an error, we want to re-raise that same error. But
|
|
341
|
+
unless you’ve enabled <a href="/1.13/guides/concepts/error-handling#displaying-raw-errors">displaying raw errors</a>, we won’t be able to - the only information we have is what’s returned from the API.</p>
|
|
342
|
+
|
|
343
|
+
<p>You’re encouraged to display raw errors when an internal or privileged
|
|
344
|
+
user:</p>
|
|
345
|
+
|
|
346
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">rescue_from</span> <span class="no">Exception</span> <span class="k">do</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span>
|
|
347
|
+
<span class="n">handle_exception</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="ss">show_raw_error: </span><span class="n">current_user</span><span class="p">.</span><span class="nf">developer?</span><span class="p">)</span>
|
|
348
|
+
<span class="k">end</span></code></pre></figure>
|
|
349
|
+
|
|
350
|
+
<p>If you do this, we’ll be able to re-raise the original error, including
|
|
351
|
+
stacktrace. If raw errors are not enabled, we’ll raise whatever
|
|
352
|
+
information is given.</p>
|
|
353
|
+
|
|
354
|
+
<p>Both styles will be wrapped in <code class="language-plaintext highlighter-rouge">Graphiti::Errors::Remote</code>, so you can
|
|
355
|
+
differentiate between a local error and a remote one.</p>
|
|
356
|
+
|
|
357
|
+
<a class="anchor" id="testing" />
|
|
358
|
+
<a class="header" href="#testing">
|
|
359
|
+
<h2>
|
|
360
|
+
Testing
|
|
361
|
+
</h2>
|
|
362
|
+
</a>
|
|
363
|
+
|
|
364
|
+
<p>When testing a remote resource, we need to mock the API request and
|
|
365
|
+
response. Graphiti gives you a spec helper to do just that -
|
|
366
|
+
<code class="language-plaintext highlighter-rouge">include_context "remote api"</code>:</p>
|
|
367
|
+
|
|
368
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'comments'</span> <span class="k">do</span>
|
|
369
|
+
<span class="n">include_context</span> <span class="s1">'remote api'</span>
|
|
370
|
+
|
|
371
|
+
<span class="n">let</span><span class="p">(</span><span class="ss">:api_response</span><span class="p">)</span> <span class="k">do</span>
|
|
372
|
+
<span class="p">{</span>
|
|
373
|
+
<span class="ss">data: </span><span class="p">[{</span>
|
|
374
|
+
<span class="ss">id: </span><span class="s1">'1'</span><span class="p">,</span>
|
|
375
|
+
<span class="ss">type: </span><span class="s1">'comments'</span><span class="p">,</span>
|
|
376
|
+
<span class="ss">attributes: </span><span class="p">{</span> <span class="ss">body: </span><span class="s1">'hello'</span> <span class="p">}</span>
|
|
377
|
+
<span class="p">}]</span>
|
|
378
|
+
<span class="p">}</span>
|
|
379
|
+
<span class="k">end</span>
|
|
380
|
+
|
|
381
|
+
<span class="n">it</span> <span class="s1">'does something'</span> <span class="k">do</span>
|
|
382
|
+
<span class="n">url</span> <span class="o">=</span> <span class="s1">'http://my-api.com/api/v1/comments?page[size]=999'</span>
|
|
383
|
+
<span class="n">mock_api</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">api_response</span><span class="p">)</span>
|
|
384
|
+
<span class="c1"># ... test ...</span>
|
|
385
|
+
<span class="k">end</span>
|
|
386
|
+
<span class="k">end</span></code></pre></figure>
|
|
387
|
+
|
|
388
|
+
<p>This shows all the pieces needed to test remote APIs. We want to test</p>
|
|
389
|
+
|
|
390
|
+
<ul>
|
|
391
|
+
<li>The correct URL is hit</li>
|
|
392
|
+
<li>When given a valid response, the rest of the flow works as expected.</li>
|
|
393
|
+
</ul>
|
|
394
|
+
|
|
395
|
+
<blockquote>
|
|
396
|
+
<p>NOTE: if the remote relationship is a has_many, the API will need to
|
|
397
|
+
return the foreign key as part of the response. Otherwise, we won’t
|
|
398
|
+
know how to associate these children to their parents.</p>
|
|
399
|
+
</blockquote>
|
|
400
|
+
|
|
401
|
+
<p>Here’s a slightly longer version, showing that <code class="language-plaintext highlighter-rouge">Post</code> can sideload
|
|
402
|
+
<code class="language-plaintext highlighter-rouge">Comment</code>s:</p>
|
|
403
|
+
|
|
404
|
+
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="s1">'sideloading'</span> <span class="k">do</span>
|
|
405
|
+
<span class="n">describe</span> <span class="s1">'comments'</span> <span class="k">do</span>
|
|
406
|
+
<span class="n">include_context</span> <span class="s1">'remote api'</span>
|
|
407
|
+
|
|
408
|
+
<span class="n">let!</span><span class="p">(</span><span class="ss">:post</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:post</span><span class="p">)</span> <span class="p">}</span>
|
|
409
|
+
|
|
410
|
+
<span class="n">let</span><span class="p">(</span><span class="ss">:api_response</span><span class="p">)</span> <span class="k">do</span>
|
|
411
|
+
<span class="p">{</span>
|
|
412
|
+
<span class="ss">data: </span><span class="p">[{</span>
|
|
413
|
+
<span class="ss">id: </span><span class="s1">'789'</span><span class="p">,</span>
|
|
414
|
+
<span class="ss">type: </span><span class="s1">'comments'</span><span class="p">,</span>
|
|
415
|
+
<span class="ss">attributes: </span><span class="p">{</span> <span class="ss">body: </span><span class="s1">'hello'</span> <span class="p">}</span>
|
|
416
|
+
<span class="p">}]</span>
|
|
417
|
+
<span class="p">}</span>
|
|
418
|
+
<span class="k">end</span>
|
|
419
|
+
|
|
420
|
+
<span class="n">before</span> <span class="k">do</span>
|
|
421
|
+
<span class="n">params</span><span class="p">[</span><span class="ss">:include</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'comments'</span>
|
|
422
|
+
<span class="k">end</span>
|
|
423
|
+
|
|
424
|
+
<span class="n">it</span> <span class="s1">'does something'</span> <span class="k">do</span>
|
|
425
|
+
<span class="n">url</span> <span class="o">=</span> <span class="s2">"http://my-api.com/api/v1/comments"</span>
|
|
426
|
+
<span class="n">url</span> <span class="o">+=</span> <span class="s2">"?filter[post_id]=</span><span class="si">#{</span><span class="n">post_id</span><span class="si">}</span><span class="s2">"</span>
|
|
427
|
+
<span class="n">mock_api</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">api_response</span><span class="p">)</span>
|
|
428
|
+
<span class="n">render</span>
|
|
429
|
+
<span class="n">sl</span> <span class="o">=</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">sideload</span><span class="p">(</span><span class="ss">:comments</span><span class="p">)</span>
|
|
430
|
+
<span class="n">expect</span><span class="p">(</span><span class="n">sl</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:id</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="s1">'789'</span><span class="p">])</span>
|
|
431
|
+
<span class="n">expect</span><span class="p">(</span><span class="n">sl</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:jsonapi_type</span><span class="p">).</span><span class="nf">uniq</span><span class="p">)</span>
|
|
432
|
+
<span class="p">.</span><span class="nf">to</span> <span class="n">eq</span><span class="p">([</span><span class="s1">'comments'</span><span class="p">])</span>
|
|
433
|
+
<span class="k">end</span>
|
|
434
|
+
<span class="k">end</span>
|
|
435
|
+
<span class="k">end</span></code></pre></figure>
|
|
436
|
+
|
|
437
|
+
<blockquote>
|
|
438
|
+
<p>Make sure to include <code class="language-plaintext highlighter-rouge">page[size]=999</code> in the test URL!</p>
|
|
439
|
+
</blockquote>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
</main>
|
|
445
|
+
<div class="main-footer main-footer--dark">
|
|
446
|
+
<div class="container">
|
|
447
|
+
<div class="row">
|
|
448
|
+
<div class="col-sm-4 menu">
|
|
449
|
+
<h3>Overview</h3>
|
|
450
|
+
<ul>
|
|
451
|
+
<li>
|
|
452
|
+
<a href="/1.13/quickstart">Quickstart</a>
|
|
453
|
+
</li>
|
|
454
|
+
<li>
|
|
455
|
+
<a href="/1.13/tutorial">Tutorial</a>
|
|
456
|
+
</li>
|
|
457
|
+
<li>
|
|
458
|
+
<a href="/1.13/guides">Guides</a>
|
|
459
|
+
</li>
|
|
460
|
+
</ul>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="col-sm-4 menu">
|
|
463
|
+
<h3>Contact</h3>
|
|
464
|
+
<ul>
|
|
465
|
+
<li>
|
|
466
|
+
<a target="_blank" href="https://discord.gg/wgqkMBsSRV">Discord Chat</a>
|
|
467
|
+
</li>
|
|
468
|
+
<li>
|
|
469
|
+
<a href="mailto:richmolj@gmail.com">Email</a>
|
|
470
|
+
</li>
|
|
471
|
+
</ul>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="col-sm-4 menu">
|
|
474
|
+
<h3>Related</h3>
|
|
475
|
+
<ul>
|
|
476
|
+
<li>
|
|
477
|
+
<a target="_blank" href="http://jsonapi.org">JSONAPI Spec</a>
|
|
478
|
+
</li>
|
|
479
|
+
<li>
|
|
480
|
+
<a target="_blank" href="http://jsonapi-rb.org">jsonapi-rb</a>
|
|
481
|
+
</li>
|
|
482
|
+
<li>
|
|
483
|
+
<a target="_blank" href="https://vuejs.org/">VueJS</a>
|
|
484
|
+
</li>
|
|
485
|
+
</ul>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
|
|
491
|
+
<script type="text/javascript">
|
|
492
|
+
$(function () {
|
|
493
|
+
|
|
494
|
+
var flipTabs = function() {
|
|
495
|
+
var isTS = true;
|
|
496
|
+
if (localStorage.getItem('js-lang') === 'javascript') {
|
|
497
|
+
isTS = false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
$('.code-tabs').each(function(index, el) {
|
|
501
|
+
if (isTS) {
|
|
502
|
+
console.log('hiding js');
|
|
503
|
+
$($(el).children()[1]).hide();
|
|
504
|
+
$($(el).children()[0]).show();
|
|
505
|
+
} else {
|
|
506
|
+
console.log('hiding ts');
|
|
507
|
+
$($(el).children()[0]).hide();
|
|
508
|
+
$($(el).children()[1]).show();
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
if (isTS) {
|
|
513
|
+
$('.tab.typescript').addClass('active');
|
|
514
|
+
$('.tab.javascript').removeClass('active');
|
|
515
|
+
} else {
|
|
516
|
+
$('.tab.typescript').removeClass('active');
|
|
517
|
+
$('.tab.javascript').addClass('active');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
$('.tab').click(function() {
|
|
522
|
+
if ($(this).hasClass('typescript')) {
|
|
523
|
+
localStorage.setItem('js-lang', 'typescript');
|
|
524
|
+
} else {
|
|
525
|
+
localStorage.setItem('js-lang', 'javascript');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
flipTabs();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
flipTabs();
|
|
532
|
+
})
|
|
533
|
+
</script>
|
|
534
|
+
|
|
535
|
+
</body>
|
|
536
|
+
</html>
|