graphql_rails 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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