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
data/docs/intro.md DELETED
@@ -1,307 +0,0 @@
1
- ---
2
- id: intro
3
- title: 'Graphiti'
4
- sidebar_label: 'Overview'
5
- sidebar_position: 0
6
- slug: /
7
- ---
8
-
9
- # Graphiti
10
-
11
- :::info These are the 2.0 docs
12
- Graphiti 2.0 folds `graphiti-rails`, `graphiti_spec_helpers` and `graphiti_errors` into the main gem, and requires Ruby 3.2+ and ActiveSupport 7.1+. Rails is optional, and 7.1+ if you use it. Coming from 1.x? Start with the **[2.0 upgrade guide](/upgrading)**. Docs for 1.x are in the version picker.
13
- :::
14
-
15
- Graphiti sits on top of your models and exposes them over a [JSON:API](https://jsonapi.org)-compliant interface. You define Resources instead of controllers and serializers, and get filtering, sorting, pagination, sparse fieldsets, statistics, and nested reads and writes across relationships, all over one endpoint.
16
-
17
- Here is the whole loop. A Resource declares what's exposed:
18
-
19
- ```ruby title="app/resources/employee_resource.rb"
20
- class EmployeeResource < ApplicationResource
21
- attribute :first_name, :string
22
- attribute :last_name, :string
23
- attribute :age, :integer
24
-
25
- has_many :positions
26
- end
27
- ```
28
-
29
- The controller hands it the request params and renders the result:
30
-
31
- ```ruby title="app/controllers/employees_controller.rb"
32
- class EmployeesController < ApplicationController
33
- def index
34
- employees = EmployeeResource.all(params)
35
-
36
- respond_to do |format|
37
- format.jsonapi { render(jsonapi: employees) }
38
- format.json { render(json: employees) }
39
- format.xml { render(xml: employees) }
40
- end
41
- end
42
-
43
- def show
44
- employee = EmployeeResource.find(params)
45
- authorize employee.data # pundit. .data is the model itself
46
- render(jsonapi: employee)
47
- end
48
- end
49
- ```
50
-
51
- A client asks for employees and their positions in one request:
52
-
53
- ```http title="Request"
54
- GET /api/v1/employees?include=positions
55
- ```
56
-
57
- ```json title="Response"
58
- {
59
- "data": [
60
- {
61
- "id": "1",
62
- "type": "employees",
63
- "attributes": {
64
- "first_name": "Jane",
65
- "last_name": "Doe",
66
- "age": 34
67
- },
68
- "relationships": {
69
- "positions": {
70
- "data": [
71
- { "type": "positions", "id": "1" },
72
- { "type": "positions", "id": "2" }
73
- ]
74
- }
75
- }
76
- }
77
- ],
78
- "included": [
79
- {
80
- "id": "1",
81
- "type": "positions",
82
- "attributes": { "title": "Engineer" }
83
- },
84
- {
85
- "id": "2",
86
- "type": "positions",
87
- "attributes": { "title": "Senior Engineer" }
88
- }
89
- ]
90
- }
91
- ```
92
-
93
- That same Resource also serves `?filter[age][gt]=30`, `?sort=-age`, `?page[size]=10`, `?fields[employees]=first_name`, and `?stats[total]=count`, without writing any of them.
94
-
95
- The same proxy renders all three formats, so `/employees.jsonapi`, `/employees.json` and `/employees.xml` all work off one action.
96
-
97
- `.all` and `.find` return that proxy, so nothing has been queried yet. `.data` is where you reach the model, and also where per-record authorization goes. See [Authorization](/topics/authorization#integrating-with-pundit).
98
-
99
- If repeating that `respond_to` block gets old, the optional [`responders`](https://github.com/heartcombo/responders) integration collapses it to `respond_with(employees)`. See [Installation](/getting-started/installation#responders).
100
-
101
- ## The whole Resource API
102
-
103
- Every Resource is a collection of defaults, and you can override any of them. Below is one Resource with those defaults written out the long way, the entire surface area on a single page. You wouldn't write this much by hand. It's here so you can see what's available.
104
-
105
- ### ApplicationResource
106
-
107
- Every Resource inherits from an `ApplicationResource`, the same way models inherit from `ApplicationRecord`. This is where cross-cutting configuration lives, so individual Resources stay small. It's also the right place to put helpers like `current_user`, which guards throughout your API can then call.
108
-
109
- ```ruby title="app/resources/application_resource.rb"
110
- class ApplicationResource < Graphiti::Resource
111
- # Required when there's no corresponding model
112
- self.abstract_class = true
113
-
114
- # Subclasses override as needed
115
- self.adapter = Graphiti::Adapters::ActiveRecord
116
-
117
- # Flip any of these to lock down every Resource at once,
118
- # e.g. a read-only API
119
- self.attributes_readable_by_default = true
120
- self.attributes_writable_by_default = true
121
- self.attributes_sortable_by_default = true
122
- self.attributes_filterable_by_default = true
123
-
124
- # Used for link generation
125
- self.base_url = Rails.application.routes.default_url_options[:host]
126
- self.endpoint_namespace = '/api/v1'
127
-
128
- def current_user
129
- context.current_user
130
- end
131
- end
132
- ```
133
-
134
- ### A Resource
135
-
136
- An individual Resource declares its attributes and relationships, plus anything about it that differs from the defaults:
137
-
138
- ```ruby
139
- class EmployeeResource < ApplicationResource
140
- # Both inferred from the class name. Set them only when they differ
141
- self.model = Employee
142
- self.type = :employees # the JSONAPI type
143
-
144
- self.default_sort = [{ name: :desc }] # default nil
145
- self.default_page_size = 10 # default 20
146
-
147
- attribute :name, :string
148
- attribute :age, :integer
149
- attribute :hired_at, :datetime, writable: false
150
-
151
- has_many :positions
152
- end
153
- ```
154
-
155
- That is a complete, working Resource. Everything below is how you override a piece of it.
156
-
157
- ### Attributes
158
-
159
- ```ruby
160
- # Each flag defaults to the corresponding class-level setting
161
- attribute :name, :string,
162
- readable: self.attributes_readable_by_default,
163
- writable: self.attributes_writable_by_default,
164
- sortable: self.attributes_sortable_by_default,
165
- filterable: self.attributes_filterable_by_default
166
-
167
- # Alter display
168
- # @object is your model instance
169
- attribute :name, :string do
170
- @object.name.upcase
171
- end
172
- ```
173
-
174
- ### Sorting
175
-
176
- ```ruby
177
- # Pass a type - sort :name, :string - if no attribute is defined
178
- sort :name do |scope, dir|
179
- scope.order(name: dir)
180
- end
181
- ```
182
-
183
- ### Filtering
184
-
185
- ```ruby
186
- # Pass a type - filter :name, :string - if no attribute is defined
187
- filter :name do
188
- # All of these operators have not_ equivalents, e.g. not_eq
189
- # Imagine ".where.not" instead of ".where"
190
-
191
- eq do |scope, value|
192
- scope.where("lower(name) IN ?", value.map(&:downcase))
193
- end
194
-
195
- eql do |scope, value|
196
- scope.where(name: value)
197
- end
198
-
199
- prefix do |scope, value|
200
- value.each do |v|
201
- scope = scope.where('lower(name) LIKE ?', "#{v.downcase}%")
202
- end
203
- scope
204
- end
205
-
206
- suffix do |scope, value|
207
- value.each do |v|
208
- scope = scope.where('lower(name) LIKE ?', "%#{v.downcase}")
209
- end
210
- scope
211
- end
212
-
213
- match do |scope, value|
214
- value.each do |v|
215
- scope = scope.where('lower(name) LIKE ?', "%#{v.downcase}%")
216
- end
217
- scope
218
- end
219
- end
220
-
221
- # Comparison operators, for integer, float, datetime, etc
222
- filter :age, :integer do
223
- eq do |scope, value|
224
- scope.where(age: value)
225
- end
226
-
227
- gt do |scope, value|
228
- value.each { |v| scope = scope.where('age > ?', v) }
229
- scope
230
- end
231
-
232
- gte do |scope, value|
233
- value.each { |v| scope = scope.where('age >= ?', v) }
234
- scope
235
- end
236
-
237
- lt do |scope, value|
238
- value.each { |v| scope = scope.where('age < ?', v) }
239
- scope
240
- end
241
-
242
- lte do |scope, value|
243
- value.each { |v| scope = scope.where('age <= ?', v) }
244
- scope
245
- end
246
- end
247
- ```
248
-
249
- Filters receive an array of values by default, which is why each operator above iterates. Pass `single: true` to accept one value instead.
250
-
251
- ### Querying
252
-
253
- ```ruby
254
- # Passed to sort, filter, paginate, etc
255
- # Apply global logic here: only return active Employees,
256
- # scope results to the current user, and so on
257
- def base_scope
258
- Employee.all
259
- end
260
-
261
- # Must execute the query and return an array of Model instances
262
- def resolve(scope)
263
- scope.to_a
264
- end
265
- ```
266
-
267
- ### Persisting
268
-
269
- Your adapter handles writes for you, so most Resources define nothing here. Reach for [lifecycle hooks](/concepts/persisting#persistence-lifecycle-hooks) when you need to intervene:
270
-
271
- ```ruby
272
- before_attributes do |attributes|
273
- # before attributes are assigned to the model
274
- end
275
-
276
- before_save do |model|
277
- # assigned, but not yet persisted
278
- end
279
-
280
- before_commit do |model|
281
- # saved and validated, still inside the transaction
282
- end
283
- ```
284
-
285
- The model you inspect is the model that saves. Attributes are assigned up front, so you can hold the model, check it, and change it before anything is written. The instance you were handed is the one that gets persisted:
286
-
287
- ```ruby
288
- employee = EmployeeResource.build(payload)
289
-
290
- employee.data # the model, attributes already assigned, nothing written yet
291
- employee.data.valid? # inspect it, or modify it
292
- employee.save # persists that same instance
293
- ```
294
-
295
- Updates work the same way, reading the persisted record until you apply the payload:
296
-
297
- ```ruby
298
- proxy = EmployeeResource.find(payload)
299
- proxy.data.first_name # => "asdf", straight from the database
300
- proxy.assign_attributes(payload)
301
- proxy.data.first_name # => "Jane", assigned but still unsaved
302
- proxy.save(action: :update)
303
- ```
304
-
305
- ## Upgrading from 1.x
306
-
307
- The [2.0 upgrade guide](/upgrading) covers the whole migration: the three gems that folded into core, the deprecated spellings that still work but warn, and the two real behavior changes. Controllers now opt in via `Graphiti::Rails::Controller`, and `around_persistence` receives the model rather than an attributes hash.
@@ -1,63 +0,0 @@
1
- ---
2
- title: 'Authentication'
3
- sidebar_position: 7
4
- ---
5
-
6
- ### Authentication
7
-
8
- Spraypaint supports [JSON Web Tokens](https://jwt.io/introduction). These can
9
- be set manually, or automatically fetched from `localStorage`.
10
-
11
- To set manually:
12
-
13
- ```typescript
14
- ApplicationRecord.jwt = 'myt0k3n'
15
- ```
16
- > All requests will now send the header:<br />
17
- > `Authorization: Token token="myt0k3n"`.
18
-
19
- To set via `localStorage`, store the token with a key of `jwt` and it will be set automatically. To customize the `localStorage` key:
20
-
21
- ```typescript
22
- ApplicationRecord.jwtStorage = "authtoken"
23
- ```
24
-
25
- ...or to opt-out of `localStorage` altogether:
26
-
27
- ```typescript
28
- ApplicationRecord.jwtStorage = false
29
- ```
30
-
31
- You can control the format of the header that is sent to the
32
- server:
33
-
34
- ```typescript
35
- class ApplicationRecord extends SpraypaintBase {
36
- // ... code ...
37
- static generateAuthHeader(token) {
38
- return `Bearer ${token}`
39
- }
40
- }
41
- ```
42
-
43
- ```javascript
44
- var ApplicationRecord = SpraypaintBase.extend({
45
- // ... code ...
46
- static: {
47
- generateAuthHeader: function(token) {
48
- return "Bearer " + token;
49
- }
50
- }
51
- });
52
- ```
53
-
54
- Finally, if your server returns a refreshed JWT within the `X-JWT` header, it will be used in all subsequent requests (and `localStorage`
55
- will be updated automatically if you're using it).
56
-
57
- <h2 id="next">
58
- <a href="/js/state-syncing">
59
- NEXT:
60
- <small>State Syncing</small>
61
- &raquo;
62
- </a>
63
- </h2>
data/docs/js/ddau.md DELETED
@@ -1,20 +0,0 @@
1
- ---
2
- title: 'Ddau'
3
- sidebar_position: 9
4
- ---
5
-
6
- ### Data Down, Actions Up
7
-
8
- It's a [popular pattern](http://www.samselikoff.com/blog/data-down-actions-up) to pass data **down** to components, avoid modifying state within the component, and instead pass **actions up** to modify state. This can make complex applications easier to track and reason about, and you'll see it in client-side frameworks like React.
9
-
10
- To follow this pattern, use `#dup()` when passing down to your component:
11
-
12
- ```bash
13
- <my-component something="model.dup()" />
14
- ```
15
-
16
- This will create a new instance of the model with all the same state.
17
- Avoid modifying this instance in your component and instead pass
18
- **actions up**.
19
-
20
- When opting-in to [state-syncing](/js/state-syncing) these instances will sync-up whenever one of these is instances is persisted. You won't have to worry about updating the child component when the parent instance is saved.
@@ -1,41 +0,0 @@
1
- ---
2
- title: 'Extra Params'
3
- sidebar_position: 10
4
- ---
5
-
6
- ### Extra Params
7
-
8
- Sometimes you need to submit params that are not standard jsonapi params. One great example would be
9
- `https://yourdomain.com/users?debug=true` which is not a param for the `UserResource` you may have, but
10
- might enable functionality in your controller as needed.
11
-
12
- Invoking it is pretty straightforward, just invoke `extraParams` and pass in params and values you wish
13
- to add to your API call when executed.
14
-
15
-
16
- ```typescript
17
- YourRecord.extraParams({ debug: true })
18
- ```
19
-
20
- One common way to use this globally is to put this into a base class so it can be chained as part of
21
- every resource.
22
-
23
- ```typescript
24
- @Model
25
- export class ApplicationRecord extends SpraypaintBase {
26
- static withDebug<T extends ApplicationRecord>(): Scope<T> {
27
- return this.extraParams({ debug: true }) as Scope<T>;
28
- }
29
- }
30
- // unfortunately you will need to pass in the
31
- // implementing class' type as a generic
32
- UserRecord.withDebug<UserRecord>().all()
33
- ```
34
- ```javascript
35
- const ApplicationRecord = SpraypaintBase.extend({
36
- static: {
37
- withDebug: () => this.extraParams({ debug: true });
38
- }
39
- })
40
- UserRecord.withDebug().all()
41
- ```
data/docs/js/index.md DELETED
@@ -1,112 +0,0 @@
1
- ---
2
- title: 'Index'
3
- sidebar_position: 1
4
- ---
5
-
6
- <h1>
7
- Spraypaint
8
- <small>the isomorphic, framework-agnostic Graphiti ORM</small>
9
- </h1>
10
-
11
- ### Why Spraypaint?
12
-
13
- Contracts like JSONAPI and GraphQL treat the API like a database. When querying a database, we have two options:
14
-
15
- * Type the low-level query language directly (in the database world, this would be hand-typing SQL).
16
- * Use an ORM (like Rails's `ActiveRecord`, Phoenix's `Ecto`, Django's `DjangoORM`, or Node's `Sequelize`).
17
-
18
- While both options have pros and cons, we tend to think ORMs have two overwhelming benefits: ***ease of use*** and ***composable queries***. We'll explore both these concepts in other sections.
19
-
20
- So, we want a javascript ORM for our JSONAPI "database". Because `ActiveRecord` is arguably the most well-known ORM, we've tried to match its interface to make this library accessible to new users. That said, you'll find we've tried to favor *explicitness* over *implicitness* in order to avoid common `ActiveRecord` pitfalls.
21
-
22
- <span>Typescript</span>
23
- <span>Javascript</span>
24
- ```typescript
25
- // Spraypaint is like "ActiveRecord in Javascript". It can:
26
- //
27
- // * Deeply nest reads and writes
28
- // * Automatically handle validation errors
29
- // * Replace *ux patterns
30
- // * ...and much more!
31
-
32
- // define models
33
- @Model()
34
- class ApplicationRecord extends SpraypaintBase {
35
- static baseUrl = "http://my-api.com"
36
- static apiNamespace = "/api/v1"
37
- }
38
-
39
- @Model()
40
- class Person extends ApplicationRecord {
41
- static jsonapiType = "people"
42
-
43
- @Attr() firstName: string
44
- @Attr() lastName: string
45
-
46
- get fullName() {
47
- return `${this.firstName} ${this.lastName}`
48
- }
49
- }
50
-
51
- // execute queries
52
- Person
53
- .where({ first_name: 'John' })
54
- .order({ created_at: 'desc' })
55
- .per(10).page(2)
56
- .includes({ jobs: 'company' })
57
- .select({ people: ['first_name', 'last_name'] })
58
-
59
- // persist data
60
- let person = new Person({ firstName: 'Jane' })
61
- person.save()
62
- ```
63
-
64
- ```javascript
65
- // Spraypaint is like "ActiveRecord in Javascript". It can:
66
- //
67
- // * Deeply nest reads and writes
68
- // * Automatically handle validation errors
69
- // * Replace *ux patterns
70
- // * ...and much more!
71
-
72
- var spnt = require('spraypaint/dist/spraypaint')
73
-
74
- // define models
75
- const ApplicationRecord = spnt.JSORMBase.extend({
76
- static: {
77
- baseUrl: 'http://my-api.com',
78
- apiNamespace: '/api/v1'
79
- }
80
- })
81
-
82
- const Person = ApplicationRecord.extend({
83
- attrs: {
84
- firstName: spnt.attr(),
85
- lastName: spnt.attr()
86
- },
87
- methods: {
88
- fullName: function() {
89
- return this.firstName + ' ' + this.lastName;
90
- }
91
- }
92
- })
93
-
94
- // execute queries
95
- Person
96
- .where({ first_name: 'John' })
97
- .order({ created_at: 'desc' })
98
- .per(10).page(2)
99
- .includes({ jobs: 'company' })
100
- .select({ people: ['first_name', 'last_name'] })
101
-
102
- // persist data
103
- var person = new Person({ firstName: 'Jane' })
104
- person.save()
105
- ```
106
-
107
- ### Where to Go Next
108
-
109
- * [Installation](/js/installation) - install spraypaint and connect it to your API
110
- * [Models](/js/models) - define models, attributes, and relationships
111
- * [Reads](/js/reads) - query your API with a composable, ActiveRecord-like interface
112
- * [Writes](/js/writes) - create, update, and destroy records
@@ -1,120 +0,0 @@
1
- ---
2
- title: 'Installation'
3
- sidebar_position: 2
4
- ---
5
-
6
- ### Installation
7
-
8
- Installation is straightforward. Since we use `fetch` underneath the hood, we recommend installing alongside a `fetch` polyfill.
9
-
10
- If using `yarn`:
11
-
12
- ```bash
13
- $ yarn add spraypaint isomorphic-fetch
14
- ```
15
-
16
- If using `npm`:
17
-
18
- ```bash
19
- $ npm install spraypaint isomorphic-fetch
20
- ```
21
-
22
- Now import it:
23
-
24
- ```typescript
25
- import {
26
- Model,
27
- SpraypaintBase,
28
- Attr,
29
- BelongsTo,
30
- HasMany
31
- // etc
32
- } from "spraypaint"
33
- ```
34
-
35
- ```javascript
36
- const {
37
- SpraypaintBase,
38
- attr,
39
- belongsTo,
40
- hasMany
41
- // etc
42
- } = require("spraypaint/dist/spraypaint")
43
- ```
44
-
45
- ...or, if you're avoiding JS modules, `spraypaint` will be available as a global in the browser.
46
-
47
- ### Typescript
48
-
49
- Spraypaint works with modern TypeScript. Depending on your `tsconfig.json` settings, you may need a `!` after each attribute and relationship declaration:
50
-
51
- ```typescript
52
- @Attr first_name!: string
53
- @HasMany() positions!: Position[]
54
- ```
55
-
56
- This is because of [Strict Class Initialization](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#strict-class-initialization) - `strictPropertyInitialization` expects every declared class field to be assigned in the constructor, which Spraypaint's decorators handle at runtime rather than at construction time. For the purposes of Spraypaint, we don't need this check. Remove the need for `!` (as the rest of these guides do) by setting
57
-
58
- `"strictPropertyInitialization": false`
59
-
60
- in `tsconfig.json`.
61
-
62
- ### Connecting to the API
63
-
64
- Just like `ActiveRecord`, our models will inherit from a base class that holds connection information (`ApplicationRecord`, or `ActiveRecord::Base` in Rails < 5):
65
-
66
- ```typescript
67
- @Model()
68
- class ApplicationRecord extends SpraypaintBase {
69
- static baseUrl = "http://my-api.com"
70
- static apiNamespace = "/api/v1"
71
- }
72
- ```
73
-
74
- ```javascript
75
- const ApplicationRecord = SpraypaintBase.extend({
76
- static: {
77
- baseUrl: "http://my-api.com",
78
- apiNamespace: "/api/v1"
79
- }
80
- })
81
- ```
82
-
83
- All URLs follow the following pattern:
84
-
85
- * `baseUrl` + `apiNamespace` + `jsonapiType`
86
-
87
- As you can see above, typically `baseUrl` and `apiNamespace` are set on a top-level `ApplicationRecord` (though any subclass can override). `jsonapiType`, however, is set per-model - see [Models](/js/models) for how to define it.
88
-
89
- > **TIP**: Avoid CORS and use relative paths by setting `baseUrl` to `""`
90
-
91
- > **TIP**: You can always use the `endpoint` option to override this pattern and set the endpoint manually.
92
-
93
- #### Setting Application Name
94
-
95
- It can be helpful to send the name of your client application in request headers. With this information, servers can keep track of which clients are hitting which APIs.
96
-
97
- To do this:
98
-
99
- ```typescript
100
- @Model()
101
- class Person extends ApplicationRecord {
102
- static clientApplication = "sales-backend"
103
- }
104
- ```
105
-
106
- ```javascript
107
- const Person = ApplicationRecord.extend({
108
- static: {
109
- clientApplication: "sales-backend"
110
- }
111
- })
112
- ```
113
-
114
- <h2 id="next">
115
- <a href="/js/models">
116
- NEXT:
117
- <small>Models</small>
118
- &raquo;
119
- </a>
120
- </h2>