graphql_rails 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ef0115ce3d9107ee48ec4e7a725baff242ce81133be4a955d0a81619b106192
4
- data.tar.gz: dcb027f23eb5f6cd71b3144e2794822ee899796a5e40f7b240ea3cd59ef586b9
3
+ metadata.gz: 47c75badc8873658f461e2bc0f6de0ca35f2c13c7ded2ed66b23b20ee91fc60e
4
+ data.tar.gz: 551a92383fad945d6fb8c03f190793431b485a8a98256a64e26f892c1e48fc2f
5
5
  SHA512:
6
- metadata.gz: 8282983f1215feffb37e0541680abd91a44799a95d687cb3adb5cfcb91a1adbf8a07d585fffcd4e477579eebdd9f4a09f0efbc6dfb0b264b0a1a288aa0ac9688
7
- data.tar.gz: f7cafd9924e8e26abc9a6e5783938ebedbea7a0dd68fc69a5980186affab6593f8af4a25c8205e20fbfb45534bec96aa4b5b36bc558084994200a73f8eca31d7
6
+ metadata.gz: 0e852e9c89ee080ca786abfaabea1162d8ed1eadb4086bf7d2f9908c454e56dc8c839ab05f6a3967859188f3c36f32eae1907b45e41476e24fb81f5fb5db8e29
7
+ data.tar.gz: 0d970a75c022b4635e6c925db92f8d7d22cc67b8e8e41ed8c961c98b2a8aa2c3b4726a29fc736af5151ea935c899b21d454e9af703e9beb6ea9fab378d5fecbf
@@ -5,7 +5,7 @@ jobs:
5
5
  strategy:
6
6
  fail-fast: false
7
7
  matrix:
8
- ruby-version: ['2.7', '3.0', '3.1', '3.2']
8
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
9
9
 
10
10
  runs-on: ubuntu-latest
11
11
  env:
data/CHANGELOG.md CHANGED
@@ -9,7 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  * Added/Changed/Deprecated/Removed/Fixed/Security: YOUR CHANGE HERE
11
11
 
12
- ## [2.4.0](2023-27-25)
12
+ ## [3.0.0](2024-05-31)
13
+
14
+ * Added: `input.attribute.property` method which allows aliasing input parameter keys
15
+ * Added: possibility to copy attribute config using the `Attribute#same_as` method
16
+ * Fixed: do not ignore custom `max_page_size` for paginated responses
17
+ * Fixed: do not ignore custom `default_page_size` and other options for paginated responses
18
+ * Fixed: do not crash when using deeply nested input definitions
19
+ * Added: `rescue_from` support in controllers
20
+ * Added: Bumped graphql version to 2.1.7
21
+ * Added: `implements` support in models
22
+ * Added: `response.controller` and `response.action_name` methods in RSpecControllerHelpers
23
+ * Removed: stop supporting ruby < 3.0.0
24
+
25
+ ## [2.4.0](2023-11-25)
13
26
 
14
27
  * Added: `hidden_in_groups` for attributes to be able to skip attribute from certain groups
15
28
  * Added: `extras` for attributes to be able to include graphql-ruby extensions
data/Gemfile.lock CHANGED
@@ -1,67 +1,67 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphql_rails (2.4.0)
4
+ graphql_rails (3.0.0)
5
5
  activesupport (>= 4)
6
- graphql (= 2.0.21)
6
+ graphql (= 2.1.7)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actioncable (6.1.4.4)
12
- actionpack (= 6.1.4.4)
13
- activesupport (= 6.1.4.4)
11
+ actioncable (6.1.7.1)
12
+ actionpack (= 6.1.7.1)
13
+ activesupport (= 6.1.7.1)
14
14
  nio4r (~> 2.0)
15
15
  websocket-driver (>= 0.6.1)
16
- actionmailbox (6.1.4.4)
17
- actionpack (= 6.1.4.4)
18
- activejob (= 6.1.4.4)
19
- activerecord (= 6.1.4.4)
20
- activestorage (= 6.1.4.4)
21
- activesupport (= 6.1.4.4)
16
+ actionmailbox (6.1.7.1)
17
+ actionpack (= 6.1.7.1)
18
+ activejob (= 6.1.7.1)
19
+ activerecord (= 6.1.7.1)
20
+ activestorage (= 6.1.7.1)
21
+ activesupport (= 6.1.7.1)
22
22
  mail (>= 2.7.1)
