graphiti 2.0.0.beta.7 → 2.0.0.beta.9

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +56 -4
  4. data/.github/workflows/docs.yml +10 -7
  5. data/.github/workflows/notify-example-app.yml +18 -0
  6. data/.github/workflows/release.yml +1 -1
  7. data/.gitignore +2 -0
  8. data/.standard.yml +6 -0
  9. data/CHANGELOG.md +66 -0
  10. data/CONTRIBUTING.md +3 -1
  11. data/README.md +13 -206
  12. data/Rakefile +34 -3
  13. data/UPGRADING.md +2 -2
  14. data/graphiti.gemspec +8 -4
  15. data/lib/generators/graphiti/generator_mixin.rb +1 -1
  16. data/lib/generators/graphiti/install_generator.rb +1 -1
  17. data/lib/generators/graphiti/resource_generator.rb +6 -3
  18. data/lib/generators/graphiti/templates/application_resource.rb.erb +19 -0
  19. data/lib/generators/graphiti/templates/controller.rb.erb +26 -8
  20. data/lib/graphiti/audit/report.rb +6 -6
  21. data/lib/graphiti/audit.rb +3 -4
  22. data/lib/graphiti/configuration.rb +11 -4
  23. data/lib/graphiti/debugger.rb +18 -6
  24. data/lib/graphiti/delegates/pagination.rb +2 -2
  25. data/lib/graphiti/deserializer.rb +3 -3
  26. data/lib/graphiti/error_serializers/deprecated_constants.rb +23 -0
  27. data/lib/graphiti/error_serializers/invalid_request.rb +6 -0
  28. data/lib/graphiti/errors.rb +22 -6
  29. data/lib/graphiti/extensions/temp_id.rb +1 -1
  30. data/lib/graphiti/query.rb +5 -4
  31. data/lib/graphiti/rails/context.rb +2 -1
  32. data/lib/graphiti/rails/controller.rb +3 -0
  33. data/lib/graphiti/rails/debugging.rb +1 -1
  34. data/lib/graphiti/rails/responders.rb +2 -1
  35. data/lib/graphiti/rails.rb +14 -0
  36. data/lib/graphiti/railtie.rb +8 -0
  37. data/lib/graphiti/resource/configuration.rb +1 -0
  38. data/lib/graphiti/resource/dsl.rb +2 -2
  39. data/lib/graphiti/resource/links.rb +1 -1
  40. data/lib/graphiti/resource/persistence.rb +1 -1
  41. data/lib/graphiti/resource/polymorphism.rb +1 -1
  42. data/lib/graphiti/resource.rb +2 -2
  43. data/lib/graphiti/resource_proxy.rb +0 -1
  44. data/lib/graphiti/scope.rb +68 -21
  45. data/lib/graphiti/scoping/filter.rb +5 -3
  46. data/lib/graphiti/scoping/paginate.rb +1 -1
  47. data/lib/graphiti/scoping/sort.rb +1 -1
  48. data/lib/graphiti/sideload/has_many.rb +1 -1
  49. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +11 -4
  50. data/lib/graphiti/sideload.rb +8 -6
  51. data/lib/graphiti/spec_helpers/errors_proxy.rb +1 -1
  52. data/lib/graphiti/spec_helpers/matchers.rb +1 -1
  53. data/lib/graphiti/types.rb +5 -1
  54. data/lib/graphiti/util/cache_debug.rb +3 -3
  55. data/lib/graphiti/util/hash.rb +1 -1
  56. data/lib/graphiti/util/persistence.rb +6 -6
  57. data/lib/graphiti/util/serializer_relationships.rb +11 -2
  58. data/lib/graphiti/version.rb +1 -1
  59. data/lib/graphiti.rb +1 -1
  60. data/lib/tasks/graphiti.rake +1 -1
  61. data/package-lock.json +114 -0
  62. data/package.json +16 -3
  63. metadata +33 -198
  64. data/docs/concepts/backends-and-models.md +0 -122
  65. data/docs/concepts/endpoints.md +0 -183
  66. data/docs/concepts/links.md +0 -212
  67. data/docs/concepts/overview.md +0 -80
  68. data/docs/concepts/persisting.md +0 -376
  69. data/docs/concepts/relationships.md +0 -627
  70. data/docs/concepts/resources.md +0 -677
  71. data/docs/getting-started/first-api.md +0 -289
  72. data/docs/getting-started/installation.md +0 -185
  73. data/docs/intro.md +0 -307
  74. data/docs/js/authentication.md +0 -63
  75. data/docs/js/ddau.md +0 -20
  76. data/docs/js/extra-params.md +0 -41
  77. data/docs/js/index.md +0 -112
  78. data/docs/js/installation.md +0 -120
  79. data/docs/js/middleware.md +0 -72
  80. data/docs/js/models.md +0 -202
  81. data/docs/js/reads.md +0 -494
  82. data/docs/js/state-syncing.md +0 -100
  83. data/docs/js/writes.md +0 -373
  84. data/docs/reference/vandal.md +0 -63
  85. data/docs/reference/why.md +0 -13
  86. data/docs/topics/authorization.md +0 -155
  87. data/docs/topics/caching.md +0 -55
  88. data/docs/topics/customizing-sideloads.md +0 -156
  89. data/docs/topics/debugging.md +0 -242
  90. data/docs/topics/error-handling.md +0 -210
  91. data/docs/topics/etags.md +0 -46
  92. data/docs/topics/hopping-relationships.md +0 -149
  93. data/docs/topics/json-attributes.md +0 -77
  94. data/docs/topics/openstruct-models.md +0 -50
  95. data/docs/topics/remote-resources.md +0 -291
  96. data/docs/topics/testing.md +0 -916
  97. data/docs/topics/without-activerecord.md +0 -324
  98. data/docs/tutorial/index.md +0 -58
  99. data/docs/tutorial/step_0.md +0 -107
  100. data/docs/tutorial/step_1.md +0 -199
  101. data/docs/tutorial/step_2.md +0 -312
  102. data/docs/tutorial/step_3.md +0 -142
  103. data/docs/tutorial/step_4.md +0 -135
  104. data/docs/tutorial/step_5.md +0 -69
  105. data/docs/tutorial/step_6.md +0 -82
  106. data/docs/tutorial/step_7.md +0 -205
  107. data/docs/tutorial/step_8.md +0 -128
  108. data/docs/tutorial/step_9.md +0 -171
  109. data/docs/upgrading.md +0 -267
  110. data/website/.gitignore +0 -20
  111. data/website/README.md +0 -43
  112. data/website/docusaurus.config.js +0 -197
  113. data/website/package-lock.json +0 -19474
  114. data/website/package.json +0 -46
  115. data/website/sidebars.js +0 -82
  116. data/website/src/css/custom.css +0 -85
  117. data/website/src/pages/markdown-page.mdx +0 -7
  118. data/website/static/.nojekyll +0 -0
  119. data/website/static/1.13/2019/03/31/graphiti-1-0.html +0 -205
  120. data/website/static/1.13/2019/05/08/graphiti-1-1.html +0 -212
  121. data/website/static/1.13/2019/05/20/graphiti-1-2.html +0 -214
  122. data/website/static/1.13/2019/10/14/tutorial.html +0 -198
  123. data/website/static/1.13/CNAME +0 -1
  124. data/website/static/1.13/README.md +0 -16
  125. data/website/static/1.13/assets/css/syntax.css +0 -60
  126. data/website/static/1.13/assets/favicons/android-chrome-192x192.png +0 -0
  127. data/website/static/1.13/assets/favicons/android-chrome-256x256.png +0 -0
  128. data/website/static/1.13/assets/favicons/apple-touch-icon.png +0 -0
  129. data/website/static/1.13/assets/favicons/browserconfig.xml +0 -9
  130. data/website/static/1.13/assets/favicons/favicon-16x16.png +0 -0
  131. data/website/static/1.13/assets/favicons/favicon-32x32.png +0 -0
  132. data/website/static/1.13/assets/favicons/favicon.ico +0 -0
  133. data/website/static/1.13/assets/favicons/mstile-150x150.png +0 -0
  134. data/website/static/1.13/assets/favicons/safari-pinned-tab.svg +0 -1
  135. data/website/static/1.13/assets/favicons/site.webmanifest +0 -19
  136. data/website/static/1.13/assets/img/backend.gif +0 -0
  137. data/website/static/1.13/assets/img/conformity.png +0 -0
  138. data/website/static/1.13/assets/img/error_payload.png +0 -0
  139. data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
  140. data/website/static/1.13/assets/img/gh.png +0 -0
  141. data/website/static/1.13/assets/img/home-bg.jpg +0 -0
  142. data/website/static/1.13/assets/img/lifecycle.gif +0 -0
  143. data/website/static/1.13/assets/img/logo-500.png +0 -0
  144. data/website/static/1.13/assets/img/logo.png +0 -0
  145. data/website/static/1.13/assets/img/love-graffiti.jpg +0 -0
  146. data/website/static/1.13/assets/img/meta_total_count.png +0 -0
  147. data/website/static/1.13/assets/img/persist.jpg +0 -0
  148. data/website/static/1.13/assets/img/resource.gif +0 -0
  149. data/website/static/1.13/assets/img/rest-graffiti.jpg +0 -0
  150. data/website/static/1.13/assets/img/rest1.gif +0 -0
  151. data/website/static/1.13/assets/img/rest2.gif +0 -0
  152. data/website/static/1.13/assets/img/rest3.gif +0 -0
  153. data/website/static/1.13/assets/img/rethink-rest-graffiti.jpg +0 -0
  154. data/website/static/1.13/assets/img/sunrise.png +0 -0
  155. data/website/static/1.13/assets/img/why.png +0 -0
  156. data/website/static/1.13/assets/js/highlight.pack.js +0 -2
  157. data/website/static/1.13/assets/main.css +0 -15518
  158. data/website/static/1.13/assets/main.css.map +0 -1
  159. data/website/static/1.13/bin/bundle +0 -109
  160. data/website/static/1.13/bin/jekyll +0 -27
  161. data/website/static/1.13/bin/kramdown +0 -27
  162. data/website/static/1.13/bin/listen +0 -27
  163. data/website/static/1.13/bin/rake +0 -27
  164. data/website/static/1.13/bin/rougify +0 -27
  165. data/website/static/1.13/bin/safe_yaml +0 -27
  166. data/website/static/1.13/bin/sass +0 -27
  167. data/website/static/1.13/bin/sass-convert +0 -27
  168. data/website/static/1.13/bin/scss +0 -27
  169. data/website/static/1.13/blog.html +0 -259
  170. data/website/static/1.13/cheatsheet.html +0 -316
  171. data/website/static/1.13/cookbooks/authorization.md +0 -0
  172. data/website/static/1.13/cookbooks/caching.md +0 -0
  173. data/website/static/1.13/cookbooks/customizing-sideloads.html +0 -325
  174. data/website/static/1.13/cookbooks/etags.md +0 -0
  175. data/website/static/1.13/cookbooks/hopping-relationships.html +0 -324
  176. data/website/static/1.13/cookbooks/json_attributes.md +0 -0
  177. data/website/static/1.13/cookbooks/openstruct-models.md +0 -0
  178. data/website/static/1.13/cookbooks/remote-resources.md +0 -0
  179. data/website/static/1.13/cookbooks/without-activerecord.html +0 -510
  180. data/website/static/1.13/features.html +0 -249
  181. data/website/static/1.13/feed.xml +0 -106
  182. data/website/static/1.13/guides/concepts/backends-and-models.html +0 -467
  183. data/website/static/1.13/guides/concepts/debugging.html +0 -440
  184. data/website/static/1.13/guides/concepts/endpoints.html +0 -432
  185. data/website/static/1.13/guides/concepts/error-handling.html +0 -396
  186. data/website/static/1.13/guides/concepts/links.html +0 -501
  187. data/website/static/1.13/guides/concepts/remote-resources.html +0 -536
  188. data/website/static/1.13/guides/concepts/resources.html +0 -2176
  189. data/website/static/1.13/guides/concepts/testing.html +0 -1469
  190. data/website/static/1.13/guides/getting-started/installation.html +0 -420
  191. data/website/static/1.13/guides/graphiti-rails-migration.html +0 -242
  192. data/website/static/1.13/guides/index.html +0 -269
  193. data/website/static/1.13/guides/overview.html +0 -325
  194. data/website/static/1.13/guides/upgrading-2-0.html +0 -193
  195. data/website/static/1.13/guides/upgrading.html +0 -314
  196. data/website/static/1.13/guides/vandal.html +0 -282
  197. data/website/static/1.13/guides/why.html +0 -1121
  198. data/website/static/1.13/index.html +0 -72
  199. data/website/static/1.13/js/authentication.html +0 -295
  200. data/website/static/1.13/js/ddau.html +0 -238
  201. data/website/static/1.13/js/extra-params.html +0 -270
  202. data/website/static/1.13/js/index.html +0 -321
  203. data/website/static/1.13/js/installation.html +0 -637
  204. data/website/static/1.13/js/introduction.html +0 -257
  205. data/website/static/1.13/js/middleware.html +0 -318
  206. data/website/static/1.13/js/reads/fieldsets.html +0 -271
  207. data/website/static/1.13/js/reads/filtering.html +0 -289
  208. data/website/static/1.13/js/reads/includes.html +0 -260
  209. data/website/static/1.13/js/reads/index.html +0 -497
  210. data/website/static/1.13/js/reads/nested-queries.html +0 -353
  211. data/website/static/1.13/js/reads/pagination.html +0 -260
  212. data/website/static/1.13/js/reads/sorting.html +0 -265
  213. data/website/static/1.13/js/reads/statistics.html +0 -289
  214. data/website/static/1.13/js/state-syncing.html +0 -340
  215. data/website/static/1.13/js/writes/deferred.html +0 -296
  216. data/website/static/1.13/js/writes/dirty-tracking.html +0 -399
  217. data/website/static/1.13/js/writes/index.html +0 -391
  218. data/website/static/1.13/js/writes/nested.html +0 -330
  219. data/website/static/1.13/js/writes/validations.html +0 -272
  220. data/website/static/1.13/quickstart.html +0 -660
  221. data/website/static/1.13/template +0 -161
  222. data/website/static/1.13/tutorial/index.html +0 -250
  223. data/website/static/1.13/tutorial/step_0.html +0 -292
  224. data/website/static/1.13/tutorial/step_1.html +0 -517
  225. data/website/static/1.13/tutorial/step_2.html +0 -481
  226. data/website/static/1.13/tutorial/step_3.html +0 -323
  227. data/website/static/1.13/tutorial/step_4.html +0 -318
  228. data/website/static/1.13/tutorial/step_5.html +0 -265
  229. data/website/static/1.13/tutorial/step_6.html +0 -276
  230. data/website/static/1.13/tutorial/step_7.html +0 -390
  231. data/website/static/1.13/tutorial/step_8.html +0 -316
  232. data/website/static/1.13/tutorial/step_9.html +0 -365
  233. data/website/static/CNAME +0 -1
  234. data/website/static/assets/img/error_payload.png +0 -0
  235. data/website/static/assets/img/legacy/legacy-0378a3bb39.png +0 -0
  236. data/website/static/assets/img/legacy/legacy-05bbd3e5fd.png +0 -0
  237. data/website/static/assets/img/legacy/legacy-07aa104495.png +0 -0
  238. data/website/static/assets/img/legacy/legacy-0c75a16b3a.gif +0 -0
  239. data/website/static/assets/img/legacy/legacy-3076df6209.png +0 -0
  240. data/website/static/assets/img/legacy/legacy-7f6889bc89.png +0 -0
  241. data/website/static/assets/img/legacy/legacy-a2cc4363c3.png +0 -0
  242. data/website/static/assets/img/legacy/legacy-f67cfa89ab.png +0 -0
  243. data/website/static/assets/img/meta_total_count.png +0 -0
  244. data/website/static/img/docusaurus-social-card.jpg +0 -0
  245. data/website/static/img/docusaurus.png +0 -0
  246. data/website/static/img/favicon.ico +0 -0
  247. data/website/static/img/logo.png +0 -0
  248. data/website/static/img/logo.svg +0 -1
  249. data/website/static/img/undraw_docusaurus_mountain.svg +0 -171
  250. data/website/static/img/undraw_docusaurus_react.svg +0 -170
  251. data/website/static/img/undraw_docusaurus_tree.svg +0 -40
