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