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.
- checksums.yaml +4 -4
- data/ext/gql_parser.c +1 -16
- data/ext/gql_parser.h +21 -0
- data/ext/shared.c +0 -5
- data/ext/shared.h +6 -6
- data/lib/generators/graphql/channel_generator.rb +27 -0
- data/lib/generators/graphql/controller_generator.rb +9 -4
- data/lib/generators/graphql/install_generator.rb +49 -0
- data/lib/generators/graphql/schema_generator.rb +9 -4
- data/lib/generators/graphql/templates/channel.erb +7 -0
- data/lib/generators/graphql/templates/config.rb +97 -0
- data/lib/generators/graphql/templates/controller.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +5 -3
- data/lib/gql_parser.so +0 -0
- data/lib/rails/graphql/alternative/field_set.rb +12 -0
- data/lib/rails/graphql/alternative/query.rb +13 -8
- data/lib/rails/graphql/alternative/subscription.rb +2 -1
- data/lib/rails/graphql/alternative.rb +4 -0
- data/lib/rails/graphql/argument.rb +5 -3
- data/lib/rails/graphql/callback.rb +10 -8
- data/lib/rails/graphql/collectors/hash_collector.rb +12 -1
- data/lib/rails/graphql/collectors/json_collector.rb +21 -0
- data/lib/rails/graphql/config.rb +86 -59
- data/lib/rails/graphql/directive/include_directive.rb +0 -1
- data/lib/rails/graphql/directive/skip_directive.rb +0 -1
- data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
- data/lib/rails/graphql/directive.rb +31 -25
- data/lib/rails/graphql/event.rb +7 -6
- data/lib/rails/graphql/field/authorized_field.rb +0 -5
- data/lib/rails/graphql/field/input_field.rb +0 -5
- data/lib/rails/graphql/field/mutation_field.rb +5 -6
- data/lib/rails/graphql/field/output_field.rb +13 -2
- data/lib/rails/graphql/field/proxied_field.rb +6 -6
- data/lib/rails/graphql/field/resolved_field.rb +1 -1
- data/lib/rails/graphql/field/subscription_field.rb +35 -52
- data/lib/rails/graphql/field/typed_field.rb +26 -2
- data/lib/rails/graphql/field.rb +20 -19
- data/lib/rails/graphql/global_id.rb +5 -1
- data/lib/rails/graphql/helpers/inherited_collection/array.rb +1 -0
- data/lib/rails/graphql/helpers/inherited_collection/base.rb +3 -1
- data/lib/rails/graphql/helpers/inherited_collection/hash.rb +2 -1
- data/lib/rails/graphql/helpers/registerable.rb +1 -1
- data/lib/rails/graphql/helpers/with_arguments.rb +3 -2
- data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
- data/lib/rails/graphql/helpers/with_callbacks.rb +3 -3
- data/lib/rails/graphql/helpers/with_description.rb +10 -8
- data/lib/rails/graphql/helpers/with_directives.rb +5 -1
- data/lib/rails/graphql/helpers/with_events.rb +1 -0
- data/lib/rails/graphql/helpers/with_fields.rb +30 -24
- data/lib/rails/graphql/helpers/with_name.rb +3 -2
- data/lib/rails/graphql/helpers/with_schema_fields.rb +75 -51
- data/lib/rails/graphql/introspection.rb +1 -1
- data/lib/rails/graphql/railtie.rb +3 -2
- data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
- data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
- data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
- data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
- data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
- data/lib/rails/graphql/railties/base_generator.rb +3 -9
- data/lib/rails/graphql/railties/channel.rb +8 -8
- data/lib/rails/graphql/railties/controller.rb +51 -26
- data/lib/rails/graphql/request/arguments.rb +2 -1
- data/lib/rails/graphql/request/backtrace.rb +31 -10
- data/lib/rails/graphql/request/component/field.rb +15 -8
- data/lib/rails/graphql/request/component/fragment.rb +13 -7
- data/lib/rails/graphql/request/component/operation/subscription.rb +4 -6
- data/lib/rails/graphql/request/component/operation.rb +12 -5
- data/lib/rails/graphql/request/component/spread.rb +13 -4
- data/lib/rails/graphql/request/component/typename.rb +1 -1
- data/lib/rails/graphql/request/component.rb +2 -0
- data/lib/rails/graphql/request/context.rb +1 -1
- data/lib/rails/graphql/request/event.rb +6 -2
- data/lib/rails/graphql/request/helpers/directives.rb +1 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +10 -4
- data/lib/rails/graphql/request/helpers/value_writers.rb +8 -5
- data/lib/rails/graphql/request/prepared_data.rb +3 -1
- data/lib/rails/graphql/request/steps/organizable.rb +1 -1
- data/lib/rails/graphql/request/steps/preparable.rb +1 -1
- data/lib/rails/graphql/request/steps/resolvable.rb +1 -1
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +3 -3
- data/lib/rails/graphql/request/strategy.rb +18 -4
- data/lib/rails/graphql/request/subscription.rb +18 -16
- data/lib/rails/graphql/request.rb +71 -41
- data/lib/rails/graphql/schema.rb +39 -86
- data/lib/rails/graphql/shortcuts.rb +11 -5
- data/lib/rails/graphql/source/active_record/builders.rb +22 -24
- data/lib/rails/graphql/source/active_record_source.rb +96 -34
- data/lib/rails/graphql/source/base.rb +13 -40
- data/lib/rails/graphql/source/builder.rb +14 -22
- data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
- data/lib/rails/graphql/source.rb +31 -38
- data/lib/rails/graphql/subscription/provider/action_cable.rb +10 -9
- data/lib/rails/graphql/subscription/provider/base.rb +6 -5
- data/lib/rails/graphql/subscription/store/base.rb +5 -9
- data/lib/rails/graphql/subscription/store/memory.rb +18 -9
- data/lib/rails/graphql/type/creator.rb +198 -0
- data/lib/rails/graphql/type/enum.rb +17 -9
- data/lib/rails/graphql/type/input.rb +30 -7
- data/lib/rails/graphql/type/interface.rb +15 -4
- data/lib/rails/graphql/type/object/directive_object.rb +6 -5
- data/lib/rails/graphql/type/object/input_value_object.rb +3 -4
- data/lib/rails/graphql/type/object/type_object.rb +40 -13
- data/lib/rails/graphql/type/object.rb +11 -6
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/json_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar/time_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar.rb +2 -2
- data/lib/rails/graphql/type/union.rb +7 -2
- data/lib/rails/graphql/type.rb +10 -2
- data/lib/rails/graphql/type_map.rb +20 -7
- data/lib/rails/graphql/uri.rb +5 -4
- data/lib/rails/graphql/version.rb +6 -2
- data/lib/rails/graphql.rb +11 -8
- data/test/assets/introspection-mem.txt +1 -1
- data/test/assets/introspection.gql +2 -0
- data/test/assets/mem.gql +74 -60
- data/test/assets/mysql.gql +69 -55
- data/test/assets/sqlite.gql +78 -64
- data/test/assets/translate.gql +50 -39
- data/test/config.rb +2 -1
- data/test/graphql/schema_test.rb +2 -31
- data/test/graphql/source_test.rb +1 -11
- data/test/graphql/type/interface_test.rb +8 -5
- data/test/graphql/type/object_test.rb +8 -2
- data/test/graphql/type_map_test.rb +13 -16
- data/test/integration/global_id_test.rb +4 -4
- data/test/integration/memory/star_wars_validation_test.rb +2 -2
- data/test/integration/mysql/star_wars_introspection_test.rb +1 -1
- data/test/integration/resolver_precedence_test.rb +1 -1
- data/test/integration/schemas/memory.rb +3 -4
- data/test/integration/sqlite/star_wars_global_id_test.rb +27 -21
- data/test/integration/sqlite/star_wars_introspection_test.rb +1 -1
- data/test/integration/translate_test.rb +26 -14
- 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 (
|
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,
|
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
|
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? &&
|
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,
|
83
|
+
safe_field(plural, type, full: true) do
|
70
84
|
before_resolve(:load_records)
|
71
85
|
end
|
72
86
|
|
73
|
-
safe_field(singular,
|
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}",
|
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}",
|
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
|
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
|
160
|
-
def load_records(scope =
|
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
|
165
|
-
def load_record(scope =
|
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
|
-
#
|
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
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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[:
|
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
|
-
#
|
200
|
-
def
|
201
|
-
|
202
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
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 =
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
51
|
-
def run_hooks(
|
52
|
-
|
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
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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 =
|
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
|
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
|
data/lib/rails/graphql/source.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
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
|
-
|
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}:#{
|
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(
|
84
|
-
super(
|
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
|
-
|
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
|