@@ -8,7 +8,7 @@ module Graphiti
8
8
 
9
9
  argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
10
10
 
11
- class_option :'omit-comments',
11
+ class_option :"omit-comments",
12
12
  type: :boolean,
13
13
  default: false,
14
14
  aliases: ["--omit-comments", "-c"],
@@ -26,7 +26,7 @@ module Graphiti
26
26
  aliases: ["--actions", "-a"],
27
27
  desc: 'Array of controller actions to support, e.g. "index show destroy"'
28
28
 
29
- class_option :'attributes-from',
29
+ class_option :"attributes-from",
30
30
  banner: "Model",
31
31
  type: :string,
32
32
  aliases: ["--model", "-m"],
@@ -127,8 +127,11 @@ module Graphiti
127
127
  end
128
128
  end
129
129
 
130
+ # Generated controllers only use respond_with when it will actually work:
131
+ # the responders gem being installed is not enough, the controller chain
132
+ # must include Graphiti::Rails::Responders.
130
133
  def responders?
131
- defined?(::Responders)
134
+ "ApplicationController".safe_constantize&.include?(Graphiti::Rails::Responders)
132
135
  end
133
136
 
134
137
  # Api::V1::Posts, Api::V1::PostsController and api/v1/posts all name the
@@ -12,4 +12,23 @@ class ApplicationResource < Graphiti::Resource
12
12
  self.adapter = Graphiti::Adapters::ActiveRecord
