rails-graphql 1.0.0.beta → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/ext/gql_parser.c +1 -16
  3. data/ext/gql_parser.h +21 -0
  4. data/ext/shared.c +0 -5
  5. data/ext/shared.h +6 -6
  6. data/lib/generators/graphql/channel_generator.rb +27 -0
  7. data/lib/generators/graphql/controller_generator.rb +9 -4
  8. data/lib/generators/graphql/install_generator.rb +49 -0
  9. data/lib/generators/graphql/schema_generator.rb +9 -4
  10. data/lib/generators/graphql/templates/channel.erb +7 -0
  11. data/lib/generators/graphql/templates/config.rb +97 -0
  12. data/lib/generators/graphql/templates/controller.erb +2 -0
  13. data/lib/generators/graphql/templates/schema.erb +5 -3
  14. data/lib/gql_parser.so +0 -0
  15. data/lib/rails/graphql/alternative/field_set.rb +12 -0
  16. data/lib/rails/graphql/alternative/query.rb +13 -8
  17. data/lib/rails/graphql/alternative/subscription.rb +2 -1
  18. data/lib/rails/graphql/alternative.rb +4 -0
  19. data/lib/rails/graphql/argument.rb +5 -3
  20. data/lib/rails/graphql/callback.rb +10 -8
  21. data/lib/rails/graphql/collectors/hash_collector.rb +12 -1
  22. data/lib/rails/graphql/collectors/json_collector.rb +21 -0
  23. data/lib/rails/graphql/config.rb +86 -59
  24. data/lib/rails/graphql/directive/include_directive.rb +0 -1
  25. data/lib/rails/graphql/directive/skip_directive.rb +0 -1
  26. data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
  27. data/lib/rails/graphql/directive.rb +31 -25
  28. data/lib/rails/graphql/event.rb +7 -6
  29. data/lib/rails/graphql/field/authorized_field.rb +0 -5
  30. data/lib/rails/graphql/field/input_field.rb +0 -5
  31. data/lib/rails/graphql/field/mutation_field.rb +5 -6
  32. data/lib/rails/graphql/field/output_field.rb +13 -2
  33. data/lib/rails/graphql/field/proxied_field.rb +6 -6
  34. data/lib/rails/graphql/field/resolved_field.rb +1 -1
  35. data/lib/rails/graphql/field/subscription_field.rb +35 -52
  36. data/lib/rails/graphql/field/typed_field.rb +26 -2
  37. data/lib/rails/graphql/field.rb +20 -19
  38. data/lib/rails/graphql/global_id.rb +5 -1
  39. data/lib/rails/graphql/helpers/inherited_collection/array.rb +1 -0
  40. data/lib/rails/graphql/helpers/inherited_collection/base.rb +3 -1
  41. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +2 -1
  42. data/lib/rails/graphql/helpers/registerable.rb +1 -1
  43. data/lib/rails/graphql/helpers/with_arguments.rb +3 -2
  44. data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
  45. data/lib/rails/graphql/helpers/with_callbacks.rb +3 -3
  46. data/lib/rails/graphql/helpers/with_description.rb +10 -8
  47. data/lib/rails/graphql/helpers/with_directives.rb +5 -1
  48. data/lib/rails/graphql/helpers/with_events.rb +1 -0
  49. data/lib/rails/graphql/helpers/with_fields.rb +30 -24
  50. data/lib/rails/graphql/helpers/with_name.rb +3 -2
  51. data/lib/rails/graphql/helpers/with_schema_fields.rb +75 -51
  52. data/lib/rails/graphql/introspection.rb +1 -1
  53. data/lib/rails/graphql/railtie.rb +3 -2
  54. data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
  55. data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
  56. data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
  57. data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
  58. data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
  59. data/lib/rails/graphql/railties/base_generator.rb +3 -9
  60. data/lib/rails/graphql/railties/channel.rb +8 -8
  61. data/lib/rails/graphql/railties/controller.rb +51 -26
  62. data/lib/rails/graphql/request/arguments.rb +2 -1
  63. data/lib/rails/graphql/request/backtrace.rb +31 -10
  64. data/lib/rails/graphql/request/component/field.rb +15 -8
  65. data/lib/rails/graphql/request/component/fragment.rb +13 -7
  66. data/lib/rails/graphql/request/component/operation/subscription.rb +4 -6
  67. data/lib/rails/graphql/request/component/operation.rb +12 -5
  68. data/lib/rails/graphql/request/component/spread.rb +13 -4
  69. data/lib/rails/graphql/request/component/typename.rb +1 -1
  70. data/lib/rails/graphql/request/component.rb +2 -0
  71. data/lib/rails/graphql/request/context.rb +1 -1
  72. data/lib/rails/graphql/request/event.rb +6 -2
  73. data/lib/rails/graphql/request/helpers/directives.rb +1 -0
  74. data/lib/rails/graphql/request/helpers/selection_set.rb +10 -4
  75. data/lib/rails/graphql/request/helpers/value_writers.rb +8 -5
  76. data/lib/rails/graphql/request/prepared_data.rb +3 -1
  77. data/lib/rails/graphql/request/steps/organizable.rb +1 -1
  78. data/lib/rails/graphql/request/steps/preparable.rb +1 -1
  79. data/lib/rails/graphql/request/steps/resolvable.rb +1 -1
  80. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +3 -3
  81. data/lib/rails/graphql/request/strategy.rb +18 -4
  82. data/lib/rails/graphql/request/subscription.rb +18 -16
  83. data/lib/rails/graphql/request.rb +71 -41
  84. data/lib/rails/graphql/schema.rb +39 -86
  85. data/lib/rails/graphql/shortcuts.rb +11 -5
  86. data/lib/rails/graphql/source/active_record/builders.rb +22 -24
  87. data/lib/rails/graphql/source/active_record_source.rb +96 -34
  88. data/lib/rails/graphql/source/base.rb +13 -40
  89. data/lib/rails/graphql/source/builder.rb +14 -22
  90. data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
  91. data/lib/rails/graphql/source.rb +31 -38
  92. data/lib/rails/graphql/subscription/provider/action_cable.rb +10 -9
  93. data/lib/rails/graphql/subscription/provider/base.rb +6 -5
  94. data/lib/rails/graphql/subscription/store/base.rb +5 -9
  95. data/lib/rails/graphql/subscription/store/memory.rb +18 -9
  96. data/lib/rails/graphql/type/creator.rb +198 -0
  97. data/lib/rails/graphql/type/enum.rb +17 -9
  98. data/lib/rails/graphql/type/input.rb +30 -7
  99. data/lib/rails/graphql/type/interface.rb +15 -4
  100. data/lib/rails/graphql/type/object/directive_object.rb +6 -5
  101. data/lib/rails/graphql/type/object/input_value_object.rb +3 -4
  102. data/lib/rails/graphql/type/object/type_object.rb +40 -13
  103. data/lib/rails/graphql/type/object.rb +11 -6
  104. data/lib/rails/graphql/type/scalar/binary_scalar.rb +2 -0
  105. data/lib/rails/graphql/type/scalar/date_scalar.rb +2 -0
  106. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +2 -0
  107. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +2 -0
  108. data/lib/rails/graphql/type/scalar/json_scalar.rb +3 -1
  109. data/lib/rails/graphql/type/scalar/time_scalar.rb +3 -1
  110. data/lib/rails/graphql/type/scalar.rb +2 -2
  111. data/lib/rails/graphql/type/union.rb +7 -2
  112. data/lib/rails/graphql/type.rb +10 -2
  113. data/lib/rails/graphql/type_map.rb +20 -7
  114. data/lib/rails/graphql/uri.rb +5 -4
  115. data/lib/rails/graphql/version.rb +6 -2
  116. data/lib/rails/graphql.rb +11 -8
  117. data/test/assets/introspection-mem.txt +1 -1
  118. data/test/assets/introspection.gql +2 -0
  119. data/test/assets/mem.gql +74 -60
  120. data/test/assets/mysql.gql +69 -55
  121. data/test/assets/sqlite.gql +78 -64
  122. data/test/assets/translate.gql +50 -39
  123. data/test/config.rb +2 -1
  124. data/test/graphql/schema_test.rb +2 -31
  125. data/test/graphql/source_test.rb +1 -11
  126. data/test/graphql/type/interface_test.rb +8 -5
  127. data/test/graphql/type/object_test.rb +8 -2
  128. data/test/graphql/type_map_test.rb +13 -16
  129. data/test/integration/global_id_test.rb +4 -4
  130. data/test/integration/memory/star_wars_validation_test.rb +2 -2
  131. data/test/integration/mysql/star_wars_introspection_test.rb +1 -1
  132. data/test/integration/resolver_precedence_test.rb +1 -1
  133. data/test/integration/schemas/memory.rb +3 -4
  134. data/test/integration/sqlite/star_wars_global_id_test.rb +27 -21
  135. data/test/integration/sqlite/star_wars_introspection_test.rb +1 -1
  136. data/test/integration/translate_test.rb +26 -14
  137. metadata +22 -9