23
- actionmailer (6.1.4.4)
24
- actionpack (= 6.1.4.4)
25
- actionview (= 6.1.4.4)
26
- activejob (= 6.1.4.4)
27
- activesupport (= 6.1.4.4)
23
+ actionmailer (6.1.7.1)
24
+ actionpack (= 6.1.7.1)
25
+ actionview (= 6.1.7.1)
26
+ activejob (= 6.1.7.1)
27
+ activesupport (= 6.1.7.1)
28
28
  mail (~> 2.5, >= 2.5.4)
29
29
  rails-dom-testing (~> 2.0)
30
- actionpack (6.1.4.4)
31
- actionview (= 6.1.4.4)
32
- activesupport (= 6.1.4.4)
30
+ actionpack (6.1.7.1)
31
+ actionview (= 6.1.7.1)
32
+ activesupport (= 6.1.7.1)
33
33
  rack (~> 2.0, >= 2.0.9)
34
34
  rack-test (>= 0.6.3)
35
35
  rails-dom-testing (~> 2.0)
36
36
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
- actiontext (6.1.4.4)
38
- actionpack (= 6.1.4.4)
39
- activerecord (= 6.1.4.4)
40
- activestorage (= 6.1.4.4)
41
- activesupport (= 6.1.4.4)
37
+ actiontext (6.1.7.1)
38
+ actionpack (= 6.1.7.1)
39
+ activerecord (= 6.1.7.1)
40
+ activestorage (= 6.1.7.1)
41
+ activesupport (= 6.1.7.1)
42
42
  nokogiri (>= 1.8.5)
43
- actionview (6.1.4.4)
44
- activesupport (= 6.1.4.4)
43
+ actionview (6.1.7.1)
44
+ activesupport (= 6.1.7.1)
45
45
  builder (~> 3.1)
46
46
  erubi (~> 1.4)
47
47
  rails-dom-testing (~> 2.0)
48
48
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
- activejob (6.1.4.4)
50
- activesupport (= 6.1.4.4)
49
+ activejob (6.1.7.1)
50
+ activesupport (= 6.1.7.1)
51
51
  globalid (>= 0.3.6)
52
- activemodel (6.1.4.4)
53
- activesupport (= 6.1.4.4)
54
- activerecord (6.1.4.4)
55
- activemodel (= 6.1.4.4)
56
- activesupport (= 6.1.4.4)
57
- activestorage (6.1.4.4)
58
- actionpack (= 6.1.4.4)
59
- activejob (= 6.1.4.4)
60
- activerecord (= 6.1.4.4)
61
- activesupport (= 6.1.4.4)
62
- marcel (~> 1.0.0)
52
+ activemodel (6.1.7.1)
53
+ activesupport (= 6.1.7.1)
54
+ activerecord (6.1.7.1)
55
+ activemodel (= 6.1.7.1)
56
+ activesupport (= 6.1.7.1)
57
+ activestorage (6.1.7.1)
58
+ actionpack (= 6.1.7.1)
59
+ activejob (= 6.1.7.1)
60
+ activerecord (= 6.1.7.1)
61
+ activesupport (= 6.1.7.1)
62
+ marcel (~> 1.0)
63
63
  mini_mime (>= 1.1.0)
64
- activesupport (6.1.4.4)
64
+ activesupport (6.1.7.1)
65
65
  concurrent-ruby (~> 1.0, >= 1.0.2)
66
66
  i18n (>= 1.6, < 2)
67
67
  minitest (>= 5.1)
@@ -74,15 +74,16 @@ GEM
74
74
  codecov (0.6.0)
75
75
  simplecov (>= 0.15, < 0.22)
76
76
  coderay (1.1.3)
77
- concurrent-ruby (1.1.10)
77
+ concurrent-ruby (1.3.1)
78
78
  crass (1.0.6)
79
79
  diff-lcs (1.5.0)
80
80
  docile (1.4.0)
81
81
  erubi (1.10.0)
82
82
  globalid (1.0.1)
83
83
  activesupport (>= 5.0)
84
- graphql (2.0.21)
85
- i18n (1.12.0)
84
+ graphql (2.1.7)
85
+ racc (~> 1.4)
86
+ i18n (1.14.5)
86
87
  concurrent-ruby (~> 1.0)
87
88
  loofah (2.19.1)
88
89
  crass (~> 1.0.2)
@@ -92,8 +93,8 @@ GEM
92
93
  marcel (1.0.2)
93
94
  method_source (1.0.0)
94
95
  mini_mime (1.1.2)
95
- mini_portile2 (2.8.1)
96
- minitest (5.17.0)
96
+ mini_portile2 (2.8.6)
97
+ minitest (5.23.1)
97
98
  mongo (2.17.0)
98
99
  bson (>= 4.8.2, < 5.0.0)
99
100
  mongoid (7.3.3)