13
13
  self.base_url = Rails.application.routes.default_url_options[:host]
14
14
  self.endpoint_namespace = '<%= api_namespace %>'
15
+ <%- unless omit_comments? -%>
16
+
17
+ # Defaults every Resource inherits. These are already the values shown;
18
+ # uncomment to change them API-wide, or set them on individual Resources.
19
+ # See https://graphiti.dev/concepts/resources
20
+ #
21
+ # self.attributes_readable_by_default = true
22
+ # self.attributes_writable_by_default = true
23
+ # self.attributes_sortable_by_default = true
24
+ # self.attributes_filterable_by_default = true
25
+ # self.attributes_schema_by_default = true
26
+ # self.relationships_readable_by_default = true
27
+ # self.relationships_writable_by_default = true
28
+ # self.belongs_to_resource_ids_by_default = :foreign_key
29
+ # self.filters_accept_nil_by_default = false
30
+ # self.filters_deny_empty_by_default = false
31
+ # self.default_page_size = 20
32
+ # self.max_page_size = 1_000
33
+ <%- end -%>
15
34
  end
@@ -1,25 +1,43 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= controller_class_name %> < ApplicationController
3
3
  <%- if actions?('index') -%>
4
+ <%- if responders? -%>
4
5
  def index
5
6
  <%= file_name.pluralize %> = <%= resource_klass %>.all(params)