@@ -3,20 +3,22 @@
3
3
  module Rails
4
4
  module GraphQL
5
5
  configure do |config|
6
- # This helps to keep track of when things were cached and registered.
7
- # Cached objects with mismatching versions needs to be upgraded or simply
8
- # reloaded. A good way to use this is to set to the commit hash, but
9
- # beware to stick to 8 characters.
6
+ # This helps to keep track of when things were cached and registered. Cached
7
+ # objects with mismatching versions need to be upgraded or simply reloaded.
8
+ # An excellent way to use this is to set it to the commit hash. TypePap will
9
+ # always use only the first 8 characters for simplicity.
10
10
  config.version = nil
11
11
 
12
- # This will be automatically mapped to +Rails.cache+. Manually setting
13
- # this property means that the object in it complies with
14
- # +ActiveSupport::Cache::Store+.
12
+ # The instance responsible for caching all the information generated by
13
+ # requests and all the other components. Manually setting this property
14
+ # means that the object in it complies with `ActiveSupport::Cache::Store`.
15
+ # This will map automatically to `Rails.cache` if kept as `nil`. This can
16
+ # also be set per Schema.
15
17
  config.cache = nil
16
18
 
17
- # If Rails cache is not properly defined, by default it is set to a
18
- # NullStore, than fallback to this option to get a memory store because
19
- # cache is extremely important, especially for subscriptions
19
+ # If Rails cache is not properly defined or just set to use a NullStore,
20
+ # this fallback will set itself up with a memory store because cache is
21
+ # crucial, especially for subscriptions.
20
22
  config.cache_fallback = -> do