@@ -101,8 +102,8 @@ GEM
101
102
  mongo (>= 2.10.5, < 3.0.0)
102
103
  ruby2_keywords (~> 0.0.5)
103
104
  nio4r (2.5.8)
104
- nokogiri (1.14.3)
105
- mini_portile2 (~> 2.8.0)
105
+ nokogiri (1.16.5)
106
+ mini_portile2 (~> 2.8.2)
106
107
  racc (~> 1.4)
107
108
  parallel (1.21.0)
108
109
  parser (3.1.0.0)
@@ -113,40 +114,41 @@ GEM
113
114
  pry-byebug (3.9.0)
114
115
  byebug (~> 11.0)
115
116
  pry (~> 0.13.0)
116
- racc (1.6.2)
117
- rack (2.2.6.4)
117
+ racc (1.8.0)
118
+ rack (2.2.9)
118
119
  rack-test (1.1.0)
119
120
  rack (>= 1.0, < 3)
120
- rails (6.1.4.4)
121
- actioncable (= 6.1.4.4)
122
- actionmailbox (= 6.1.4.4)
123
- actionmailer (= 6.1.4.4)
124
- actionpack (= 6.1.4.4)
125
- actiontext (= 6.1.4.4)
126
- actionview (= 6.1.4.4)
127
- activejob (= 6.1.4.4)
128
- activemodel (= 6.1.4.4)
129
- activerecord (= 6.1.4.4)
130
- activestorage (= 6.1.4.4)
131
- activesupport (= 6.1.4.4)
121
+ rails (6.1.7.1)
122
+ actioncable (= 6.1.7.1)
123
+ actionmailbox (= 6.1.7.1)
124
+ actionmailer (= 6.1.7.1)
125
+ actionpack (= 6.1.7.1)
126
+ actiontext (= 6.1.7.1)
127
+ actionview (= 6.1.7.1)
128
+ activejob (= 6.1.7.1)
129
+ activemodel (= 6.1.7.1)
130
+ activerecord (= 6.1.7.1)
131
+ activestorage (= 6.1.7.1)
132
+ activesupport (= 6.1.7.1)
132
133
  bundler (>= 1.15.0)
133
- railties (= 6.1.4.4)
134
+ railties (= 6.1.7.1)
134
135
  sprockets-rails (>= 2.0.0)
135
136
  rails-dom-testing (2.0.3)
136
137
  activesupport (>= 4.2.0)
137
138
  nokogiri (>= 1.6)
138
139
  rails-html-sanitizer (1.4.4)
139
140
  loofah (~> 2.19, >= 2.19.1)
140
- railties (6.1.4.4)
141
- actionpack (= 6.1.4.4)
142
- activesupport (= 6.1.4.4)
141
+ railties (6.1.7.1)
142
+ actionpack (= 6.1.7.1)
143
+ activesupport (= 6.1.7.1)
143
144
  method_source
144
- rake (>= 0.13)
145
+ rake (>= 12.2)
145
146
  thor (~> 1.0)
146
147
  rainbow (3.1.1)
147
148
  rake (13.0.6)
148
149
  regexp_parser (2.2.0)
149
- rexml (3.2.5)
150
+ rexml (3.2.8)
151
+ strscan (>= 3.0.9)
150
152
  rspec (3.10.0)
151
153
  rspec-core (~> 3.10.0)
152
154
  rspec-expectations (~> 3.10.0)
@@ -192,14 +194,15 @@ GEM
192
194
  actionpack (>= 5.2)
193
195
  activesupport (>= 5.2)
194
196
  sprockets (>= 3.0.0)
197
+ strscan (3.1.0)
195
198
  thor (1.2.1)
196
- tzinfo (2.0.5)
199
+ tzinfo (2.0.6)
197
200
  concurrent-ruby (~> 1.0)
198
201
  unicode-display_width (1.8.0)
199
202
  websocket-driver (0.7.5)
200
203
  websocket-extensions (>= 0.1.0)
201
204
  websocket-extensions (0.1.5)
202
- zeitwerk (2.6.6)
205
+ zeitwerk (2.6.15)
203
206
 
204
207
  PLATFORMS
205
208
  ruby
@@ -194,7 +194,21 @@ class UsersController < GraphqlRails::Controller
194
194
  action(:index).paginated(max_page_size: 10) # add max items limit
195
195
 
196
196
  def index
197
- User.all # it will render 10 users even you have more
197
+ User.all # it will render max 10 users even you have requested more
198
+ end
199
+ end
200
+ ```
201
+
202
+ #### *default_page_size*
203
+
204
+ Allows to specify max items count per request
205
+
206
+ ```ruby
207
+ class UsersController < GraphqlRails::Controller
208
+ action(:index).paginated(default_page_size: 5) # add default items per page size
209
+
210
+ def index
211
+ User.all # it will render 5 users even you have more
198
212
  end