6
- <%- if responders? -%>
7
7
  respond_with(<%= file_name.pluralize %>)
8
- <%- else -%>
9
- render jsonapi: <%= file_name.pluralize %>
10
- <%- end -%>
11
8
  end
9
+ <%- else -%>
10
+ <%- unless omit_comments? -%>
11
+ # With Graphiti::Rails::Responders included, these blocks collapse to
12
+ # respond_with(<%= file_name.pluralize %>). See https://graphiti.dev/getting-started/installation#responders
13
+ <%- end -%>
14
+ def index
15
+ <%= file_name.pluralize %> = <%= resource_klass %>.all(params)
16
+
17
+ respond_to do |format|
18
+ format.jsonapi { render jsonapi: <%= file_name.pluralize %> }
19
+ format.json { render json: <%= file_name.pluralize %> }
20
+ end
21
+ end
22
+ <%- end -%>
12
23
  <%- end -%>
13
24
  <%- if actions?('show') -%>
14
25
 
26
+ <%- if responders? -%>
15
27
  def show
16
28
  <%= file_name %> = <%= resource_klass %>.find(params)
17
- <%- if responders? -%>
18
29
  respond_with(<%= file_name %>)
19
- <%- else -%>
20
- render jsonapi: <%= file_name %>
21
- <%- end -%>
22
30
  end
31
+ <%- else -%>
32
+ def show
33
+ <%= file_name %> = <%= resource_klass %>.find(params)
34
+
35
+ respond_to do |format|
36
+ format.jsonapi { render jsonapi: <%= file_name %> }
37
+ format.json { render json: <%= file_name %> }
38
+ end
39
+ end
40
+ <%- end -%>
23
41
  <%- end -%>
24
42
  <%- if actions?('create') -%>
25
43
 
@@ -60,7 +60,7 @@ module Graphiti
60
60
 
61
61
  section_heading(label, color, ISSUE_HEADINGS.fetch(check, check.to_s.tr("_", " "))) +
62
62
  lines_grouped_by_resource(group.map { |row, finding| [row, finding.message] }) +
63
- (fixes.size == 1 ? [fix_line(fixes.first), ""] : [])
63
+ ((fixes.size == 1) ? [fix_line(fixes.first), ""] : [])
64
64
  end
65
65
 
66
66
  def loading_section
@@ -71,7 +71,7 @@ module Graphiti
71
71
  lines_grouped_by_resource(rows.map { |row| [row, nil] }) +
72
72
  [" #{paint("That is a query per record rendered.", :dim)}",
73
73
  fix_line("preload the association in base_scope, or drop `resource_ids`"),
74
- " #{paint("See www.graphiti.dev/concepts/relationships#customizing-relationships", :dim)}",
74
+ " #{paint("See graphiti.dev/concepts/relationships#customizing-relationships", :dim)}",
75
75
  ""]
76
76
  end
77
77
 