21
23
  ::ActiveSupport::Cache::MemoryStore.new(max_prune_time: nil)
22
24
  end
@@ -27,42 +29,47 @@ module Rails
27
29
 
28
30
  # The list of nested paths inside of the graphql folder that does not
29
31
  # require to be in their own namespace.
30
- config.paths = %w[directives fields sources enums inputs interfaces object
31
- scalars unions].to_set
32
+ config.paths = %w[directives fields sources enums inputs interfaces objects
33
+ scalars unions concerns].to_set
32
34
 
33
- # This exposes the clean path from where a GraphQL request was started.
35
+ # This is very similar to `ActiveRecord` verbose logs, which simply show the
36
+ # path of the file that started a GraphQL request.
34
37
  config.verbose_logs = true
35
38
 
36
- # The list of parameters to omit from logger when running a GraphQL
37
- # request. Those values will be better displayed in the internal runtime
38
- # logger controller.
39
+ # The list of parameters to omit from the logger when running a GraphQL
40
+ # request. Those values are displayed better in the internal runtime logger
41
+ # controller.
39
42
  config.omit_parameters = %w[query operationName operation_name variables graphql]
40
43
 
41
- # This list will actually affect what is displayed in the logs. When it is
42
- # set to nil, it will copy its value from Rails +filter_parameters+.
44
+ # Identical to the one available on a Rails application, but exclusive for
45
+ # GraphQL operations. The list of parameters to display as filtered in the
46
+ # logs. When it is nil, it will use the same as the Rails application.
43
47
  config.filter_parameters = nil
44
48
 
45
- # A list of ActiveRecord adapters and their specific internal naming used
46
- # to compound the accessors for direct query serialization.
49
+ # A list of all `ActiveRecord` adapters supported. When an adapter is
50
+ # supported, it will map the database types into GraphQL types using proper
51
+ # aliases. Plus, it will have the method to map models attributes to their
52
+ # equivalent fields.
47
53
  config.ar_adapters = {
48
54
  'Mysql2' => { key: :mysql, path: "#{__dir__}/adapters/mysql_adapter" },
49
55
  'PostgreSQL' => { key: :pg, path: "#{__dir__}/adapters/pg_adapter" },
50
56
  'SQLite' => { key: :sqlite, path: "#{__dir__}/adapters/sqlite_adapter" },
51
57
  }
52
58
 
53
- # For all the input object type defined, auto add the following prefix to
54
- # their name, so we don't have to define classes like +PointInputInput+.
59
+ # The suffix that is added automatically to all the Input type objects. This
60
+ # prevents situations like `PointInputInput`. If your inputs have a
61
+ # different suffix, change this value to it.
55
62
  config.auto_suffix_input_objects = 'Input'
56
63
 
57
- # Introspection is enabled by default. Changing this will affect all the
58
- # schemas off the application and reduce memory usage. This can also be
59
- # set at per schema.
60
- config.enable_introspection = true
64
+ # Introspection is enabled by default. It is recommended to only use
65
+ # introspection during development and tests, never in production.
66
+ # This can also be set per schema level.
67
+ config.enable_introspection = false
61
68
 
62
69
  # Define the names of the schema/operations types. The single "_" is a