199
213
  end
200
214
  ```
@@ -368,6 +368,21 @@ class User
368
368
  end
369
369
  ```
370
370
 
371
+ ### attribute.same_as
372
+
373
+ When you want to have identical attributes, you can use `Attribute#same_as` to make sure that attribute params will stay in sync:
374
+
375
+ ```ruby
376
+ class User
377
+ include GraphqlRails::Model
378
+
379
+ graphql do |c|
380
+ c.attribute(:user_id).type('ID').description('User ID')
381
+ c.attribute(:person_id).same_as(c.attribute(:user_id))
382
+ end
383
+ end
384
+ ```
385
+
371
386
  ### attribute.with
372
387
 
373
388
  When you want to define some options dynamically, it's quite handy to use "Attribute#with" method:
@@ -447,6 +462,30 @@ class User
447
462
  end
448
463
  ```
449
464
 
465
+ ## implements
466
+
467
+ `implements` indicates that graphql type implements one or more interfaces:
468
+
469
+ ```ruby
470
+ module UserInterface
471
+ include GraphQL::Schema::Interface
472
+ # ....
473
+ end
474
+
475
+ module AdvancedUserInterface
476
+ include GraphQL::Schema::Interface
477
+ # ...
478
+ end
479
+
480
+ class AdminUser
481
+ include GraphqlRails::Model
482
+
483
+ graphql do |c|
484
+ c.implements(UserInterface, AdvancedUserInterface)
485
+ end
486
+ end
487
+ ```
488
+
450
489
  ## graphql_type
451
490
 
452
491
  Sometimes it's handy to get raw graphql type. To do so you can call:
@@ -588,7 +627,6 @@ class User
588
627
  end
589
628
  ```
590
629
 
591
-
592
630
  #### input attribute deprecation
593
631
 
594
632
  You can mark input attribute as deprecated with `deprecated` method:
@@ -618,6 +656,59 @@ class User
618
656
  end
619
657
  ```
620
658
 
659
+ #### input attribute config copy
660
+
661
+ You can copy existing config from other attribute using `Attribute#same_as` method:
662
+
663
+ ```ruby
664
+ class User
665
+ include GraphqlRails::Model
666
+
667
+ graphql.input(:create) do |c|
668
+ c.attribute(:first_name).type('String!')
669
+ c.attribute(:last_name).type('String!')
670
+ end
671
+
672
+ graphql.input(:update) do |c|
673
+ c.attribute(:id).type('ID!')
674
+ graphql.input(:contract).attributes.each_value do |attr|
675
+ c.attribute(attr.name).same_as(attr)
676
+ end
677
+ end
678
+ end
679
+ ```
680
+
681
+ #### input attribute property
682
+
683
+ Sometimes it's handy to have different input attribute on graphql level and different on controller. That's when `Attribute#property` comes to the rescue:
684
+
685
+ ```ruby
686
+ class Post
687
+ include GraphqlRails::Model
688
+
689
+ graphql.input do |c|
690
+ c.attribute(:author_id).type('ID').property(:user_id)
691
+ end
692
+ end
693
+ ```
694
+
695
+ Then mutation such as:
696
+
697
+ ```gql
698
+ mutation createPost(input: { authorId: 123 }) {
699
+ author
700
+ }
701
+ ```
702
+
703
+ Will pass `user_id` instead of `authorId` in controller:
704
+
705
+ ```ruby
706
+ # posts_controller.rb
707
+ def create
708
+ Post.create!(user_id: params[:input][:user_id])
709
+ end
710
+ ```
711
+
621
712
  ## graphql_context
622
713
 
623
714
  It's possible to access graphql_context in your model using method `graphql_context`:
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_dependency 'graphql', '2.0.21'
23
+ spec.add_dependency 'graphql', '2.1.7'
24
24
  spec.add_dependency 'activesupport', '>= 4'
25
25
 
26
26
  spec.add_development_dependency 'bundler', '~> 2'
@@ -8,6 +8,12 @@ module GraphqlRails
8
8
  # contains methods which are shared between various attribute-like classes
9
9
  # expects `initial_name` and `type` to be defined
10
10
  module Attributable
11
+ def initialize_copy(_original)
12
+ super
13
+ @attribute_name_parser = nil
14
+ @type_parser = nil
15
+ end
16
+
11
17
  def field_name
12
18
  attribute_name_parser.field_name
13
19
  end