@@ -83,7 +83,7 @@ module Graphiti
83
83
  rows = @rows.select(&:would_start_loading?)
84
84
  return [] if rows.empty?
85
85
 
86
- verb = rows.size == 1 ? "renders" : "render"
86
+ verb = (rows.size == 1) ? "renders" : "render"
87
87
 
88
88
  section_heading("FYI", :blue, "#{count(rows.size, "belongs_to relationship")} #{verb} no resource ids") +
89
89
  lines_grouped_by_resource(rows.map { |row| [row, nil] }) +
@@ -95,7 +95,7 @@ module Graphiti
95
95
  ["",
96
96
  " #{paint("Either would load the association on every render. That potential performance cost is why ids are opt-in.", :dim)}",
97
97
  " #{paint("Preloading the association in base_scope keeps that load cheap.", :dim)}",
98
- " #{paint("See www.graphiti.dev/concepts/relationships#belongs-to-resource-ids", :dim)}",
98
+ " #{paint("See graphiti.dev/concepts/relationships#belongs-to-resource-ids", :dim)}",
99
99
  ""]
100
100
  end
101
101
 
@@ -189,7 +189,7 @@ module Graphiti
189
189
 
190
190
  rows.map do |cells|
191
191
  line = cells.each_with_index.map { |cell, index|
192
- index == cells.size - 1 ? cell.painted : cell.pad_to(widths[index])
192
+ (index == cells.size - 1) ? cell.painted : cell.pad_to(widths[index])
193
193
  }.join(" ")
194
194
  indent + line
195
195
  end
@@ -203,7 +203,7 @@ module Graphiti
203
203
  counts = [
204
204
  count(@rows.group_by(&:resource).size, "resource"),
205
205
  count(@rows.size, "relationship"),
206
- paint(count(errors, "error"), errors > 0 ? :red : nil)
206
+ paint(count(errors, "error"), (errors > 0) ? :red : nil)
207
207
  ]
208
208
  counts << paint("#{loading} loading ids without preloading", :yellow) if loading > 0
209
209
  counts << "#{no_ids} without resource ids" if no_ids > 0
@@ -1,7 +1,7 @@
1
1
  module Graphiti
2
2
  # Resources register themselves as they load, so eager load before auditing.
3
3
  class Audit
4
- Finding = Struct.new(:severity, :check, :message, :remedy, keyword_init: true) do
4
+ Finding = Struct.new(:severity, :check, :message, :remedy) do
5
5
  def error?
6
6
  severity == :error
7
7
  end
@@ -18,8 +18,7 @@ module Graphiti
18
18
  :resource_ids_blocker,
19
19
  :preloaded,
20
20
  :options,
21
- :findings,
22
- keyword_init: true
21
+ :findings
23
22
  ) do
24
23
  def would_start_loading?
25
24
  return false if resource_ids_default == :never
@@ -75,7 +74,7 @@ module Graphiti
75
74
  resource_ids_source_if_always: resource_ids_source_if_always(resource_class, sideload),
76
75
  resource_ids_default: resource_class.belongs_to_resource_ids_by_default,
77
76
  resource_ids_blocker: sideload.resource_ids_blocker,
78
- preloaded: source == :load ? association_preloaded?(resource_class, sideload) : nil,
77
+ preloaded: (source == :load) ? association_preloaded?(resource_class, sideload) : nil,
79
78
  options: declaration_options(sideload),
