rails-graphql 1.0.0.beta → 1.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -8,7 +8,7 @@ module Rails
8
8
  # source object, creating:
9
9
  # 1. 1 Object
10
10
  # 2. 1 Input
11
- # 3. 2 Query fields (ingular and plural)
11
+ # 3. 2 Query fields (singular and plural)
12
12
  # 4. 3 Mutation fields (create, update, destroy)
13
13
  class Source::ActiveRecordSource < Source::Base
14
14
  include Source::ScopedArguments
@@ -24,17 +24,25 @@ module Rails
24
24
  # associations associated to the object
25
25
  class_attribute :with_associations, instance_accessor: false, default: true
26
26
 
27
+ # Set what type of errors should be exported to the extensions of the
28
+ # request when trying to save records. False will disable it
29
+ class_attribute :errors_to_extensions, instance_accessor: false, default: false
30
+
31
+ # Marks if the source should be threated as an interface, meaning that
32
+ # no object will be created, instead an interface will
33
+ class_attribute :act_as_interface, instance_accessor: false
34
+
27
35
  # The name of the class (or the class itself) to be used as superclass for
28
36
  # the generate GraphQL interface type of this source
29
37
  class_attribute :interface_class, instance_accessor: false
30
38
 
31
39
  %i[object interface input].each do |type|
32
- settings = { abstract: true, with_owner: true }
40
+ settings = { abstract: true, owner: true }
33
41
  send("#{type}_class=", create_type(type, **settings))
34
42
  end
35
43
 
36
44
  self.abstract = true
37
- self.hook_names = hook_names.to_a.insert(1, :enums).to_set
45
+ self.hook_names = hook_names.to_a.insert(1, :enums, :interface).to_set.freeze
38
46
 
39
47
  delegate :primary_key, :singular, :plural, :model, :id_columns, to: :class
40
48
 
@@ -48,44 +56,51 @@ module Rails
48
56
  build_reflection_fields(self)
49
57
  end
50
58
 
59
+ step(:interface) do
60
+ build_attribute_fields(self)
61
+ build_reflection_fields(self)
62
+ end
63
+
51
64
  step(:input) do
52
65
  extra = GraphQL.enumerate(primary_key).entries.product([{ null: true }]).to_h
53
66
  build_attribute_fields(self, **extra)
54
67
  build_reflection_inputs(self)
55
68
 
56
- safe_field(model.inheritance_column, :string, null: false) if object.interface?
69
+ safe_field(model.inheritance_column, :string, null: false) if interface?
57
70
  safe_field(:_delete, :boolean, default: false)
58
71
 
59
72
  reference = model.new
60
73
  model.columns_hash.each_value do |column|
61
74
  change_field(column.name, default: reference[column.name]) \
62
- if column.default.present? && field?(column.name)
75
+ if column.default.present? && has_field?(column.name)
63
76
  end
64
77
  end
65
78
 
66
79
  step(:query) do
67
- build_object
80
+ interface? ? build_interface : build_object
81
+ type = interface? ? interface : object
68
82
 
69
- safe_field(plural, object, full: true) do
83
+ safe_field(plural, type, full: true) do
70
84
  before_resolve(:load_records)
71
85
  end
72
86
 
73
- safe_field(singular, object, null: false) do
87
+ safe_field(singular, type, null: false) do
74
88
  build_primary_key_arguments(self)
75
89
  before_resolve(:load_record)
76
90
  end
77
91
  end
78
92
 
79
93
  step(:mutation) do
80
- build_object
94
+ interface? ? build_interface : build_object
95
+ type = interface? ? interface : object
81
96
  build_input
82
97
 
83
- safe_field("create_#{singular}", object, null: false) do
98
+ safe_field("create_#{singular}", type, null: false) do
84
99
  argument(singular, input, null: false)
85
100
  perform(:create_record)
86
101
  end
87
102
 
88
- safe_field("update_#{singular}", object, null: false) do
103
+ safe_field("update_#{singular}", type, null: false) do
89
104
  build_primary_key_arguments(self)
90
105
  argument(singular, input, null: false)