@@ -21,18 +21,12 @@ module GraphqlRails
21
21
  @attributes ||= {}
22
22
  end
23
23
 
24
- def property(new_value = NOT_SET)
25
- return @property if new_value == NOT_SET
26
-
27
- @property = new_value.to_s
28
- self
29
- end
30
-
31
24
  def field_args
32
25
  [
33
26
  field_name,
34
27
  type_parser.type_arg,
35
- description
28
+ description,
29
+ *field_args_options
36
30
  ].compact
37
31
  end
38
32
 
@@ -67,6 +61,17 @@ module GraphqlRails
67
61
  def deprecation_reason_params
68
62
  { deprecation_reason: deprecation_reason }.compact
69
63
  end
64
+
65
+ def field_args_options
66
+ options = { **field_args_pagination_options }
67
+ return nil if options.empty?
68
+
69
+ [options]
70
+ end
71
+
72
+ def field_args_pagination_options
73
+ pagination_options || {}
74
+ end
70
75
  end
71
76
  end
72
77
  end
@@ -64,6 +64,24 @@ module GraphqlRails
64
64
  def deprecation_reason
65
65
  @deprecation_reason
66
66
  end
67
+
68
+ def property(new_value = ChainableOptions::NOT_SET)
69
+ return @property if new_value == ChainableOptions::NOT_SET
70
+
71
+ @property = new_value.to_s
72
+ self
73
+ end
74
+
75
+ def same_as(other_attribute)
76
+ other = other_attribute.dup
77
+ other.instance_variables.each do |instance_variable|
78
+ next if instance_variable == :@initial_name
79
+
80
+ instance_variable_set(instance_variable, other.instance_variable_get(instance_variable))
81
+ end
82
+
83
+ self
84
+ end
67
85
  end
68
86
  end
69
87
  end
@@ -33,6 +33,7 @@ module GraphqlRails
33
33
  groups: groups,
34
34
  hidden_in_groups: hidden_in_groups,
35
35
  **default_value_option,
36
+ **property_params,
36
37
  **deprecation_reason_params
37
38
  }
38
39
  end
@@ -59,6 +60,10 @@ module GraphqlRails
59
60
  { deprecation_reason: deprecation_reason }.compact
60
61
  end
61
62
 
63
+ def property_params
64
+ { as: property }.compact
65
+ end
66
+
62
67
  def attribute_naming_options
63
68
  options.slice(:input_format)
64
69
  end
@@ -94,9 +94,9 @@ module GraphqlRails
94
94
  end
95
95
 
96
96
  def graphql_type_object?(type_class)
97
- return false unless type_class.is_a?(Class)
97
+ return false if !type_class.is_a?(Class) && !type_class.is_a?(Module)
98
98
 
99
- type_class < GraphQL::Schema::Member
99
+ type_class < GraphQL::Schema::Member::GraphQLTypeNames
100
100
  end
101
101
 
102
102
  def applicable_graphql_type?(type)
@@ -43,16 +43,16 @@ module GraphqlRails
43
43
  { null: !type_parser.required? }
44
44
  end
45
45
 
46
+ def action_config
47
+ controller.controller_configuration.action_config(name)
48
+ end
49
+
46
50
  private
47
51
 
48
52
  attr_reader :route
49
53
 
50
54
  delegate :type_parser, to: :action_config
51
55
 
52
- def action_config
53
- controller.controller_configuration.action_config(name)
54
- end
55
-
56
56
  def namespaced_controller_name
57
57
  [route.module_name, controller_name].reject(&:empty?).join('/')
58
58
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql_rails/controller/action'
4
3
  require 'graphql_rails/concerns/service'
5
4
  require 'graphql_rails/controller/action_configuration'
6
5
  require 'graphql_rails/controller/build_controller_action_resolver/controller_action_resolver'
@@ -11,12 +10,12 @@ module GraphqlRails
11
10
  class BuildControllerActionResolver
12
11
  include ::GraphqlRails::Service
13
12
 
14
- def initialize(route:)
15
- @route = route
13
+ def initialize(action:)
14
+ @action = action
16
15
  end
17
16
 
18
17
  def call # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
- action = build_action
18
+ action = self.action
20
19
 
21
20
  Class.new(ControllerActionResolver) do
22
21
  graphql_name("ControllerActionResolver#{SecureRandom.hex}")
@@ -38,17 +37,7 @@ module GraphqlRails
38
37
 
39
38
  private
40
39
 
41
- attr_reader :route
42
-
43
- def build_action
44
- Action.new(route).tap do |action|
45
- assert_action(action)
46
- end
47
- end
48
-
49
- def assert_action(action)
50
- action.return_type
51
- end
40
+ attr_reader :action
52
41
  end