80
79
  findings: [
81
80
  missing_association_method(sideload, model),
@@ -87,13 +87,20 @@ module Graphiti
87
87
  end
88
88
 
89
89
  def debug=(val)
90
- @debug = val
91
- Debugger.enabled = val
90
+ @debug = coerce_flag(val)
91
+ Debugger.enabled = @debug
92
92
  end
93
93
 
94
94
  def debug_models=(val)
95
- @debug_models = val
96
- Debugger.debug_models = val
95
+ @debug_models = coerce_flag(val)
96
+ Debugger.debug_models = @debug_models
97
+ end
98
+
99
+ # every value comes back from ENV as a string and "false" is truthy
100
+ def coerce_flag(val)
101
+ return false if ["false", "0", ""].include?(val.to_s.strip.downcase)
102
+
103
+ !!val
97
104
  end
98
105
 
99
106
  def with_option(key, value)
@@ -4,11 +4,22 @@
4
4
  module Graphiti
5
5
  class Debugger
6
6
  class << self
7
- attr_accessor :enabled, :chunks, :debug_models, :preserve, :pry
7
+ attr_accessor :enabled, :debug_models, :preserve, :pry
8
8
  end
9
- self.chunks = []
9
+
10
+ CHUNKS = :__graphiti_debugger_chunks
11
+ private_constant :CHUNKS
10
12
 
11
13
  class << self
14
+ # Pool threads inherit this through the thread-local copy Scope#future_with_context makes, so their chunks reach the right request.
15
+ def chunks
16
+ Thread.current[CHUNKS] ||= Concurrent::Array.new
17
+ end
18
+
19
+ def chunks=(value)
20
+ Thread.current[CHUNKS] = value
21
+ end
22
+
12
23
  def on_data(name, start, stop, id, payload)
13
24
  return [] unless enabled
14
25
 
@@ -179,14 +190,15 @@ module Graphiti
179
190
  end
180
191
 
181
192
  def graph_statements
182
- @chunks.each do |chunk|
193
+ statements = chunks
194
+ statements.each do |chunk|
183
195
  if (parent = chunk[:parent])
184
- relevant = chunks.find { |c| c[:resource] == parent }
196
+ relevant = statements.find { |c| c[:resource] == parent }
185
197
  relevant[:children].unshift(chunk) if relevant
186
198
  end
187
199
  end
188
- @chunks.reject! { |c| !!c[:parent] }
189
- @chunks
200
+ statements.reject! { |c| !!c[:parent] }
201
+ statements
190
202
  end
191
203
 
192
204
  def chunk_to_hash(chunk)
@@ -32,7 +32,7 @@ module Graphiti
32
32
  private
33
33
 
34
34
  def pagination_params
35
- @pagination_params ||= @proxy.query.params.reject { |key, _| [:action, :controller, :format].include?(key) }
35
+ @pagination_params ||= @proxy.query.params.except(:action, :controller, :format)
36
36
  end
37
37
 
38
38
  def pagination_link(page)
@@ -109,7 +109,7 @@ module Graphiti
109
109
  end
110
110
 
111
111
  def page_param
112
- @page_param ||= (@proxy.query.hash[:page] || {})
112
+ @page_param ||= @proxy.query.hash[:page] || {}
113
113
  end
114
114
  end
115
115
  end
@@ -85,7 +85,7 @@ class Graphiti::Deserializer
85
85
  def meta(action: :update)
86
86
  {
87
87
  type: data[:type],
88
- temp_id: data[:'temp-id'],
88
+ temp_id: data[:"temp-id"],
89
89
  method: action,
90
90
  payload_path: ["data"]
91
91
  }
@@ -162,12 +162,12 @@ class Graphiti::Deserializer
162
162
  end
163
163
 
164
164
  def process_relationship_datum(datum)
165
- temp_id = datum[:'temp-id']
165
+ temp_id = datum[:"temp-id"]
166
166
  included_object = included.find { |i|
167
167
  next unless i[:type] == datum[:type]
168
168
 
169
169
  (i[:id] && i[:id] == datum[:id]) ||
170
- (i[:'temp-id'] && i[:'temp-id'] == temp_id)
170
+ (i[:"temp-id"] && i[:"temp-id"] == temp_id)
171
171
  }
172
172
  included_idx = included.index(included_object)
173
173
 
@@ -23,6 +23,29 @@ module GraphitiErrors
23
23
  MSG
24
24
  end
25
25
 
26
+ # The old global rendering toggle, most often flipped in specs.
27
+ class << self
28
+ def enable!
29
+ Graphiti::DEPRECATOR.deprecation_warning("GraphitiErrors.enable!", "wrap the request in Graphiti::Rails::TestHelpers#handle_request_exceptions instead")
30
+ test_helpers.handle_request_exceptions(true)
31
+ end
32
+
33
+ def disable!
34
+ Graphiti::DEPRECATOR.deprecation_warning("GraphitiErrors.disable!", "wrap the request in Graphiti::Rails::TestHelpers#handle_request_exceptions instead")
35
+ test_helpers.handle_request_exceptions(false)
36
+ end
37
+
38
+ def disabled?
39
+ !test_helpers.handle_request_exceptions?
40
+ end
41
+
42
+ private
43
+
44
+ def test_helpers
45
+ @test_helpers ||= Object.new.extend(Graphiti::Rails::TestHelpers)
46
+ end
47
+ end
48
+
26
49
  module Validation; end
27
50
 
28
51
  module InvalidRequest; end
@@ -36,6 +36,12 @@ module Graphiti
36
36
  end
37
37
  end
38
38
 
39
+ # graphiti_errors named the payload this. Remove in 3.0.
40
+ def rendered_errors
41
+ Graphiti::DEPRECATOR.deprecation_warning("#rendered_errors", "use #errors")
42
+ errors
43
+ end
44
+
39
45
  private
40
46
 
41
47
  def code
@@ -270,7 +270,7 @@ module Graphiti
270
270
  #{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
271
271
 
272
272
  The error was raised while attempting to build query parameters for the associated Resource.
273
- Read more about sideload scoping here: www.graphiti.dev/concepts/relationships#customizing-scope
273
+ Read more about sideload scoping here: graphiti.dev/concepts/relationships#customizing-scope
274
274
 
275
275
  A good way to debug is to put a debugger within the 'params' block.
276
276
 
@@ -294,7 +294,7 @@ module Graphiti
294
294
 
295
295
  The error was raised while attempting to build the scope for the associated Resource.
296
296
 
297
- Read more about sideload scoping here: www.graphiti.dev/concepts/relationships#customizing-scope
297
+ Read more about sideload scoping here: graphiti.dev/concepts/relationships#customizing-scope
298
298
 
299
299
  Here's the original, underlying error:
300
300
 
@@ -315,7 +315,7 @@ module Graphiti
315
315
  #{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
316
316
 
317
317
  The error was raised while attempting to assign relevant model instances. Read
318
- more about sideload assignment here: www.graphiti.dev/concepts/relationships#customizing-assignment
318
+ more about sideload assignment here: graphiti.dev/concepts/relationships#customizing-assignment
319
319
 
320
320
  A good way to debug is to put a debugger within the 'assign' block.
321
321
 
@@ -449,7 +449,7 @@ module Graphiti
449
449
 
450
450
  self.validate_endpoints = false
451
451
 
452
- See https://www.graphiti.dev/concepts/links for more information.
452
+ See https://graphiti.dev/concepts/links for more information.
453
453
 
454
454
  The current endpoints allowed for this resource are: #{@resource_class.endpoints.inspect}
455
455
  MSG
@@ -708,6 +708,22 @@ module Graphiti
708
708
  end
709
709
  end
710
710
 
711
+ class UnselectedForeignKey < Base
712
+ def initialize(resource_class, sideload, model)
713
+ @resource_class = resource_class
714
+ @sideload = sideload
715
+ @model = model
716
+ end
717
+
718
+ def message
719
+ <<~MSG
720
+ #{@resource_class.name}: rendering resource linkage for relationship #{@sideload.name.inspect} reads ##{@sideload.foreign_key} off #{@model.class.name}, but that attribute is not loaded.
721
+
722
+ Keep #{@sideload.foreign_key} in the selected columns, or turn linkage off with `resource_ids: false` on the relationship or `self.belongs_to_resource_ids_by_default = :never` on the resource.
723
+ MSG
724
+ end
725
+ end
726
+
711
727
  class MissingRelationshipMethod < Base
712
728
  def initialize(resource_class, sideload, model)
713
729
  @resource_class = resource_class
@@ -731,7 +747,7 @@ module Graphiti
731
747
 
732
748
  <<~MSG
733
749
 
734
- resource_ids is set on this relationship, so every render reads the association, not just requests that include it. See www.graphiti.dev/concepts/relationships#customizing-relationships.
750
+ resource_ids is set on this relationship, so every render reads the association, not just requests that include it. See graphiti.dev/concepts/relationships#customizing-relationships.
735
751
  MSG
736
752
  end
737
753
  end
@@ -909,7 +925,7 @@ module Graphiti
909
925
  def initialize(resource, filter_names, required)
910
926
  @resource = resource
911
927
  @filter_names = filter_names
912
- @required_label = required == :all ? "All" : "One"
928
+ @required_label = (required == :all) ? "All" : "One"
913
929
  end
914
930
 
915
931
  def message
@@ -14,7 +14,7 @@ module Graphiti
14
14
  def as_jsonapi(*)
15
15
  super.tap do |hash|
16
16
  if (temp_id = @object.instance_variable_get(:@_jsonapi_temp_id))
17
- hash[:'temp-id'] = temp_id
17
+ hash[:"temp-id"] = temp_id
18
18
  end
19
19
  end
20
20
  end
@@ -22,6 +22,7 @@ module Graphiti
22
22
  @include_param = nested_include || @params[:include]
23
23
  @parents = parents
24
24
  @action = parse_action(action)
25
+ @entity_map = Concurrent::Map.new if parents.empty?
25
26
  end
26
27
 
27
28
  def association?
@@ -32,7 +33,7 @@ module Graphiti
32
33
  def entity_map
33
34
  return root.entity_map unless root == self
34
35
 
35
- @entity_map ||= Concurrent::Map.new
36
+ @entity_map
36
37
  end
37
38
 
38
39
  def top_level?
@@ -235,7 +236,7 @@ module Graphiti
235
236
 
236
237
  scrubbed.filter do |key, value|
237
238
  sideload = @resource.class.sideload(key)
238
- sideload.nil? ? true : sideload.readable?
239
+ sideload.nil? || sideload.readable?
239
240
  end
240
241
  end
241
242
  end
@@ -311,7 +312,7 @@ module Graphiti
311
312
  return false unless association?
312
313
 
313
314
  split = name.to_s.split(".")
314
- query_names = split[0..split.length - 2].map(&:to_sym)
315
+ query_names = split[0..-2].map(&:to_sym)
315
316
  my_names = parents.map(&:association_name).compact + [association_name].compact
316
317
  query_names == my_names
317
318
  end
@@ -343,7 +344,7 @@ module Graphiti
343
344
  end
344
345
 
345
346
  def sort_hash(attr)
346
- value = attr[0] == "-" ? :desc : :asc
347
+ value = (attr[0] == "-") ? :desc : :asc
347
348
  key = attr.sub("-", "").to_sym
348
349
 
349
350
  {key => value}
@@ -1,11 +1,12 @@
1
1
  module Graphiti
2
2
  module Rails
3
- # Wraps controller actions in a [Graphiti Context](https://www.graphiti.dev/concepts/resources#context) which points to the
3
+ # Wraps controller actions in a [Graphiti Context](https://graphiti.dev/concepts/resources#context) which points to the
4
4
  # controller instance by default.
5
5
  module Context
6
6
  def self.included(klass)
7
7
  klass.class_eval do
8
8
  include Graphiti::Context
9
+
9
10
  around_action :wrap_graphiti_context
10
11
  end
11
12
  end
@@ -17,6 +17,9 @@ module Graphiti
17
17
  included do
18
18
  include Graphiti::Rails::Context
19
19
  include Graphiti::Rails::Debugging
20
+ # ActionController::API strips this out, but Graphiti serves multiple
21
+ # formats (jsonapi/json/xml), so respond_to has to work everywhere.
22
+ include ActionController::MimeResponds
20
23
 
21
24
  # Broad, but the handler only renders formats listed in
22
25
  # config.graphiti.handled_exception_formats and hands everything else
@@ -1,6 +1,6 @@
1
1
  module Graphiti
2
2
  module Rails
3
- # Wraps controller actions in a [Graphiti Debugger](https://www.graphiti.dev/topics/debugging#debugger).
3
+ # Wraps controller actions in a [Graphiti Debugger](https://graphiti.dev/topics/debugging#debugger).
4
4
  module Debugging
5
5
  def self.included(klass)
6
6
  klass.around_action :debug_graphiti
@@ -6,6 +6,7 @@ module Graphiti
6
6
 
7
7
  included do
8
8
  include ActionController::MimeResponds
9
+
9
10
  respond_to(*Graphiti::Rails.respond_to_formats)
10
11
  end
11
12
 
@@ -14,7 +15,7 @@ module Graphiti
14
15
  opts = args.extract_options!
15
16
  opts[:location] = nil
16
17
  args << opts
17
- super(*args, &blk)
18
+ super
18
19
  end
19
20
  end
20
21
  end
@@ -31,6 +31,13 @@ module Graphiti
31
31
  autoload :Responders, "graphiti/rails/responders"
32
32
  autoload :TestHelpers, "graphiti/rails/test_helpers"
33
33
 
34
+ # Deprecated. Backed graphiti-rails' enable!/disable! spec toggles. Remove in 3.0.
35
+ GraphitiErrorsTesting = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
36
+ "Graphiti::Rails::GraphitiErrorsTesting",
37
+ "Graphiti::Rails::TestHelpers",
38
+ DEPRECATOR
39
+ )
40
+
34
41
  # @!attribute self.handled_exception_formats