91
106
  before_resolve(:load_record)
@@ -110,7 +125,8 @@ module Rails
110
125
 
111
126
  # Set the assignment to a model with a similar name as the source
112
127
  def assigned_to
113
- @assigned_to ||= name.delete_prefix('GraphQL::')[0..-7]
128
+ return @assigned_to if defined?(@assigned_to)
129
+ @assigned_to = base_name.gsub('_', '::')
114
130
  end
115
131
 
116
132
  # Stores columns associated with enums so that the fields can have a
@@ -124,6 +140,23 @@ module Rails
124
140
  super if model&.table_exists?
125
141
  end
126
142
 
143
+ # Allows setting up an interface instead of an object. Mostly because
144
+ # some models are better dealt as interfaces than actual objects
145
+ def interface
146
+ @interface ||= create_type(superclass: interface_class, gql_name: object_name)
147
+ end
148
+
149
+ # Checks if the source is building an interface instead of an object
150
+ def interface?
151
+ defined?(@interface) || act_as_interface == true ||
152
+ (act_as_interface != false && sti_interface?)
153
+ end
154
+
155
+ # Provides access to the default plural query field, for associations interconnection
156
+ def collection_field
157
+ find_field(:query, plural)
158
+ end
159
+
127
160
  # Hook into the unregister to clean enums
128
161
  def unregister!
129
162
  super
@@ -151,23 +184,56 @@ module Rails
151
184
 
152
185
  private
153
186
 
187
+ # Hook into the build process to selective avoid :interface or :object
188
+ def build!(type)
189
+ return if type == :object && interface?
190
+ return if type == :interface && !interface?
191
+ super
192
+ end
193
+
154
194
  def presence_validator
155
195
  ::ActiveRecord::Validations::PresenceValidator
156
196
  end
157
197
  end
158
198
 
159
- # Prepare to load multiple records from the underlying table
160
- def load_records(scope = model.default_scoped)
199
+ # Prepare to load multiple records from the underlying model
200
+ def load_records(scope = nil)
201
+ scope ||= event.last_result || model.default_scoped
161
202
  inject_scopes(scope, :relation)
162
203
  end
163
204
 
164
- # Prepare to load a single record from the underlying table
165
- def load_record(scope = model.default_scoped, find_by: nil)
205
+ # Prepare to load a single record from the underlying model
206
+ def load_record(scope = nil, find_by: nil)
207
+ scope ||= event.last_result || model.default_scoped
166
208
  find_by ||= { primary_key => event.argument(primary_key) }
167
209
  inject_scopes(scope, :relation).find_by(find_by)
168
210
  end
169
211
 
170
- # Get the chain result and preload the records with thre resulting scope
212
+ # The perform step for the +create+ based mutation
213
+ def create_record
214
+ input_argument.resource.tap(&:save!)
215
+ rescue ::ActiveRecord::RecordInvalid => error
216
+ errors_to_extensions(error.record.errors)
217
+ raise
218
+ end
219
+
220
+ # The perform step for the +update+ based mutation
221
+ def update_record
222
+ current_value.tap { |record| record.update!(**input_argument.params) }
223
+ rescue ::ActiveRecord::RecordInvalid => error
224
+ errors_to_extensions(error.record.errors)
225
+ raise
226
+ end
227
+
228
+ # The perform step for the +delete+ based mutation
229
+ def destroy_record
230
+ !!current_value.destroy!
231
+ rescue ::ActiveRecord::RecordInvalid => error
232
+ errors_to_extensions(error.record.errors)
233
+ raise
234
+ end
235
+
236
+ # Get the chain result and preload the records with the resulting scope
171
237
  def preload_association(association, scope = nil)
172
238
  event.stop(preload(association, scope || event.last_result), layer: :object)
173
239
  end
@@ -177,10 +243,11 @@ module Rails
177
243
  scope = model._reflect_on_association(association).klass.default_scoped
178
244
 
179
245
  # Apply proxied injected scopes
