graphiti 2.0.0.beta.6 → 2.0.0.beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +55 -3
  4. data/.github/workflows/docs.yml +1 -1
  5. data/.github/workflows/notify-example-app.yml +18 -0
  6. data/.gitignore +2 -0
  7. data/.standard.yml +6 -0
  8. data/CHANGELOG.md +54 -0
  9. data/CONTRIBUTING.md +3 -1
  10. data/README.md +13 -206
  11. data/Rakefile +6 -3
  12. data/UPGRADING.md +1 -1
  13. data/graphiti.gemspec +8 -4
  14. data/lib/generators/graphiti/generator_mixin.rb +1 -1
  15. data/lib/generators/graphiti/install_generator.rb +1 -2
  16. data/lib/generators/graphiti/resource_generator.rb +6 -3
  17. data/lib/generators/graphiti/templates/application_resource.rb.erb +19 -0
  18. data/lib/generators/graphiti/templates/controller.rb.erb +26 -8
  19. data/lib/generators/graphiti/templates/index_request_spec.rb.erb +2 -2
  20. data/lib/generators/graphiti/templates/resource_reads_spec.rb.erb +4 -4
  21. data/lib/generators/graphiti/templates/show_request_spec.rb.erb +2 -2
  22. data/lib/graphiti/audit/report.rb +6 -6
  23. data/lib/graphiti/audit.rb +3 -4
  24. data/lib/graphiti/delegates/pagination.rb +2 -2
  25. data/lib/graphiti/deserializer.rb +3 -3
  26. data/lib/graphiti/errors.rb +6 -6
  27. data/lib/graphiti/extensions/temp_id.rb +1 -1
  28. data/lib/graphiti/query.rb +27 -8
  29. data/lib/graphiti/rails/context.rb +2 -1
  30. data/lib/graphiti/rails/controller.rb +3 -0
  31. data/lib/graphiti/rails/debugging.rb +1 -1
  32. data/lib/graphiti/rails/responders.rb +2 -1
  33. data/lib/graphiti/resource/configuration.rb +1 -0
  34. data/lib/graphiti/resource/dsl.rb +2 -2
  35. data/lib/graphiti/resource/interface.rb +3 -3
  36. data/lib/graphiti/resource/links.rb +1 -1
  37. data/lib/graphiti/resource/persistence.rb +1 -1
  38. data/lib/graphiti/resource/polymorphism.rb +1 -1
  39. data/lib/graphiti/resource.rb +2 -2
  40. data/lib/graphiti/resource_proxy.rb +0 -1
  41. data/lib/graphiti/runner.rb +8 -3
  42. data/lib/graphiti/scope.rb +13 -0
  43. data/lib/graphiti/scoping/filter.rb +5 -3
  44. data/lib/graphiti/scoping/paginate.rb +1 -1
  45. data/lib/graphiti/scoping/sort.rb +1 -1
  46. data/lib/graphiti/sideload/has_many.rb +1 -1
  47. data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
  48. data/lib/graphiti/spec_helpers/matchers.rb +1 -1
  49. data/lib/graphiti/spec_helpers.rb +5 -0
  50. data/lib/graphiti/types.rb +5 -1
  51. data/lib/graphiti/util/cache_debug.rb +3 -3
  52. data/lib/graphiti/util/hash.rb +1 -1
  53. data/lib/graphiti/util/persistence.rb +6 -6
  54. data/lib/graphiti/util/serializer_relationships.rb +1 -1
  55. data/lib/graphiti/version.rb +1 -1
  56. data/lib/tasks/graphiti.rake +1 -1
  57. data/package.json +1 -0
  58. metadata +31 -197
  59. data/docs/concepts/backends-and-models.md +0 -122
  60. data/docs/concepts/endpoints.md +0 -183
  61. data/docs/concepts/links.md +0 -212
  62. data/docs/concepts/overview.md +0 -80
  63. data/docs/concepts/persisting.md +0 -376
  64. data/docs/concepts/relationships.md +0 -627
  65. data/docs/concepts/resources.md +0 -677
  66. data/docs/getting-started/first-api.md +0 -289
  67. data/docs/getting-started/installation.md +0 -185
  68. data/docs/intro.md +0 -307
  69. data/docs/js/authentication.md +0 -63
  70. data/docs/js/ddau.md +0 -20
  71. data/docs/js/extra-params.md +0 -41
  72. data/docs/js/index.md +0 -112
  73. data/docs/js/installation.md +0 -120
  74. data/docs/js/middleware.md +0 -72
  75. data/docs/js/models.md +0 -202
  76. data/docs/js/reads.md +0 -494
  77. data/docs/js/state-syncing.md +0 -100
  78. data/docs/js/writes.md +0 -373
  79. data/docs/reference/vandal.md +0 -63
  80. data/docs/reference/why.md +0 -13
  81. data/docs/topics/authorization.md +0 -155
  82. data/docs/topics/caching.md +0 -55
  83. data/docs/topics/customizing-sideloads.md +0 -156
  84. data/docs/topics/debugging.md +0 -242
  85. data/docs/topics/error-handling.md +0 -210
  86. data/docs/topics/etags.md +0 -46
  87. data/docs/topics/hopping-relationships.md +0 -149
  88. data/docs/topics/json-attributes.md +0 -77
  89. data/docs/topics/openstruct-models.md +0 -50
  90. data/docs/topics/remote-resources.md +0 -291
  91. data/docs/topics/testing.md +0 -894
  92. data/docs/topics/without-activerecord.md +0 -324
  93. data/docs/tutorial/index.md +0 -58
  94. data/docs/tutorial/step_0.md +0 -107
  95. data/docs/tutorial/step_1.md +0 -199
  96. data/docs/tutorial/step_2.md +0 -312
  97. data/docs/tutorial/step_3.md +0 -142
  98. data/docs/tutorial/step_4.md +0 -135
  99. data/docs/tutorial/step_5.md +0 -69
  100. data/docs/tutorial/step_6.md +0 -82
  101. data/docs/tutorial/step_7.md +0 -205
  102. data/docs/tutorial/step_8.md +0 -128
  103. data/docs/tutorial/step_9.md +0 -171
  104. data/docs/upgrading.md +0 -266
  105. data/website/.gitignore +0 -20
  106. data/website/README.md +0 -43
  107. data/website/docusaurus.config.js +0 -197
  108. data/website/package-lock.json +0 -19474
  109. data/website/package.json +0 -46
  110. data/website/sidebars.js +0 -82
  111. data/website/src/css/custom.css +0 -85
  112. data/website/src/pages/markdown-page.mdx +0 -7
  113. data/website/static/.nojekyll +0 -0
  114. data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
  115. data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
  116. data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
  117. data/website/static/1.13/2019/10/14/tutorial.html +0 -198
  118. data/website/static/1.13/CNAME +0 -1
  119. data/website/static/1.13/README.md +0 -16
  120. data/website/static/1.13/assets/css/syntax.css +0 -60
  121. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  122. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  123. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  124. data/website/static/1.13/assets/favicons/browserconfig.xml +0 -9
  125. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  126. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  127. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  128. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  129. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +0 -1
  130. data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
  131. data/website/static/1.13/assets/img/backend.gif +0 -0
  132. data/website/static/1.13/assets/img/conformity.png +0 -0
  133. data/website/static/1.13/assets/img/error_payload.png +0 -0
  134. data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
  135. data/website/static/1.13/assets/img/gh.png +0 -0
  136. data/website/static/1.13/assets/img/home-bg.jpg +0 -0
  137. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  138. data/website/static/1.13/assets/img/logo-500.png +0 -0
  139. data/website/static/1.13/assets/img/logo.png +0 -0
  140. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  141. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  142. data/website/static/1.13/assets/img/persist.jpg +0 -0
  143. data/website/static/1.13/assets/img/resource.gif +0 -0
  144. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  145. data/website/static/1.13/assets/img/rest1.gif +0 -0
  146. data/website/static/1.13/assets/img/rest2.gif +0 -0
  147. data/website/static/1.13/assets/img/rest3.gif +0 -0
  148. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  149. data/website/static/1.13/assets/img/sunrise.png +0 -0
  150. data/website/static/1.13/assets/img/why.png +0 -0
  151. data/website/static/1.13/assets/js/highlight.pack.js +0 -2
  152. data/website/static/1.13/assets/main.css +0 -15518
  153. data/website/static/1.13/assets/main.css.map +0 -1
  154. data/website/static/1.13/bin/bundle +0 -109
  155. data/website/static/1.13/bin/jekyll +0 -27
  156. data/website/static/1.13/bin/kramdown +0 -27
  157. data/website/static/1.13/bin/listen +0 -27
  158. data/website/static/1.13/bin/rake +0 -27
  159. data/website/static/1.13/bin/rougify +0 -27
  160. data/website/static/1.13/bin/safe_yaml +0 -27
  161. data/website/static/1.13/bin/sass +0 -27
  162. data/website/static/1.13/bin/sass-convert +0 -27
  163. data/website/static/1.13/bin/scss +0 -27
  164. data/website/static/1.13/blog.html +0 -259
  165. data/website/static/1.13/cheatsheet.html +0 -316
  166. data/website/static/1.13/cookbooks/authorization.md +0 -0
  167. data/website/static/1.13/cookbooks/caching.md +0 -0
  168. data/website/static/1.13/cookbooks/customizing-sideloads.html +0 -325
  169. data/website/static/1.13/cookbooks/etags.md +0 -0
  170. data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
  171. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  172. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  173. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  174. data/website/static/1.13/cookbooks/without-activerecord.html +0 -510
  175. data/website/static/1.13/features.html +0 -249
  176. data/website/static/1.13/feed.xml +0 -106
  177. data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
  178. data/website/static/1.13/guides/concepts/debugging.html +0 -440
  179. data/website/static/1.13/guides/concepts/endpoints.html +0 -432
  180. data/website/static/1.13/guides/concepts/error-handling.html +0 -396
  181. data/website/static/1.13/guides/concepts/links.html +0 -501
  182. data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
  183. data/website/static/1.13/guides/concepts/resources.html +0 -2176
  184. data/website/static/1.13/guides/concepts/testing.html +0 -1469
  185. data/website/static/1.13/guides/getting-started/installation.html +0 -420
  186. data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
  187. data/website/static/1.13/guides/index.html +0 -269
  188. data/website/static/1.13/guides/overview.html +0 -325
  189. data/website/static/1.13/guides/upgrading-2-0.html +0 -193
  190. data/website/static/1.13/guides/upgrading.html +0 -314
  191. data/website/static/1.13/guides/vandal.html +0 -282
  192. data/website/static/1.13/guides/why.html +0 -1121
  193. data/website/static/1.13/index.html +0 -72
  194. data/website/static/1.13/js/authentication.html +0 -295
  195. data/website/static/1.13/js/ddau.html +0 -238
  196. data/website/static/1.13/js/extra-params.html +0 -270
  197. data/website/static/1.13/js/index.html +0 -321
  198. data/website/static/1.13/js/installation.html +0 -637
  199. data/website/static/1.13/js/introduction.html +0 -257
  200. data/website/static/1.13/js/middleware.html +0 -318
  201. data/website/static/1.13/js/reads/fieldsets.html +0 -271
  202. data/website/static/1.13/js/reads/filtering.html +0 -289
  203. data/website/static/1.13/js/reads/includes.html +0 -260
  204. data/website/static/1.13/js/reads/index.html +0 -497
  205. data/website/static/1.13/js/reads/nested-queries.html +0 -353
  206. data/website/static/1.13/js/reads/pagination.html +0 -260
  207. data/website/static/1.13/js/reads/sorting.html +0 -265
  208. data/website/static/1.13/js/reads/statistics.html +0 -289
  209. data/website/static/1.13/js/state-syncing.html +0 -340
  210. data/website/static/1.13/js/writes/deferred.html +0 -296
  211. data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
  212. data/website/static/1.13/js/writes/index.html +0 -391
  213. data/website/static/1.13/js/writes/nested.html +0 -330
  214. data/website/static/1.13/js/writes/validations.html +0 -272
  215. data/website/static/1.13/quickstart.html +0 -660
  216. data/website/static/1.13/template +0 -161
  217. data/website/static/1.13/tutorial/index.html +0 -250
  218. data/website/static/1.13/tutorial/step_0.html +0 -292
  219. data/website/static/1.13/tutorial/step_1.html +0 -517
  220. data/website/static/1.13/tutorial/step_2.html +0 -481
  221. data/website/static/1.13/tutorial/step_3.html +0 -323
  222. data/website/static/1.13/tutorial/step_4.html +0 -318
  223. data/website/static/1.13/tutorial/step_5.html +0 -265
  224. data/website/static/1.13/tutorial/step_6.html +0 -276
  225. data/website/static/1.13/tutorial/step_7.html +0 -390
  226. data/website/static/1.13/tutorial/step_8.html +0 -316
  227. data/website/static/1.13/tutorial/step_9.html +0 -365
  228. data/website/static/CNAME +0 -1
  229. data/website/static/assets/img/error_payload.png +0 -0
  230. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  231. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  232. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  233. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  234. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  235. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  236. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  237. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  238. data/website/static/assets/img/meta_total_count.png +0 -0
  239. data/website/static/img/docusaurus-social-card.jpg +0 -0
  240. data/website/static/img/docusaurus.png +0 -0
  241. data/website/static/img/favicon.ico +0 -0
  242. data/website/static/img/logo.png +0 -0
  243. data/website/static/img/logo.svg +0 -1
  244. data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
  245. data/website/static/img/undraw_docusaurus_react.svg +0 -170
  246. data/website/static/img/undraw_docusaurus_tree.svg +0 -40