35
42
  # A list of formats as symbols whose exceptions will be handled by Graphiti. See {Railtie}.
36
43
  cattr_accessor :handled_exception_formats, default: []
@@ -47,6 +54,13 @@ module Graphiti
47
54
  "Graphiti::Rails::Responders",
48
55
  DEPRECATOR
49
56
  )
57
+
58
+ # Deprecated. Core's railtie merged into graphiti-rails'. Remove in 3.0.
59
+ Railtie = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
60
+ "Graphiti::Railtie",
61
+ "Graphiti::Rails::Railtie",
62
+ DEPRECATOR
63
+ )
50
64
  end
51
65
 
52
66
  ActiveSupport.on_load(:active_record) do
@@ -0,0 +1,8 @@
1
+ # Deprecated require path, kept so a leftover 1.x require does not raise.
2
+ # The railtie lives in Graphiti::Rails as of 2.0. Remove in 3.0.
3
+ require "graphiti"
4
+
5
+ Graphiti::DEPRECATOR.warn(
6
+ 'require "graphiti/railtie" is no longer needed. The railtie is part of the Rails ' \
7
+ "integration as of 2.0, and loads with graphiti when Rails is present."
8
+ )
@@ -60,6 +60,7 @@ module Graphiti
60
60
  def remote=(val)