180
- proxied = event.field.try(:proxied_owner)
181
- scope = event.on_instance(proxied) do |instance|
182
- instance.inject_scopes(scope, :relation)
183
- end if proxied.present? && proxied <= Source::ActiveRecordSource
246
+ # TODO: Arguments comes with their proxy, so we might not need this
247
+ # proxied = event.field.try(:proxied_owner)
248
+ # scope = event.on_instance(proxied) do |instance|
249
+ # instance.inject_scopes(scope, :relation)
250
+ # end if proxied.present? && proxied <= Source::ActiveRecordSource
184
251
 
185
252
  # Apply self defined injected scopes
186
253
  inject_scopes(scope, :relation)
@@ -189,26 +256,21 @@ module Rails
189
256
  # Once the records are pre-loaded due to +preload_association+, use the
190
257
  # parent value and the preloader result to get the records
191
258
  def parent_owned_records(collection_result = false)
192
- data = event.data[:prepared]
259
+ data = event.data[:prepared_data]
193
260
  return collection_result ? [] : nil unless data
194
261
 
195
262
  result = data.records_by_owner[current_value] || []
196
263
  collection_result ? result : result.first
197
264
  end
198
265
 
199
- # The perform step for the +create+ based mutation
200
- def create_record
201
- input_argument.resource.tap(&:save!)
202
- end
203
-
204
- # The perform step for the +update+ based mutation
205
- def update_record
206
- current_value.tap { |record| record.update!(**input_argument.params) }
207
- end
266
+ # Expose the errors to the extensions of the response
267
+ def errors_to_extensions(errors, path = nil, format = nil)
268
+ format ||= self.class.errors_to_extensions
269
+ return unless format
208
270
 
209
- # The perform step for the +delete+ based mutation
210
- def destroy_record
211
- !!current_value.destroy!
271
+ path ||= [operation.name, field.gql_name].compact
272
+ hash = GraphQL.enumerate(path).reduce(request.extensions) { |h, k| h[k] ||= {} }
273
+ hash.replace(format == :messages ? errors.as_json : errors.details)
212
274
  end
213
275
 
214
276
  protected
@@ -32,8 +32,8 @@ module Rails
32
32
 
33
33
  # Unregister all objects that this source was providing
34
34
  def unregister!
35
- GraphQL.type_map.unregister(*created_types) if defined?(@created_types)
36
35
  @object = @input = nil
36
+ super
37
37
  end
38
38
 
39
39
  # Return the GraphQL object type associated with the source. It will
@@ -59,52 +59,25 @@ module Rails
59
59
  enumerator = values.each_pair if values.respond_to?(:each_pair)
60
60
  enumerator ||= values.each.with_index
61
61
 
62
- xargs = xargs.reverse_merge(once: true)
63
- create_type(:enum, as: enum_name.classify, **xargs) do
64
- indexed! if enumerator.first.last.is_a?(Numeric)
65
- enumerator.sort_by(&:last).map(&:first).each(&method(:add))
66
- instance_exec(&block) if block.present?
67
- end
62
+ xargs[:values] = enumerator.sort_by(&:last).map(&:first)
63
+ xargs[:indexed] = enumerator.first.last.is_a?(Numeric)
64
+
65
+ create_type(:enum, enum_name.to_s.classify, **xargs, &block)
68
66
  end
69
67
 
70
68
  # Helper method to create a class based on the given +type+ and
71
69
  # allows several other settings to be executed on it