53
42
  end
54
43
  end
@@ -13,7 +13,7 @@ module GraphqlRails
13
13
 
14
14
  LIB_REGEXP = %r{/graphql_rails/lib/}
15
15
 
16
- attr_reader :action_by_name
16
+ attr_reader :action_by_name, :error_handlers
17
17
 
18
18
  def initialize(controller)
19
19
  @controller = controller
@@ -25,6 +25,7 @@ module GraphqlRails
25
25
 
26
26
  @action_by_name = {}
27
27
  @action_default = nil
28
+ @error_handlers = {}
28
29
  end
29
30
 
30
31
  def initialize_copy(other)
@@ -56,6 +57,10 @@ module GraphqlRails
56
57
  ActionHook.new(name: hook_name, **options, &block)
57
58
  end
58
59
 
60
+ def add_error_handler(error, with:, &block)
61
+ @error_handlers[error] = with || block
62
+ end
63
+
59
64
  def action_default
60
65
  @action_default ||= ActionConfiguration.new(name: :default, controller: nil)
61
66
  yield(@action_default) if block_given?
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ class Controller
5
+ # runs {before/around/after}_action controller hooks
6
+ class HandleControllerError
7
+ def initialize(error:, controller:)
8
+ @error = error
9
+ @controller = controller
10
+ end
11
+
12
+ def call
13
+ return custom_handle_error if custom_handle_error?
14
+
15
+ render_unhandled_error(error)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :error, :controller
21
+
22
+ def render_unhandled_error(error)
23
+ return render(error: error) if error.is_a?(GraphQL::ExecutionError)
24
+
25
+ render(error: SystemError.new(error))
26
+ end
27
+
28
+ def custom_handle_error
29
+ return unless custom_handler
30
+
31
+ begin
32
+ if custom_handler.is_a?(Proc)
33
+ controller.instance_exec(error, &custom_handler)
34
+ else
35
+ controller.send(custom_handler)
36
+ end
37
+ rescue StandardError => e
38
+ render_unhandled_error(e)
39
+ end
40
+ end
41
+
42
+ def custom_handler
43
+ return @custom_handler if defined?(@custom_handler)
44
+
45
+ handler = controller_config.error_handlers.detect do |error_class, _handler|
46
+ error.class <= error_class
47
+ end
48
+
49
+ @custom_handler = handler&.last
50
+ end
51
+
52
+ def custom_handle_error?
53
+ custom_handler.present?
54
+ end
55
+
56
+ def controller_config
57
+ @controller_config ||= controller.class.controller_configuration
58
+ end
59
+
60
+ def render(*args, **kwargs, &block)
61
+ controller.send(:render, *args, **kwargs, &block)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -7,6 +7,7 @@ module GraphqlRails
7
7
  # logs controller start and end times
8
8
  class LogControllerAction
9
9
  require 'graphql_rails/concerns/service'
10
+ require 'active_support/notifications'
10
11
 
11
12
  include ::GraphqlRails::Service
12
13
 
@@ -6,6 +6,7 @@ require 'graphql_rails/controller/configuration'
6
6
  require 'graphql_rails/controller/request'
7
7
  require 'graphql_rails/controller/action_hooks_runner'
8
8
  require 'graphql_rails/controller/log_controller_action'
9
+ require 'graphql_rails/controller/handle_controller_error'
9
10
  require 'graphql_rails/errors/system_error'
10
11
 
11
12
  module GraphqlRails
@@ -45,6 +46,12 @@ module GraphqlRails
45
46
  def controller_configuration
46
47
  @controller_configuration ||= Controller::Configuration.new(self)
47
48
  end
49
+
50
+ def rescue_from(*errors, with: nil, &block)
51
+ Array(errors).each do |error|
52
+ controller_configuration.add_error_handler(error, with: with, &block)
53
+ end
54
+ end
48
55
  end
49
56
 
50
57
  attr_reader :action_name
@@ -86,12 +93,8 @@ module GraphqlRails
86
93
  response = hooks_runner.call { public_send(action_name) }
87
94
 
88
95
  render response if graphql_request.no_object_to_return?
89
- rescue StandardError => error
90
- if error.is_a?(GraphQL::ExecutionError)
91
- render error: error
92
- else
93
- render error: SystemError.new(error)
94
- end
96
+ rescue StandardError => e
97
+ HandleControllerError.new(error: e, controller: self).call
95
98
  end
96
99
 
97
100
  def graphql_errors_from_render_params(rendering_params)
@@ -24,10 +24,21 @@ module GraphqlRails
24
24
  @input = other.instance_variable_get(:@input)&.transform_values(&:dup)