@@ -1,289 +0,0 @@
1
- ---
2
- title: 'Build Your First API'
3
- ---
4
-
5
- # Build Your First API
6
-
7
- By the end of this page you'll have a working Rails API, backed by Graphiti, that supports filtering, sorting, pagination, and nested relationships out of the box.
8
-
9
- We'll use Rails and ActiveRecord here, on familiar ground. For how the pieces fit together, see [Lifecycle of a Request](/concepts/overview).
10
-
11
- You'll need Ruby 3.2+ and Rails 7.1+ installed for this walkthrough. Graphiti itself only requires Ruby 3.2+ and ActiveSupport, so you can [use it without Rails](/getting-started/installation#without-rails).
12
-
13
- ## Installation {#installation}
14
-
15
- Let's start with a classic Rails blog. We'll use a [template](http://guides.rubyonrails.org/rails_application_templates.html) to handle some of the boilerplate. Run this command and accept all the defaults for now:
16
-
17
- ```bash
18
- $ rails new blog --api -m https://raw.githubusercontent.com/graphiti-api/graphiti_rails_template/master/all.rb
19
- ```
20
-
21
- Feel free to run `git diff` if you're interested in the
22
- particulars. This is mostly installing gems and including modules.
23
-
24
- > Note: if a network issue prevents you from pointing to this URL
25
- > directly, you can download the file and and run this command as `-m
26
- > /path/to/template`
27
-
28
- Alternatively, you can [**add to an existing project**](/getting-started/installation#adding-to-an-existing-app).
29
-
30
- ## Defining a Resource {#defining-a-resource}
31
-
32
- A [**Resource**](/concepts/resources) defines how to query and persist your [**Model**](/concepts/backends-and-models). In other
33
- words: a Model is to the database as Resource is to the API. So
34
- first, let's define our Model:
35
-
36
- ```bash
37
- $ bundle exec rails generate model Post title:string upvotes:integer active:boolean
38
- $ bundle exec rails db:migrate
39
- ```
40
-
41
- Now we can use the built-in [generator](/concepts/resources#generators) to define our Resource,
42
- corresponding [**Endpoint**](/concepts/endpoints), and
43
- [**Integration Tests**](/topics/testing).
44
-
45
- ```bash
46
- $ bundle exec rails g graphiti:resource Post title:string upvotes:integer active:boolean
47
- ```
48
-
49
- You'll see a number of files created. Now run your app!:
50
-
51
- ```bash
52
- $ bundle exec rails s
53
- ```
54
-
55
- Verify `http://localhost:3000/api/v1/posts` renders JSON correctly.
56
- Now we need data.
57
-
58
- ##### Seeding Data {#seeding-data}
59
-
60
- Edit `db/seeds.rb` to create a few `Post`s:
61
-
62
- ```ruby
63
- Post.create!(title: 'My title', upvotes: 10, active: true)
64
- Post.create!(title: 'Another title', upvotes: 20, active: false)
65
- Post.create!(title: 'OMG! A title', upvotes: 30, active: true)
66
- ```
67
-
68
- And run the script:
69
-
70
- ```bash
71
- $ bundle exec rails db:seed
72
- ```
73
-
74
- Now load `http://localhost:3000/api/v1/posts`. You should have 3 `Post`s in
75
- your database.
76
-
77
-
78
-
79
- <hr />
80
-
81
- ## Querying {#querying}
82
-
83
- Now that we've defined our Resource and seeded some data, let's see
84
- what query functionality we have. We've listed all `Post`s at `http://localhost:3000/api/v1/posts`. Let's see what we can do:
85
-
86
- | What you want | URL |
87
- | --- | --- |
88
- | Sort by title, ascending | `/api/v1/posts?sort=title` |
89
- | Sort by title, descending | `/api/v1/posts?sort=-title` |
90
- | Paginate, 2 per page | `/api/v1/posts?page[size]=2` |
91
- | Paginate, 2 per page, second page | `/api/v1/posts?page[size]=2&page[number]=2` |
92
- | Sparse fieldset: only `title` | `/api/v1/posts?fields[posts]=title` |
93
- | Filter, simple equality | `/api/v1/posts?filter[title]=my title` |
94
- | Filter, case-insensitive equality | `/api/v1/posts?filter[title][eql]=My title` |
95
- | Filter, prefix | `/api/v1/posts?filter[title][prefix]=my` |
96
- | Filter, suffix | `/api/v1/posts?filter[title][suffix]=title` |
97
- | Filter, contains | `/api/v1/posts?filter[title][match]=itl` |
98
- | Filter, greater than | `/api/v1/posts?filter[upvotes][gt]=20` |
99
- | Filter, greater than or equal to | `/api/v1/posts?filter[upvotes][gte]=20` |
100
- | Filter, less than | `/api/v1/posts?filter[upvotes][lt]=20` |
101
- | Filter, less than or equal to | `/api/v1/posts?filter[upvotes][lte]=20` |
102
-
103
- Filtering on an attribute you haven't made filterable raises `Graphiti::Errors::InvalidAttributeAccess`. Filtering on one that doesn't exist raises `Graphiti::Errors::UnknownAttribute`. All filter logic can be customized, and customizations can be packaged into an **Adapter** for reuse. See [Filter](/concepts/resources#filter).
104
-
105
- ### Extra Fields
106
-
107
- Some fields are expensive enough that you only want to compute them when a client asks. Declare those with `extra_attribute`:
108
-
109
- ```ruby
110
- # app/resources/post_resource.rb
111
- extra_attribute :description, :string do
112
- @object.active? ? 'Active Post' : 'Inactive Post'
113
- end
114
- ```
115
-
116
- Request it with `/api/v1/posts?extra_fields[posts]=description`. You can also eager load data conditionally when the field is requested.
117
-
118
- ### Statistics
119
-
120
- Useful for search grids ("the first 10 active posts, plus the total count of all posts") in a single request. Hit `/api/v1/posts?stats[total]=count` and the result arrives in the `meta` section of the response:
121
-
122
- ![meta_total_count](/assets/img/meta_total_count.png)
123
-
124
- Statistics respect your filters, so the count adjusts accordingly. There are several built-in stats and you can [add your own](/concepts/resources#statistics).
125
-
126
- ### Error Handling
127
-
128
- Your app always renders a JSONAPI-compliant error response. Raise something in the controller:
129
-
130
- ```ruby
131
- # app/controllers/posts_controller.rb
132
- def index
133
- raise 'foo'
134
- end
135
- ```
136
-
137
- and this is what you'd see in production:
138
-
139
- ![error_payload](/assets/img/error_payload.png)
140
-
141
- Different errors can be given different response codes, JSON, and side effects. See [Error Handling](/topics/error-handling).
142
-
143
- ## Persisting {#persisting}
144
-
145
- Resources can Create, Update, and Delete (and you can persist multiple
146
- Resources in a single request). The best way to observe this behavior is
147
- to take a look at the tests the generator created. One example:
148
-
149
- ```ruby
150
- # spec/api/v1/employees/create_spec.rb
151
- subject(:make_request) do
152
- jsonapi_post "/api/v1/employees", payload
153
- end
154
-
155
- describe 'basic create' do
156
- let(:payload) do
157
- {
158
- data: {
159
- type: 'employees',
160
- attributes: {
161
- first_name: 'Jane'
162
- }
163
- }
164
- }
165
- end
166
-
167
- it 'works' do
168
- expect(EmployeeResource).to receive(:build).and_call_original
169
- expect {
170
- make_request
171
- }.to change { Employee.count }.by(1)
172
- expect(response.status).to eq(201)
173
- end
174
- end
175
- ```
176
-
177
- Read more about [Persistence](/concepts/persisting) and
178
- [Testing Persistence](/topics/testing#writes).
179
-
180
- ## Adding Relationships {#adding-relationships}
181
-
182
- Let’s start by defining our Model:
183
-
184
- ```bash
185
- $ bundle exec rails g model Comment post_id:integer body:text active:boolean
186
- $ bundle exec rails db:migrate
187
- ```
188
-
189
- ```ruby
190
- # app/models/post.rb
191
- has_many :comments
192
-
193
- # app/models/comment.rb
194
- belongs_to :post
195
- ```
196
-
197
- ...and corresponding Resource object:
198
-
199
- ```bash
200
- $ bundle exec rails g graphiti:resource Comment body:string active:boolean created_at:datetime
201
- ```
202
-
203
- Configure the relationship in `PostResource`:
204
-
205
- ```ruby
206
- # app/resources/post_resource.rb
207
- has_many :comments
208
- ```
209
-
210
- And allow filtering Comments based on the Post `id`:
211
-
212
- ```ruby
213
- # app/resources/comment_resource.rb
214
- attribute :post_id, :integer, only: [:filterable]
215
- ```
216
-
217
- This code:
218
-
219
- * Allows eager-loading the relationship.
220
- * URL: `/api/v1/posts?include=comments`
221
- * SQL: `SELECT * FROM comments WHERE post_id = 123`
222
- * Generates a [**Link**](/concepts/links) for
223
- lazy-loading.
224
- * Will use `CommentResource` for querying logic (so we can [Deep
225
- Query](/concepts/relationships#deep-queries), e.g.
226
- "only return the latest 3 active comments").
227
- * By default, this will generate the query `CommentResource.all(filter: { post_id: 123 })`, but [relationships can be customized](/concepts/relationships)
228
-
229
- You should now be able to hit `/api/v1/comments` with all the same
230
- functionality as before. We need to seed data.
231
-
232
- #### Seeding Relationships {#seeding-relationships}
233
-
234
- Start by clearing out your database:
235
-
236
- ```bash
237
- $ bundle exec rails db:migrate:reset
238
- ```
239
-
240
- Replace your `db/seeds.rb` with this code to persist one `Post` and three `Comment`s:
241
-
242
- ```ruby
243
- comment1 = Comment.new(body: 'comment one', active: true)
244
- comment2 = Comment.new(body: 'comment two', active: false)
245
- comment3 = Comment.new(body: 'comment three', active: true)
246
-
247
- Post.create! \
248
- title: 'My title!',
249
- active: true,
250
- comments: [comment1, comment2, comment3]
251
- ```
252
-
253
- And run it:
254
-
255
- ```bash
256
- $ bundle exec rails db:seed
257
- ```
258
-
259
- ## Relationship Usage {#relationship-usage}
260
-
261
- Now let's fetch a `Post` and filtered `Comment`s in a single request:
262
-
263
- `/api/v1/posts?include=comments`
264
-
265
- Any logic in `CommentResource` is available to us. Let's sort the comments by `created_at` descending:
266
-
267
- `/api/v1/posts?include=comments&sort=-comments.created_at`.
268
-
269
- Logic from `CommentResource` is accessible at the `/api/v1/comments` endpoint, and reusable when eager-loading Comments at `/api/v1/posts:`
270
-
271
- * `/api/v1/comments?filter[active]=true`
272
- * `/api/v1/posts?include=comments&filter[comments.active]=true`
273
-
274
- This is why Resource objects exist: they provide an interface to
275
- reuse code across multiple Endpoints.
276
-
277
- Just as we can query a graph of Resources in a single
278
- request, we can *persist* a graph of Resources in a single request. See
279
- [Sideposting](/concepts/persisting#sideposting).
280
-
281
- ## Exploring with Vandal {#exploring-with-vandal}
282
-
283
- Graphiti ships with Vandal, a UI that introspects your schema for point-and-click data exploration. See the [Vandal Guide](/reference/vandal) to try it against this blog.
284
-
285
- ## Next Steps {#whats-next}
286
-
287
- * Continue with the [Tutorial](/tutorial) for a deeper walkthrough of customization and relationships.
288
- * Browse the [Resources guide](/) for the full capability reference.
289
- * Read the [Testing Guide](/topics/testing) to start testing your API.
@@ -1,185 +0,0 @@
1
- ---
2
- title: 'Installation'
3
- ---
4
-
5
- ## From Scratch {#from-scratch}
6
-
7
- The easiest way to start from scratch is to use the application
8
- template:
9
-
10
- ```bash
11
- $ rails new blog --api -m https://raw.githubusercontent.com/graphiti-api/graphiti_rails_template/master/all.rb
12
- ```
13
-
14
- Alternatively, download and point to the template locally:
15
-
16
- ```bash
17
- $ curl -O https://raw.githubusercontent.com/graphiti-api/graphiti_rails_template/master/all.rb
18
- $ rails new blog --api -m all.rb
19
- ```
20
-
21
- Run `git diff` to see the changes to a blank Rails app.
22
-
23
- ## Adding to an Existing App {#adding-to-an-existing-app}
24
-
25
- This process is straightforward. You can add Graphiti to an existing
26
- Rails app alongside [JBuilder](https://github.com/rails/jbuilder) or [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers).
27
-
28
- Start with gems:
29
-
30
- ```ruby
31
- # The only strictly-required gem
32
- gem 'graphiti'
33
-
34
- # For automatic ActiveRecord pagination
35
- gem 'kaminari'
36
-
37
- # Test-specific gems
38
- group :development, :test do
39
- gem 'rspec-rails'
40
- gem 'factory_bot_rails'
41
- gem 'faker'
42
- end
43
-
44
- group :test do
45
- gem 'database_cleaner'
46
- end
47
- ```
48
-
49
- You'll be up-and-running at this point. Verify with a simple standalone
50
- Resource:
51
-
52
- ```ruby
53
- # Assuming you already have a Post ActiveRecord Model
54
- class PostResource < Graphiti::Resource
55
- self.adapter = Graphiti::Adapters::ActiveRecord
56
- attribute :title, :string
57
- end
58
-
59
- PostResource.all.data # => [#<Post>, #<Post>, ...]
60
- ```
61
-
62
- Now we need to integrate with Rails endpoints (to give us things
63
- like [#context](/concepts/resources#context)):
64
-
65
- ```ruby
66
- # app/controllers/application_controller.rb
67
- class ApplicationController < ActionController::Base
68
- include Graphiti::Rails::Controller
69
- end
70
- ```
71
-
72
- And wire-up our error-handling:
73
-
74
- ```ruby
75
- # app/controllers/application_controller.rb
76
- # When #show action does not find record, return 404
77
- register_exception Graphiti::Errors::RecordNotFound,
78
- status: 404
79
-
80
- rescue_from Exception do |e|
81
- handle_exception(e)
82
- end
83
- ```
84
-
85
- That's it for the basics. You may have issues with generators
86
- conflicting with your existing application structure - but you can
87
- always write files manually or [submit an issue](https://github.com/graphiti-api/graphiti/issues).
88
-
89
- ### Responders {#responders}
90
-
91
- Graphiti supports JSONAPI, simple JSON, and XML. You can do this
92
- manually when inheriting from `ActionController::Base`
93
-
94
- ```ruby
95
- def index
96
- posts = PostResource.all(params)
97
-
98
- respond_to do |format|
99
- format.json { render(json: posts) }
100
- format.jsonapi { render(jsonapi: posts) }
101
- format.xml { render(xml: posts) }
102
- end
103
- end
104
- ```
105
-
106
- But we can inherit from `ActionController::API` while avoiding this
107
- boilerplate with with the [Responders](https://github.com/plataformatec/responders) gem:
108
-
109
- ```ruby
110
- def index
111
- posts = PostResource.all(params)
112
- respond_with(posts)
113
- end
114
- ```
115
-
116
- To get this functionality:
117
-
118
- ```ruby
119
- # Gemfile
120
- gem 'responders'
121
-
122
- # app/controllers/application_controller.rb
123
- include Graphiti::Rails::Responders
124
- ```
125
-
126
- > Note: Persistence operations only support JSONAPI format, so you'll
127
- > still use `render jsonapi:` and `render jsonapi_errors:` for those.
128
-
129
- ### .graphiticfg.yml {#graphiticfg}
130
-
131
- The `.graphiticfg.yml` file lives in the root directory of your
132
- application. It holds configuration we need to reuse across a variety of
133
- contexts (primarily generates and rake tasks). If you use our template to create your application, it's created for you.
134
-
135
- Primarily this is used to hold your "API namespace":
136
-
137
- ```yaml
138
- namespace: /my_api/v1
139
- ```
140
-
141
- If this file doesn't exist you may get unexpected errors - make sure to
142
- create it!
143
-
144
- ### Testing {#testing}
145
-
146
- To add our [Integration Tests](/topics/testing):
147
-
148
- ```ruby
149
- # Gemfile
150
- group :development, :test do
151
- gem 'factory_bot_rails'
152
- gem 'rspec_rails'
153
- gem 'faker'
154
- end
155
-
156
- group :test do
157
- gem 'database_cleaner'
158
- end
159
- ```
160
-
161
- Bootstrap RSpec if you haven't already:
162
-
163
- ```bash
164
- $ bin/rails g rspec:install
165
- ```
166
-
167
- Then add the Graphiti spec helpers and database cleaning to your `RSpec.configure` block. See [RSpec Setup](/topics/testing#rspec) in the Testing guide for the config to paste in.
168
-
169
- ### will_paginate {#will-paginate}
170
-
171
- By default, we use [Kaminari](https://github.com/kaminari/kaminari) for
172
- ActiveRecord pagination. If you prefer [will_paginate] (or anything
173
- else):
174
-
175
- ```ruby
176
- # app/resources/application_resource.rb
177
- paginate do |scope, current_page, per_page|
178
- scope.paginate(page: current_page, per_page: per_page)
179
- end
180
- ```
181
-
182
- ## Without Rails {#without-rails}
183
-
184
- You can use Graphiti in any plain `.rb` file. To see this in action,
185
- check out the [Plain Ruby Sample App](https://github.com/graphiti-api/plain_ruby_example).