72
- def create_type(type = nil, **xargs, &block)
73
- name = "#{gql_module.name}::#{xargs.delete(:as) || base_name}"
74
- superclass = xargs.delete(:superclass)
75
- with_owner = xargs.delete(:with_owner)
76
-
77
- if superclass.nil?
78
- superclass = type.to_s.classify
79
- elsif superclass.is_a?(String)
80
- superclass = superclass.constantize
81
- end
82
-
83
- source = self
84
- gql_name = xargs.delete(:gql_name)
85
- Schema.send(:create_type, name, superclass, **xargs) do
86
- include Helpers::WithOwner if with_owner
87
- set_namespaces(*source.namespaces)
88
-
89
- instance_variable_set(:@gql_name, gql_name) unless gql_name.nil?
90
-
91
- self.owner = source if respond_to?(:owner=)
92
- self.assigned_to = source.safe_assigned_class \
93
- if source.assigned? && is_a?(Helpers::WithAssignment)
94
-
95
- instance_exec(&block) if block.present?
96
- end.tap { |klass| created_types << klass }
97
- end
98
-
99
- private
100
-
101
- # Keep track of all the types created byt this source
102
- def created_types
103
- @created_types ||= []
70
+ def create_type(type = nil, name = nil, **xargs, &block)
71
+ xargs[:owner] ||= self
72
+ xargs[:namespaces] = namespaces
73
+ xargs[:assigned_to] = safe_assigned_class
74
+ superclass = xargs.delete(:superclass) || type
75
+
76
+ name ||= base_name.tr('_', '')
77
+ GraphQL::Type.create!(self, name, superclass, **xargs, &block)
104
78
  end
105
79
 
106
80
  end
107
-
108
81
  end
109
82
  end
110
83
  end
@@ -21,23 +21,23 @@ module Rails
21
21
  build_all! unless abstract?
22
22
  end
23
23
 
24
- # Allows building anything that is in hooks
24
+ # Make sure to properly indicate about build methods
25
25
  def respond_to_missing?(method_name, *)
26
26
  return super unless method_name.to_s.start_with?('build_') &&
27
27
  hook_names.include?(method_name.to_s[6..-1].to_sym)
28
28
  end
29
29
 
30
- # Allow fast creation of values
30
+ # Allows all sorts of building methods to be called
31
31
  def method_missing(method_name, *args, **xargs, &block)
32
32
  return super unless method_name.to_s.start_with?('build_')
33
33
 
34
34
  type = method_name.to_s[6..-1]
35
35
  type = type.singularize unless hook_names.include?(type.to_sym)
36
36
  type = type.to_sym
37
+ return if built?(type)
37
38
 
38
39
  import_skips_for(type, xargs)
39
-
40
- build!(type, *args, **xargs, &block) unless built?(type)
40
+ build!(type, *args, **xargs, &block)
41
41
  end
42
42
 
43
43
  protected
@@ -47,11 +47,9 @@ module Rails
47
47
  @built ||= Set.new
48
48
  end
49
49
 
50
- # Make sure to mark the hook name as built
51
- def run_hooks(hook_name, *)
52
- super
53
- ensure
54
- built << hook_name
50
+ # Mark as built before running the hooks
51
+ def run_hooks(type, *)
52
+ built.add(type)
55
53
  end
56
54
 
57
55
  private
@@ -101,28 +99,22 @@ module Rails
101
99
  def build!(type)
102
100
  ensure_build!(type)
103
101
 
102
+ schema_type = Helpers::WithSchemaFields::TYPE_FIELD_CLASS.key?(type)
104
103
  catch(:skip) { run_hooks(:start) } unless built?(:start)
105
- catch(:skip) { run_hooks(type, hook_scope_for(type)) }
104
+ catch(:skip) { run_hooks(type, hook_scope_for(type, schema_type)) }
106
105
 
107
- built << type.to_sym
106
+ attach_fields!(type, fields_for(type)) if schema_type && fields_for?(type)
108
107
  end
109
108
 
110
109
  # Get the correct +self_object+ for the hook instance
111
- def hook_scope_for(type)
110
+ def hook_scope_for(type, schema_type)
112
111
  type = type.to_sym
113
- object =
114
- if Helpers::WithSchemaFields::TYPE_FIELD_CLASS.key?(type)
115
- Helpers::WithSchemaFields::ScopedConfig.new(self, type)
116
- else
117
- Helpers::AttributeDelegator.new(self, type)
118
- end
119
-
120
- Source::ScopedConfig.new(self, object, type)
112
+ klass = schema_type ? 'WithSchemaFields::ScopedConfig' : 'AttributeDelegator'
113
+ klass = Helpers.const_get(klass, false).new(self, type)
114
+ Source::ScopedConfig.new(self, klass, type)
121
115
  end
122
116
 
123
117
  end
124
118
  end
125
119
  end
126
120
  end