25
25
  end
26
26
 
27
+ def implements(*interfaces)
28
+ previous_implements = get_or_set_chainable_option(:implements) || []
29
+ return previous_implements if interfaces.blank?
30
+
31
+ full_implements = (previous_implements + interfaces).uniq
32
+
33
+ get_or_set_chainable_option(:implements, full_implements) || []
34
+ end
35
+
27
36
  def attribute(attribute_name, **attribute_options)
28
37
  key = attribute_name.to_s
29
38
 
30
- attributes[key] ||= build_attribute(attribute_name).tap do |new_attribute|
39
+ attributes[key] ||= build_attribute(attribute_name)
40
+
41
+ attributes[key].tap do |new_attribute|
31
42
  new_attribute.with(**attribute_options)
32
43
  yield(new_attribute) if block_given?
33
44
  end
@@ -52,7 +63,8 @@ module GraphqlRails
52
63
  name: name,
53
64
  description: description,
54
65
  attributes: attributes,
55
- type_name: type_name
66
+ type_name: type_name,
67
+ implements: implements
56
68
  )
57
69
  end
58
70
 
@@ -86,6 +98,7 @@ module GraphqlRails
86
98
  description: description,
87
99
  attributes: attributes,
88
100
  type_name: type_name,
101
+ implements: implements,
89
102
  force_define_attributes: true
90
103
  )
91
104
  end
@@ -23,6 +23,14 @@ module GraphqlRails
23
23
  end
24
24
  end
25
25
  end
26
+
27
+ def find_or_build_dynamic_type(attribute)
28
+ graphql_model = attribute.graphql_model
29
+ return unless graphql_model
30
+
31
+ graphql = graphql_model.graphql.input(attribute.subtype)
32
+ find_or_build_graphql_model_type(graphql)
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -10,21 +10,35 @@ module GraphqlRails
10
10
 
11
11
  include ::GraphqlRails::Service
12
12
 
13
- def initialize(name:, description:, attributes:, type_name:, force_define_attributes: false)
13
+ # rubocop:disable Metrics/ParameterLists
14
+ def initialize(
15
+ name:,
16
+ description:,
17
+ attributes:,
18
+ type_name:,
19
+ force_define_attributes: false,
20
+ implements: []
21
+ )
14
22
  @name = name
15
23
  @description = description
16
24
  @attributes = attributes
17
25
  @type_name = type_name
18
26
  @force_define_attributes = force_define_attributes
27
+ @implements = implements
19
28
  end
29
+ # rubocop:enable Metrics/ParameterLists
20
30
 
21
31
  def call
22
- klass.tap { add_attributes if new_class? || force_define_attributes }
32
+ klass.tap do
33
+ add_attributes if new_class? || force_define_attributes
34
+ add_interfaces
35
+ end
23
36
  end
24
37
 
25
38
  private
26
39
 
27
- attr_reader :name, :description, :attributes, :type_name, :force_define_attributes
40
+ attr_reader :name, :description, :attributes, :type_name, :force_define_attributes,
41
+ :implements
28
42
 
29
43
  delegate :klass, :new_class?, to: :type_class_finder
30
44
 
@@ -41,6 +55,7 @@ module GraphqlRails
41
55
  name: name,
42
56
  type_name: type_name,
43
57
  description: description,
58
+ implements: implements,
44
59
  parent_class: parent_class
45
60
  )
46
61
  end
@@ -53,17 +68,27 @@ module GraphqlRails
53
68
  add_attributes_batch(dynamic_attributes)
54
69
  end
55
70
 
71
+ def add_interfaces
72
+ implements.each do |interface|
73
+ next if klass.interfaces.include?(interface)
74
+
75
+ klass.implements(interface)
76
+ end
77
+ end
78
+
56
79
  def find_or_build_dynamic_type(attribute)
57
80
  graphql_model = attribute.graphql_model
58
- find_or_build_graphql_model_type(graphql_model) if graphql_model
81
+ return unless graphql_model
82
+
83
+ find_or_build_graphql_model_type(graphql_model.graphql)
59
84
  end
60
85
 
61
- def find_or_build_graphql_model_type(graphql_model)
86
+ def find_or_build_graphql_model_type(graphql_config)
62
87
  self.class.call(
63
- name: graphql_model.graphql.name,
64
- description: graphql_model.graphql.description,
65
- attributes: graphql_model.graphql.attributes,
66
- type_name: graphql_model.graphql.type_name
88
+ name: graphql_config.name,
89
+ description: graphql_config.description,
90
+ attributes: graphql_config.attributes,
91
+ type_name: graphql_config.type_name
67
92
  )