61
61
  super
62
62
  include ::Graphiti::Resource::Remote
63
+
63
64
  self.endpoint = {
64
65
  path: val,
65
66
  full_path: val,
@@ -9,7 +9,7 @@ module Graphiti
9
9
  opts = args.extract_options!
10
10
  type_override = args[0]
11
11
 
12
- if (att = (attributes[name] || extra_attributes[name]))
12
+ if (att = attributes[name] || extra_attributes[name])
13
13
  # We're opting in to filtering, so force this
14
14
  # UNLESS the filter is guarded at the attribute level
15
15
  att[:filterable] = true if att[:filterable] == false
@@ -27,7 +27,7 @@ module Graphiti
27
27
  end
28
28
 
29
29
  required = att[:filterable] == :required || !!opts[:required]
30
- schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
30
+ schema = (!!opts[:via_attribute_dsl]) ? att[:schema] : opts[:schema] != false
31
31
 
32
32
  config[:filters][name.to_sym] = {
33
33
  aliases: aliases,
@@ -41,7 +41,7 @@ module Graphiti
41
41
  def infer_endpoint
42
42
  return unless name
43
43
 
44
- path = "/#{name.gsub("Resource", "").pluralize.underscore}".to_sym
44
+ path = :"/#{name.gsub("Resource", "").pluralize.underscore}"
45
45
  {
46
46
  path: path,
47
47
  full_path: full_path_for(path),
@@ -65,7 +65,7 @@ module Graphiti
65
65
  def add_callback(kind, lifecycle, method, only, &blk)
66
66
  config[:callbacks][kind] ||= {}
67
67
  config[:callbacks][kind][lifecycle] ||= []
68
- config[:callbacks][kind][lifecycle] << {callback: (method || blk), only: Array(only)}
68
+ config[:callbacks][kind][lifecycle] << {callback: method || blk, only: Array(only)}
69
69
  end
70
70
  end
71
71
 
@@ -56,7 +56,7 @@ module Graphiti
56
56
  on_type, name = split_on[1].split("--").map(&:to_sym)
57
57
  end
58
58
 
59
- sl = super(name)
59
+ sl = super
60
60
  if !polymorphic_child? && sl.nil?
61
61
  children.each do |c|
62
62
  next if on_type && c.type != on_type
@@ -137,14 +137,14 @@ module Graphiti
137
137
 
138
138
  # TODO: make foreign_key a keyword once the satellite gems are rolled in - they call these positionally
139
139
  def assign_with_relationships(meta, attributes, relationships, caller_model = nil, foreign_key = nil, model_instance: nil)
140
- persistence = Graphiti::Util::Persistence \
140
+ persistence = Graphiti::Util::Persistence
141
141
  .new(self, meta, attributes, relationships, caller_model, foreign_key,
142
142
  assigned_model: model_instance)
143
143
  persistence.assign
144
144
  end
145
145
 
146
146
  def persist_with_relationships(meta, attributes, relationships, caller_model = nil, foreign_key = nil, assigned_model: nil)
147
- persistence = Graphiti::Util::Persistence \
147
+ persistence = Graphiti::Util::Persistence
148
148
  .new(self, meta, attributes, relationships, caller_model, foreign_key,
149
149
  assigned_model: assigned_model)
150
150
  persistence.run
@@ -16,7 +16,6 @@ module Graphiti
16
16
  cache_expires_in: nil,
17
17
  cache_tag: nil
18
18
  )
19
-
20
19
  @resource = resource
21
20
  @scope = scope
22
21
  @query = query