127
-
128
-
@@ -15,8 +15,8 @@ module Rails
15
15
  def initialize(*args, block:, on: nil, **xargs)
16
16
  super(*args, **xargs)
17
17
 
18
- @block = block
19
- @fields = GraphQL.enumerate(on)
18
+ @block = (block == true) ? name : block
19
+ @fields = on
20
20
  end
21
21
 
22
22
  # Apply the argument block to the given object, using or not the value
@@ -36,7 +36,7 @@ module Rails
36
36
  def attach_to?(field)
37
37
  return true if @fields.nil?
38
38
 
39
- @fields.any? do |item|
39
+ GraphQL.enumerate(@fields).any? do |item|
40
40
  (item.is_a?(Symbol) && field.name.eql?(item)) || field.gql_name.eql?(item)
41
41
  end
42
42
  end
@@ -48,11 +48,17 @@ module Rails
48
48
  defined?(@scoped_arguments) ? @scoped_arguments : {}
49
49
  end
50
50
 
51
+ # Hook into the attach fields process to attach the scoped arguments
52
+ def attach_fields!(type, fields)
53
+ attach_scoped_arguments_to(fields.values)
54
+ super
55
+ end
56
+
51
57
  protected
52
58
 
53
59
  # Add a new scoped param to the list
54
60
  def scoped_argument(param, type = :string, proc_method = nil, **settings, &block)
55
- block = proc_method if proc_method.present? && block.nil?
61
+ block ||= proc_method if proc_method.present?
56
62
  argument = Argument.new(param, type, **settings, owner: self, block: block)
57
63
  (@scoped_arguments ||= {})[argument.name] = argument
58
64
  end
@@ -17,13 +17,6 @@ module Rails
17
17
 
18
18
  include Helpers::Instantiable
19
19
 
20
- ATTACH_FIELDS_STEP = -> do
21
- if fields?
22
- attach_fields!(type, fields)
23
- attach_scoped_arguments_to(fields.values)
24
- end
25
- end
26
-
27
20
  autoload :Base
28
21
  autoload :Builder
29
22
 
@@ -39,7 +32,6 @@ module Rails
39
32
  self_object.safe_field(name, *args, **xargs, &block)
40
33
  end
41
34
 
42
- # skip_field?(item.name, on: holder.kind)
43
35
  def respond_to_missing?(method_name, include_private = false)
44
36
  self_object.respond_to?(method_name, include_private) ||
45
37
  receiver.respond_to?(method_name, include_private)
@@ -61,7 +53,7 @@ module Rails
61
53
  # set the order of the execution of the hooks while validating the hooks
62
54
  # callbacks using the +on+ method
63
55
  class_attribute :hook_names, instance_accessor: false,
64
- default: %i[start object input query mutation subscription].to_set
56
+ default: %i[start object input query mutation subscription].to_set.freeze
65
57
 
66
58
  # The list of hooks defined in order to describe a source
67
59
  inherited_collection :hooks, instance_reader: false, type: :hash_array
@@ -93,7 +85,13 @@ module Rails
93
85
 
94
86
  # Get the main name of the source
95
87
  def base_name
96
- name.demodulize[0..-7]
88
+ @base_name ||= begin
89
+ nested = "::#{Type::Creator::NESTED_MODULE}::"
90
+
91
+ value = name.delete_prefix('GraphQL::')
92
+ value = name.split(nested).last if name.include?(nested)
93
+ value.chomp('Source')
94
+ end
97
95
  end
98
96
 
99
97
  # :singleton-method:
@@ -113,6 +111,22 @@ module Rails
113
111
  base_sources.reverse_each.find { |source| object <= source.assigned_class }
114
112
  end
115
113
 
114
+ # Add a new description hook. You can use +throw :skip+ and skip
115
+ # parent hooks. If the class is already built, then execute the hook.
116
+ # Use the +unshift: true+ to add the hook at the beginning of the
117
+ # list, which will then be the last to run
118
+ def step(hook_name, unshift: false, &block)
119
+ raise ArgumentError, (+<<~MSG).squish unless hook_names.include?(hook_name.to_sym)
120
+ The #{hook_name.inspect} is not a valid hook method.
121
+ MSG
122
+
123
+ if built?(hook_name)
124
+ hook_scope_for(hook_name).instance_exec(&block)
125
+ else
126
+ hooks[hook_name.to_sym].public_send(unshift ? :unshift : :push, block)
127
+ end
128
+ end
129
+
116
130
  # Attach all defined schema fields into the schemas using the namespaces
