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
@@ -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