68
93
  end
69
94
  end
@@ -9,12 +9,13 @@ module GraphqlRails
9
9
 
10
10
  include ::GraphqlRails::Service
11
11
 
12
- def initialize(name:, type_name:, parent_class:, description: nil)
12
+ def initialize(name:, type_name:, parent_class:, description: nil, implements: [])
13
13
  @name = name
14
14
  @type_name = type_name
15
15
  @description = description
16
16
  @new_class = false
17
17
  @parent_class = parent_class
18
+ @implements = implements
18
19
  end
19
20
 
20
21
  def klass
@@ -28,15 +29,17 @@ module GraphqlRails
28
29
  private
29
30
 
30
31
  attr_accessor :new_class
31
- attr_reader :name, :type_name, :description, :parent_class
32
+ attr_reader :name, :type_name, :description, :parent_class, :implements
32
33
 
33
34
  def build_graphql_type_klass
34
35
  graphql_type_name = name
35
36
  graphql_type_description = description
37
+ interfaces = implements
36
38
 
37
39
  graphql_type_klass = Class.new(parent_class) do
38
40
  graphql_name(graphql_type_name)
39
41
  description(graphql_type_description)
42
+ interfaces.each { |interface| implements(interface) }
40
43
  end
41
44
 
42
45
  self.new_class = true
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../controller/build_controller_action_resolver'
4
+ require 'graphql_rails/controller/action'
4
5
 
5
6
  module GraphqlRails
6
7
  class Router
@@ -38,7 +39,7 @@ module GraphqlRails
38
39
  if function
39
40
  { function: function }
40
41
  else
41
- { resolver: resolver, extras: [:lookahead] }
42
+ { resolver: resolver, extras: [:lookahead], **resolver_options }
42
43
  end
43
44
  end
44
45
 
@@ -47,7 +48,16 @@ module GraphqlRails
47
48
  attr_reader :function
48
49
 
49
50
  def resolver
50
- @resolver ||= Controller::BuildControllerActionResolver.call(route: self)
51
+ @resolver ||= Controller::BuildControllerActionResolver.call(action: action)
52
+ end
53
+
54
+ def action
55
+ @action ||= Controller::Action.new(self).tap(&:return_type)
56
+ end
57
+
58
+ def resolver_options
59
+ action_config = action.action_config
60
+ action_config.pagination_options || {}
51
61
  end
52
62
  end
53
63
  end
@@ -46,6 +46,14 @@ module GraphqlRails
46
46
  !success?
47
47
  end
48
48
 
49
+ def controller
50
+ request.controller
51
+ end
52
+
53
+ def action_name
54
+ request.action_name
55
+ end
56
+
49
57
  private
50
58
 
51
59
  attr_reader :request
@@ -94,9 +102,13 @@ module GraphqlRails
94
102
 
95
103
  # controller request object more suitable for testing
96
104
  class Request < GraphqlRails::Controller::Request
97
- def initialize(params, context)
105
+ attr_reader :controller, :action_name
106
+
107
+ def initialize(params, context, controller: nil, action_name: nil)
98
108
  inputs = params || {}
99
109
  inputs = inputs.merge(lookahead: ::GraphQL::Execution::Lookahead::NullLookahead.new)
110
+ @controller = controller
111
+ @action_name = action_name
100
112
  super(nil, inputs, context)
101
113
  end
102
114
  end
@@ -104,7 +116,7 @@ module GraphqlRails
104
116
  def query(query_name, params: {}, context: {})
105
117
  schema_builder = SingleControllerSchemaBuilder.new(described_class)
106
118
  context_object = FakeContext.new(values: context, schema: schema_builder.call)
107
- request = Request.new(params, context_object)
119
+ request = Request.new(params, context_object, controller: described_class, action_name: query_name)
108
120
  described_class.new(request).call(query_name)
109
121
 
110
122
  @response = Response.new(request)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlRails
4
- VERSION = '2.4.0'
4
+ VERSION = '3.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Povilas Jurčys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-25 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.21
19
+ version: 2.1.7
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 2.0.21
26
+ version: 2.1.7
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -184,6 +184,7 @@ files:
184
184
  - lib/graphql_rails/controller/build_controller_action_resolver.rb
185
185
  - lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb
186
186
  - lib/graphql_rails/controller/configuration.rb
187
+ - lib/graphql_rails/controller/handle_controller_error.rb
187
188
  - lib/graphql_rails/controller/log_controller_action.rb
188
189
  - lib/graphql_rails/controller/request.rb
189
190
  - lib/graphql_rails/controller/request/format_errors.rb