117
131
  # configured for the source
118
132
  def attach_fields!(type = :all, from = self)
@@ -149,22 +163,6 @@ module Rails
149
163
  segmented_skip_fields[source] += fields.flatten.compact.map(&:to_sym).to_set
150
164
  end
151
165
 
152
- # Add a new description hook. You can use +throw :skip+ and skip
153
- # parent hooks. If the class is already built, then execute the hook.
154
- # Use the +unshift: true+ to add the hook at the beginning of the
155
- # list, which will then be the last to run
156
- def step(hook_name, unshift: false, &block)
157
- raise ArgumentError, (+<<~MSG).squish unless hook_names.include?(hook_name.to_sym)
158
- The #{hook_name.inspect} is not a valid hook method.
159
- MSG
160
-
161
- if built?(hook_name)
162
- hook_scope_for(hook_name).instance_exec(&block)
163
- else
164
- hooks[hook_name.to_sym].public_send(unshift ? :unshift : :push, block)
165
- end
166
- end
167
-
168
166
  # Creates a hook that throws a done action, preventing any parent hooks
169
167
  def skip(*names)
170
168
  names.each do |hook_name|
@@ -183,22 +181,21 @@ module Rails
183
181
  # It's an alternative to +self.hook_names -= %i[*names]+ which
184
182
  # disables a specific hook
185
183
  def disable(*names)
186
- self.hook_names -= names.flatten.map do |hook_name|
184
+ list = names.flatten.map do |hook_name|
187
185
  hook_name.to_s.singularize.to_sym
188
186
  end
187
+
188
+ self.hook_names = (hook_names - list).freeze
189
189
  end
190
190
 
191
191
  # It's an alternative to +self.hook_names += %i[*names]+ which
192
192
  # enables additional hooks
193
193
  def enable(*names)
194
- self.hook_names += names.flatten.map do |hook_name|
194
+ list = names.flatten.map do |hook_name|
195
195
  hook_name.to_s.singularize.to_sym
196
196
  end
197
- end
198
197
 
199
- # Return the module where the GraphQL types should be created at
200
- def gql_module
201
- name.start_with?('GraphQL::') ? module_parent : ::GraphQL
198
+ self.hook_names = (hook_names + list).freeze
202
199
  end
203
200
 
204
201
  # Add one or more fields to the list of fields that needs to be
@@ -223,6 +220,7 @@ module Rails
223
220
 
224
221
  # Run a list of hooks using the +source+ as the instance of the block
225
222
  def run_hooks(hook_name, source = self)
223
+ super
226
224
  all_hooks.try(:[], hook_name.to_sym)&.reverse_each do |block|
227
225
  source.instance_exec(&block)
228
226
  end
@@ -236,17 +234,12 @@ module Rails
236
234
  super if defined? super
237
235
  end
238
236
 
239
- # Find all classes that inherits from source that are abstract,
240
- # meaning that they are a base sources
237
+ # Constantize all the base sources that were defined in the settings
241
238
  def base_sources
242
239
  @@base_sources ||= GraphQL.config.sources.map(&:constantize).to_set
243
240
  end
244
241
 
245
242
  end
246
-
247
- step(:query, &ATTACH_FIELDS_STEP)
248
- step(:mutation, &ATTACH_FIELDS_STEP)
249
- step(:subscription, &ATTACH_FIELDS_STEP)
250
243
  end
251
244
  end
252
245
  end
@@ -10,6 +10,7 @@ module Rails
10
10
  #
11
11
  # The subscription provider associated with Rails Action Cable, that
12
12
  # delivers subscription notifications through an Action Cable Channel
13
+ # TODO: Try to serialize and deserialize the origin
13
14
  class ActionCable < Base