63
- # suggestion so that in an application that has, most likely, a
64
- # Subscription type, it does not generate a conflict. Plus, it is easy to
65
- # spot that it is something internal.
70
+ # suggestion. In an application that has a Subscription object, it will
71
+ # prevent the conflict. Plus, it is easy to spot that it is something
72
+ # internal. This can also be set per Schema.
66
73
  config.schema_type_names = {
67
74
  query: '_Query',
68
75
  mutation: '_Mutation',
@@ -71,13 +78,15 @@ module Rails
71
78
 
72
79
  # For performance purposes, this gem implements a
73
80
  # {JsonCollector}[rdoc-ref:Rails::GraphQL::Collectors::JsonCollector].
74
- # If you prefer to use the normal hash to string serialization, you can
75
- # disable this option.
81
+ # You can disable this option if you prefer to use the standard
82
+ # hash-to-string serialization provided by `ActiveSupport`.
83
+ # This can also be set per Schema.
76
84
  config.enable_string_collector = true
77
85
 
78
86
  # Set what is de default expected output type of GraphQL requests. String
79
- # combined with the previous setting has the best performance. On console,
80
- # it will automatically shift to hash.
87
+ # combined with the previous setting has the best performance. On the
88
+ # console, it will automatically shift to Hash. This can also be set per
89
+ # Schema.
81
90
  config.default_response_format = :string
82
91
 
83
92
  # Specifies if the results of operations should be encoded with
@@ -85,60 +94,63 @@ module Rails
85
94
  # See also https://github.com/rails/rails/blob/master/activesupport/lib/active_support/json/encoding.rb
86
95
  config.encode_with_active_support = false
87
96
 
88
- # Enable the ability of a callback to dynamically inject arguments to the
97
+ # Enable the ability of a callback to inject arguments dynamically into the
89
98
  # calling method.
90
99
  config.callback_inject_arguments = true
91
100
 
92
- # Enable the ability of a callback to dynamically inject named arguments
93
- # to the calling method.
101
+ # Enable the ability of a callback to inject named arguments dynamically
102
+ # into the calling method.
94
103
  config.callback_inject_named_arguments = true
95
104
 
96
- # When importing fields into other places, if the given class is
97
- # incompatible it will display an warning. This can make such warning be
98
- # silenced.
105
+ # When importing fields from modules or other objects, a warning is
106
+ # displayed for any given element that was not able to be correctly
107
+ # imported. You can silence such warnings by changing this option.
99
108
  config.silence_import_warnings = false
100
109
 
101
- # Enable the ability to active custom descriptions using i18n
102
- config.enable_i18n_descriptions = true
110
+ # Enable the ability to define the description of any object, field, or
111
+ # argument using I18n. It is recommended for multi-language documentation.
112
+ config.enable_i18n_descriptions = false
103
113
 
104
- # Specify the scopes for I18n translations
114
+ # The list of scopes that will be used to locate the descriptions.
105
115
  config.i18n_scopes = [
106
116
  'graphql.%{namespace}.%{kind}.%{parent}.%{name}',
107
117
  'graphql.%{namespace}.%{kind}.%{name}',
108
118
  'graphql.%{namespace}.%{name}',
109
119
  'graphql.%{kind}.%{parent}.%{name}',
110
120
  'graphql.%{kind}.%{name}',
111
- 'graphql.%{name}'
121
+ 'graphql.%{name}',
112
122
  ]
113
123
 
114
- # A list of execution strategies. Each application can add their own by
115
- # simply append a class name, preferable as string, in this list.
124
+ # A list of execution strategies. Each application can add its own by
125
+ # appending a class, preferably as a string, in this list. This can also be
126
+ # set per Schema.
116
127
  config.request_strategies = [
117
128
  'Rails::GraphQL::Request::Strategy::MultiQueryStrategy',
118
129
  'Rails::GraphQL::Request::Strategy::SequencedStrategy',
119
130
  # 'Rails::GraphQL::Request::Strategy::CachedStrategy',
120
131
  ]
121
132
 
122
- # A list of all possible rails-graphql-compatible sources.
133
+ # A list of all possible ruby-to-graphql compatible sources.
123
134
  config.sources = [
124
135
  'Rails::GraphQL::Source::ActiveRecordSource',
125
136
  ]
126
137
 
127
- # A list of all available subscription providers which bases on
128
- # Rails::GraphQL::SubscriptionProvider::Base
138
+ # A list of all available subscription providers.
129
139
  config.subscription_providers = [
130
140
  'Rails::GraphQL::Subscription::Provider::ActionCable',
131
141
  ]
132
142
 
133
- # The default subscription provider for all the schemas
143
+ # The default subscription provider for all schemas. This can also be set
144
+ # per Schema.
134
145
  config.default_subscription_provider = config.subscription_providers.first
135
146
 
136
- # The default value for fields about their ability of being broadcasted
147
+ # The default value for fields about their ability to be broadcasted. This
148
+ # can also be set per Schema.
137
149
  config.default_subscription_broadcastable = nil
138
150
 
139
151
  # A list of known dependencies that can be requested and included in any
140
- # schema. This is the best place for other gems to add their own
141
- # dependencies and allow users to pick them.
152
+ # Schema. This is the best place for other gems to add their own resources
153
+ # and allow users to enable them.
142
154
  config.known_dependencies = {
143
155
  scalar: {
144
156
  any: "#{__dir__}/type/scalar/any_scalar",
@@ -150,20 +162,35 @@ module Rails
150
162
  time: "#{__dir__}/type/scalar/time_scalar",
151
163
  json: "#{__dir__}/type/scalar/json_scalar",
152
164
  },
165
+ enum: {},
166
+ input: {},
167
+ interface: {},
168
+ object: {},
169
+ union: {},
153
170
  directive: {
154
171
  # cached: "#{__dir__}/directive/cached_directive",
155
172
  },
156
173
  }
157
174
 
158
- # The method that should be used to parse literal input values when they
159
- # are provided as hash. +JSON.parse+ only supports keys wrapped in quotes,
160
- # to support keys without quotes, you can use +Psych.method(:safe_load)+,
161
- # which behaves closer to YAML, but the received value is ensure to be
162
- # wrapped in "{}". If that produces unexpected results, you can assign a
163
- # proc and then parse the value in any other way, like
164
- # +->(value) { anything }+
175
+ # The method that should be used to parse literal input values when they are
176
+ # provided as Hash. `JSON.parse` only supports keys wrapped in quotes. You
177
+ # can use `Psych.method(:safe_load)` to support keys without quotes, which
178
+ # behaves closer to YAML. The received value is ensured to be wrapped in
179
+ # "{}". If that produces unexpected results, you can assign a proc and then
180
+ # parse the value in any other way.
165
181
  config.literal_input_parser = JSON.method(:parse)
166
182
 
183
+ # A mapping for the internal parameters and where they should be taken
184
+ # from. You can point to nested values using dot notation.
185
+ # TODO: Needs implementation
186
+ config.params_mapping = {
187
+ query: 'query',
188
+ variables: 'variables',
189
+ operation_name: 'operation_name',
190
+ query_cache_key: 'extensions.persistedQuery.sha256Hash',
191
+ query_cache_version: 'extensions.persistedQuery.version',
192
+ }
193
+
167
194
  # TODO: To be implemented
168
195
  # allow_query_serialization
169
196
  end
@@ -16,7 +16,6 @@ module Rails
16
16
  When false, the underlying element will be automatically marked as null.
17
17
  DESC
18
18
 
19
- # TODO: On attach does not covers default value per operation variable scenario
20
19
  on(:attach) do |source|
21
20
  source.skip! unless args[:if]
22
21
  end
@@ -16,7 +16,6 @@ module Rails
16
16
  When true, the underlying element will be automatically marked as null.
17
17
  DESC
18
18
 
19
- # TODO: On attach does not covers default value per operation variable scenario
20
19
  on(:attach) do |source|
21
20
  source.skip! if args[:if]
22
21
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module GraphQL
5
+ # = GraphQL Spec Specified By Directive
6
+ #
7
+ # Provides a scalar specification URL for specifying the behavior of
8
+ # custom scalar types.
9
+ class Directive::SpecifiedByDirective < Directive
10
+ self.spec_object = true
11
+
12
+ placed_on :scalar
13
+
14
+ desc <<~DESC
15
+ A built-in directive used within the type system definition language to provide
16
+ a scalar specification URL for specifying the behavior of custom scalar types.
17
+ DESC
18
+
19
+ argument :url, :string, null: false, desc: <<~DESC
20
+ Point to a human-readable specification of the data format.
21
+ DESC
22
+ end
23
+ end
24
+ end
@@ -44,6 +44,13 @@ module Rails
44
44
  :directive
45
45
  end
46
46
 
47
+ # Ensure to return the directive class
48
+ def base_type
49
+ GraphQL::Directive
50
+ end
51
+
52
+ alias gid_base_class base_type
53
+
47
54
  # Return the name of the object as a GraphQL name, ensure to use the
48
55
  # first letter as lower case when being auto generated
49
56
  def gql_name
@@ -71,11 +78,6 @@ module Rails
71
78
  @locations = list.to_set
72
79
  end
73
80
 
74
- # Ensure to return the directive class
75
- def gid_base_class
76
- GraphQL::Directive
77
- end
78
-
79
81
  # A helper method that allows directives to be initialized while
80
82
  # correctly parsing the arguments
81
83
  def build(**xargs)
@@ -97,10 +99,11 @@ module Rails
97
99
  def inspect
98
100
  return super if eql?(GraphQL::Directive)
99
101
 
102
+ repeatable = ' [repeatable]' if repeatable?
100
103
  args = all_arguments&.each_value&.map(&:inspect)
101
104
  args = args.force if args.respond_to?(:force)
102
105
  args = args.presence && "(#{args.join(', ')})"
103
- +"#<GraphQL::Directive @#{gql_name}#{args}>"
106
+ +"#<GraphQL::Directive @#{gql_name}#{repeatable}#{args}>"
104
107
  end
105
108
 
106
109
  private
@@ -126,7 +129,7 @@ module Rails
126
129
  GraphQL.enumerate(setting).map do |item|
127
130
  next item unless item.is_a?(String) || item.is_a?(Symbol)
128
131
  GraphQL.type_map.fetch(item, namespaces: namespaces) ||
129
- ::GraphQL.const_get(item)
132
+ ::GraphQL.const_get(item, false)
130
133
  end
131
134
  end
132
135
 
@@ -141,30 +144,23 @@ module Rails
141
144
  Invalid locations for @#{gql_name}: #{invalid.force.to_sentence}.
142
145
  MSG
143
146
  end
144
-
145
- # Allows checking value existence
146
- def respond_to_missing?(method_name, *)
147
- (const_defined?(method_name) rescue nil) || autoload?(method_name) || super
148
- end
149
-
150
- # Allow fast creation of values
151
- def method_missing(method_name, *args, **xargs, &block)
152
- const_get(method_name)&.new(*args, **xargs, &block) || super
153
- rescue ::NameError
154
- super
155
- end
156
147
  end
157
148
 
149
+ # Marks if the directive may be used repeatedly at a single location
150
+ class_attribute :repeatable, instance_accessor: false, default: false
151
+
158
152
  self.abstract = true
159
153
 
160
154
  autoload :DeprecatedDirective
161
155
  autoload :IncludeDirective
162
156
  autoload :SkipDirective
157
+ autoload :SpecifiedByDirective
163
158
 
164
159
  autoload :CachedDirective
165
160
 
166
- delegate :locations, :gql_name, :gid_base_class, to: :class
161
+ delegate :locations, :gql_name, :gid_base_class, :repeatable?, to: :class
167
162
 
163
+ # TODO: This filters are a bit confusing now, but `for` is working for @deprecated
168
164
  event_filter(:for) do |options, event|
169
165
  sanitize_objects(options).any?(&event.source.method(:of_type?))
170
166
  end
@@ -177,7 +173,7 @@ module Rails
177
173
  event.key?(:phase) && GraphQL.enumerate(options).include?(event[:phase])
178
174
  end
179
175
 
180
- attr_reader :args
176
+ attr_reader :args, :event
181
177
 
182
178
  def initialize(args = nil, **xargs)
183
179
  @args = args || OpenStruct.new(xargs.transform_keys { |key| key.to_s.underscore })
@@ -211,10 +207,11 @@ module Rails
211
207
 
212
208
  # When fetching all the events, embed the actual instance as the context
213
209
  # of the callback
210
+ # TODO: Maybe add a soft cached, based on the total number of events
214
211
  def all_events
215
212
  return unless self.class.events?
216
213
 
217
- @all_events ||= self.class.all_events.transform_values do |events|
214
+ self.class.all_events.transform_values do |events|
218
215
  events.map { |item| Callback.set_context(item, self) }
219
216
  end
220
217
  end
@@ -237,18 +234,27 @@ module Rails
237
234
  MSG
238
235
  end
239
236
 
237
+ # This allows combining directives
238
+ def +(other)
239
+ [self, other].flatten
240
+ end
241
+
242
+ alias_method :&, :+
243
+
240
244
  def inspect
241
- args = all_arguments&.map do |name, arg|
245
+ args = all_arguments&.filter_map do |name, arg|
242
246
  +"#{arg.gql_name}: #{@args[name].inspect}" unless @args[name].nil?
243
- end&.compact
247
+ end
244
248
 
245
249
  args = args.presence && +"(#{args.join(', ')})"
250
+ repeatable = ' [repeatable]' if repeatable?
246
251
  unbound = ' # unbound' unless defined?(@owner)
247
- +"@#{gql_name}#{args}#{unbound}"
252
+ +"@#{gql_name}#{repeatable}#{args}#{unbound}"
248
253
  end
249
254
 
250
255
  %i[to_global_id to_gid to_gid_param].each do |method_name|
251
256
  define_method(method_name) do
257
+ # TODO: The option is kind of broken, because they should always be a Hash
252
258
  self.class.public_send(method_name, args_as_json&.compact || '')
253
259
  end
254
260
  end
@@ -7,7 +7,7 @@ module Rails
7
7
  # This class is responsible for triggering events. It also contains the
8
8
  # +data+ that can be used on the event handlers.
9
9
  class Event
10
- attr_reader :source, :data, :name, :object, :last_result
10
+ attr_reader :source, :data, :event_name, :object, :last_result
11
11
 
12
12
  alias event itself
13
13
 
@@ -34,7 +34,7 @@ module Rails
34
34
  @collect = data.delete(:collect?)
35
35
  @reverse = data.delete(:reverse?)
36
36
 
37
- @name = name
37
+ @event_name = name
38
38
  @data = data
39
39
  @source = source
40
40
  @layers = []
@@ -42,9 +42,10 @@ module Rails
42
42
 
43
43
  # Check if the provided +other+ is equal to the source of the event. If
44
44
  # other is a directive, then check if the source is using that directive
45
+ # TODO: Other cannot be an instance
45
46
  def same_source?(other)
46
47
  if other.is_a?(Directive) || (other.is_a?(Module) && other < Directive)
47
- source.using?(other)
48
+ event_name == :attach || source.using?(other)
48
49
  else
49
50
  source == other
50
51
  end
@@ -81,6 +82,8 @@ module Rails
81
82
  end
82
83
  end
83
84
 
85
+ alias on_instance set_on
86
+
84
87
  # From the list of all given objects, run the +trigger_object+
85
88
  def trigger_all(*objects)
86
89
  catchable(:stack) do
@@ -102,7 +105,7 @@ module Rails
102
105
  old_items, old_object, old_result, @object = @items, @object, @last_result, object
103
106
 
104
107
  catchable(:object) do
105
- events ||= object.all_events.try(:[], name)
108
+ events ||= object.all_events.try(:[], event_name)
106
109
  stop if events.blank?
107
110
 
108
111
  @items = @reverse ? events.reverse_each : events.each
@@ -136,8 +139,6 @@ module Rails
136
139
  # Do not do anything when missing next/super
137
140
  end
138
141
 
139
- alias call_super call_next
140
-
141
142
  protected
142
143
 
143
144
  alias args_source itself
@@ -16,11 +16,6 @@ module Rails
16
16
  end
17
17
  end
18
18
 
19
- # Just add the callbacks setup to the field
20
- def self.included(other)
21
- other.event_types(:authorize, append: true)
22
- end
23
-
24
19
  # Add either settings for authorization or a block to be executed. It
25
20
  # returns +self+ for chain purposes
26
21
  def authorize(*args, **xargs, &block)
@@ -28,11 +28,6 @@ module Rails
28
28
  @default = deserialize(@default) if @default.is_a?(::GQLParser::Token)
29
29
  end
30
30
 
31
- # Prevent input fields from being further configured using a block
32
- def configure
33
- raise ArgumentError, +'Input fields can\'t be further configured using blocks'
34
- end
35
-
36
31
  # Allow change the default value for the input
37
32
  def apply_changes(**xargs, &block)
38
33
  @default = xargs[:default] if xargs.key?(:default)
@@ -5,7 +5,7 @@ module Rails
5
5
  # = GraphQL Mutation Field
6
6
  #
7
7
  # This is an extension of a normal output field, which just add extra
8
- # validation and ensurance that the +perform+ step can be executed
8
+ # validation and insurance that the +perform+ step can be executed
9
9
  #
10
10
  # ==== Options
11
11
  #
@@ -58,17 +58,16 @@ module Rails
58
58
  # Get the performer that can be already defined or used through the
59
59
  # +method_name+ if that is callable
60
60
  def performer
61
- @performer ||= callable?(perform_method_name) \
62
- ? Callback.new(self, :perform, perform_method_name) \
63
- : false
61
+ return @performer if defined?(@performer)
62
+
63
+ @performer = callable?(perform_method_name)
64
+ @performer = Callback.new(self, :perform, perform_method_name) if @performer
64
65
  end
65
66
 
66
67
  # Ensures that the performer is defined
67
68
  def validate!(*)
68
69
  super if defined? super
69
70
 
70
- binding.pry unless performer.present?
71
-
72
71
  raise ValidationError, (+<<~MSG).squish unless performer.present?
73
72
  The "#{gql_name}" mutation field must have a perform action through a given
74
73
  block or a method named #{method_name} on #{owner.class.name}.
@@ -29,10 +29,12 @@ module Rails
29
29
  include Field::TypedField
30
30
 
31
31
  module Proxied # :nodoc: all
32
+ Field.proxyable_methods %w[broadcastable?], klass: self
33
+
32
34
  def all_arguments
33
35
  inherited = field.all_arguments
34
36
  return inherited unless defined?(@arguments)
35
- inherited.blank? ? super : inherited + super
37
+ inherited.blank? ? super : inherited.merge(super)
36
38
  end
37
39
 
38
40
  def has_argument?(name)
@@ -43,12 +45,17 @@ module Rails
43
45
  super || field.arguments?
44
46
  end
45
47
 
48
+ # TODO: Break events into directive/type/local
49
+ # because type events should only be added
50
+ # from the proxy
46
51
  def all_events
47
52
  if (inherited = super).nil?
48
53
  field.all_events
49
54
  elsif (proxied = field.all_events).nil?
50
55
  inherited
51
56
  else
57
+ # The order is reversed because events from
58
+ # the proxy must come first
52
59
  Helpers.merge_hash_array(proxied, inherited)
53
60
  end
54
61
  end
@@ -163,6 +170,10 @@ module Rails
163
170
  defined?(@broadcastable) && @broadcastable
164
171
  end
165
172
 
173
+ def entry_point?
174
+ owner.is_a?(Helpers::WithSchemaFields)
175
+ end
176
+
166
177
  protected
167
178
 
168
179
  # Check if the given +value+ is a valid array as output
@@ -177,7 +188,7 @@ module Rails
177
188
 
178
189
  # Properly display the owner section when the field is owned by a Schema
179
190
  def inspect_owner
180
- owner.is_a?(Helpers::WithSchemaFields) ? +"#{owner.name}[:#{schema_type}]" : super
191
+ entry_point? ? +"#{owner.name}[:#{schema_type}]" : super
181
192
  end
182
193
 
183
194
  def proxied
@@ -25,10 +25,9 @@ module Rails
25
25
  # * <tt>:alias</tt> - Same as the +:as+ key (defaults to nil).
26
26
  module Field::ProxiedField
27
27
  delegate_missing_to :field
28
- delegate :leaf_type?, :array?, :internal?, :valid_input?, :valid_output?,
29
- :to_json, :as_json, :deserialize, :valid?, :proxied_owner, to: :field
28
+ delegate :leaf_type?, :array?, :internal?, :proxied_owner, to: :field
30
29
 
31
- Field.proxyable_methods %w[name gql_name method_name resolver description
30
+ Field.proxyable_methods %w[name gql_name method_name description
32
31
  null? nullable? enabled?], klass: self
33
32
 
34
33
  def initialize(field, owner:, **xargs, &block)
@@ -49,7 +48,7 @@ module Rails
49
48
  super || field == other
50
49
  end
51
50
 
52
- # Allow chaging most of the general kind-independent initialize settings
51
+ # Allow changing most of the general kind-independent initialize settings
53
52
  def apply_changes(**xargs, &block)
54
53
  if (deprecated = xargs[:deprecated])
55
54
  xargs[:directives] = ::Array.wrap(xargs[:directives])
@@ -62,7 +61,8 @@ module Rails
62
61
  @directives = GraphQL.directives_to_set(xargs[:directives], source: self) \
63
62
  if xargs.key?(:directives)
64
63
 
65
- @desc = xargs[:desc]&.strip_heredoc&.chomp if xargs.key?(:desc)
64
+ self.description = xargs[:desc] if xargs.key?(:desc)
65
+ self.description = xargs[:description] if xargs.key?(:description)
66
66
  @enabled = xargs.fetch(:enabled, !xargs.fetch(:disabled, false)) \
67
67
  if xargs.key?(:enabled) || xargs.key?(:disabled)
68
68
 
@@ -72,7 +72,7 @@ module Rails
72
72
 
73
73
  # Override this to include proxied owners
74
74
  def all_owners
75
- super + proxied_owner.all_owners
75
+ super + @field.all_owners
76
76
  end
77
77
 
78
78
  # Return the proxied field
@@ -18,7 +18,7 @@ module Rails
18
18
 
19
19
  # Just add the callbacks setup to the field
20
20
  def self.included(other)
21
- other.event_types(:prepare, :finalize, append: true, expose: true)
21
+ other.send(:expose_events!, :organized, :finalize, :prepared, :prepare)
22
22
  other.alias_method(:before_resolve, :prepare)
23
23
  other.alias_method(:after_resolve, :finalize)
24
24
  end