14
15
  INTERNAL_CHANNEL = 'rails-graphql:events'
15
16
 
@@ -17,7 +18,7 @@ module Rails
17
18
 
18
19
  def initialize(*args, **options)
19
20
  @cable = options.fetch(:cable, ::ActionCable)
20
- @prefix = options.fetch(:prefix, 'graphql')
21
+ @prefix = options.fetch(:prefix, 'rails-graphql')
21
22
 
22
23
  @event_callback = ->(message) do
23
24
  method_name, args, xargs = Marshal.load(message)
@@ -46,20 +47,20 @@ module Rails
46
47
  end
47
48
 
48
49
  def async_remove(item)
49
- return unless instance?(item) || !(item = store.fetch(item)).nil?
50
+ return if (item = store.fetch(item)).nil?
50
51
  cable.server.broadcast(stream_name(item), unsubscribed_payload)
51
52
  store.remove(item)
52
53
 
53
54
  log(:removed, item)
54
55
  end
55
56
 
56
- def async_update(item, data = nil)
57
- return unless instance?(item) || !(item = store.fetch(item)).nil?
57
+ def async_update(item, data = nil, **xargs)
58
+ return if (item = store.fetch(item)).nil?
58
59
  removing = false
59
60
 
60
61
  log(:updated, item) do
61
- data = execute(item) if data.nil?
62
- item.updated!
62
+ data = execute(item, **xargs) if data.nil?
63
+ store.update!(item)
63
64
 
64
65
  unless (removing = unsubscribing?(data))
65
66
  data = { 'result' => data, 'more' => true }
@@ -71,7 +72,7 @@ module Rails
71
72
  end
72
73
 
73
74
  def stream_name(item)
74
- "#{prefix}:#{instance?(item) ? item.sid : item}"
75
+ "#{prefix}:#{item.sid}"
75
76
  end
76
77
 
77
78
  protected
@@ -80,8 +81,8 @@ module Rails
80
81
  item.origin.stream_from(stream_name(item))
81
82
  end
82
83
 
83
- def execute(subscription, **xargs)
84
- super(subscription, origin: subscription.origin, **xargs, as: :hash)
84
+ def execute(item, **xargs)
85
+ super(item, origin: item.origin, **xargs, as: :hash)
85
86
  end
86
87
 
87
88
  def async_exec(method_name, *args, **xargs)
@@ -94,7 +94,7 @@ module Rails
94
94
  # Update one single subscription, for broadcasting, use +update_all+
95
95
  # or +search_and_update+. You can provide the +data+ that will be sent
96
96
  # to upstream, skipping it from being collected from a request
97
- async_exec def update(item, data = nil)
97
+ async_exec def update(item, data = nil, **xargs)
98
98
  raise NotImplementedError, +"#{self.class.name} does not implement update"
99
99
  end
100
100
 
@@ -104,15 +104,15 @@ module Rails
104
104
  end
105
105
 
106
106
  # A simple shortcut for calling update on each individual sid
107
- async_exec def update_all(*sids)
107
+ async_exec def update_all(*sids, **xargs)
108
108
  return if sids.blank?
109
109
 
110
110
  enum = GraphQL.enumerate(store.fetch(*sids))
111
111
  enum.group_by(&:operation_id).each_value do |subscriptions|
112
- data = execute(subscriptions.first, broadcasting: true) \
112
+ data = execute(subscriptions.first, **xargs, broadcasting: true) \
113
113
  unless subscriptions.one? || first.broadcastable?
114
114
 
115
- subscriptions.each { |item| update(item, data) }
115
+ subscriptions.each { |item| update(item, data, **xargs) }
116
116
  end
117
117
  end
118
118
 
@@ -123,7 +123,8 @@ module Rails
123
123
 
124
124
  # A shortcut for finding the subscriptions and then updating them
125
125
  async_exec def search_and_update(**options)
126
- update_all(*find_each(**options))
126
+ xargs = options.slice(:data_for)
127
+ update_all(*find_each(**options), **xargs)
127
128
  end
128
129
 
129
130
  # Get the payload that should be used when unsubscribing