rails-graphql 1.0.0.beta → 1.0.0.rc1
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 +9 -4
- data/lib/rails/graphql/alternative/subscription.rb +2 -1
- data/lib/rails/graphql/argument.rb +5 -3
- data/lib/rails/graphql/callback.rb +8 -7
- 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 +73 -57
- 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 +30 -24
- 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 +5 -5
- 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 +2 -0
- 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_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 +28 -22
- data/lib/rails/graphql/helpers/with_name.rb +3 -2
- data/lib/rails/graphql/helpers/with_schema_fields.rb +72 -48
- 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 +45 -24
- 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 +11 -4
- 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 +67 -37
- 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 +20 -21
- data/lib/rails/graphql/source/active_record_source.rb +93 -33
- data/lib/rails/graphql/source/base.rb +11 -39
- data/lib/rails/graphql/source/builder.rb +9 -22
- data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
- data/lib/rails/graphql/source.rb +23 -37
- 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 +196 -0
- data/lib/rails/graphql/type/enum.rb +17 -9
- data/lib/rails/graphql/type/input.rb +20 -4
- 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 +10 -5
- 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 +1 -1
- data/lib/rails/graphql/type/union.rb +7 -2
- data/lib/rails/graphql/type.rb +10 -2
- data/lib/rails/graphql/type_map.rb +18 -7
- data/lib/rails/graphql/uri.rb +5 -4
- data/lib/rails/graphql/version.rb +6 -2
- data/lib/rails/graphql.rb +9 -7
- 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 +0 -10
- 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 +20 -7
@@ -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, default: 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)
|
@@ -124,6 +139,22 @@ module Rails
|
|
124
139
|
super if model&.table_exists?
|
125
140
|
end
|
126
141
|
|
142
|
+
# Allows setting up an interface instead of an object. Mostly because
|
143
|
+
# some models are better dealt as interfaces than actual objects
|
144
|
+
def interface
|
145
|
+
@interface ||= create_type(superclass: interface_class, gql_name: object_name)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Checks if the source is building an interface instead of an object
|
149
|
+
def interface?
|
150
|
+
defined?(@interface) || act_as_interface? || sti_interface?
|
151
|
+
end
|
152
|
+
|
153
|
+
# Provides access to the default plural query field, for associations interconnection
|
154
|
+
def collection_field
|
155
|
+
find_field(:query, plural)
|
156
|
+
end
|
157
|
+
|
127
158
|
# Hook into the unregister to clean enums
|
128
159
|
def unregister!
|
129
160
|
super
|
@@ -151,23 +182,56 @@ module Rails
|
|
151
182
|
|
152
183
|
private
|
153
184
|
|
185
|
+
# Hook into the build process to selective avoid :interface or :object
|
186
|
+
def build!(type)
|
187
|
+
return if type == :object && interface?
|
188
|
+
return if type == :interface && !interface?
|
189
|
+
super
|
190
|
+
end
|
191
|
+
|
154
192
|
def presence_validator
|
155
193
|
::ActiveRecord::Validations::PresenceValidator
|
156
194
|
end
|
157
195
|
end
|
158
196
|
|
159
|
-
# Prepare to load multiple records from the underlying
|
160
|
-
def load_records(scope =
|
197
|
+
# Prepare to load multiple records from the underlying model
|
198
|
+
def load_records(scope = nil)
|
199
|
+
scope ||= event.last_result || model.default_scoped
|
161
200
|
inject_scopes(scope, :relation)
|
162
201
|
end
|
163
202
|
|
164
|
-
# Prepare to load a single record from the underlying
|
165
|
-
def load_record(scope =
|
203
|
+
# Prepare to load a single record from the underlying model
|
204
|
+
def load_record(scope = nil, find_by: nil)
|
205
|
+
scope ||= event.last_result || model.default_scoped
|
166
206
|
find_by ||= { primary_key => event.argument(primary_key) }
|
167
207
|
inject_scopes(scope, :relation).find_by(find_by)
|
168
208
|
end
|
169
209
|
|
170
|
-
#
|
210
|
+
# The perform step for the +create+ based mutation
|
211
|
+
def create_record
|
212
|
+
input_argument.resource.tap(&:save!)
|
213
|
+
rescue ::ActiveRecord::RecordInvalid => error
|
214
|
+
errors_to_extensions(error.record.errors)
|
215
|
+
raise
|
216
|
+
end
|
217
|
+
|
218
|
+
# The perform step for the +update+ based mutation
|
219
|
+
def update_record
|
220
|
+
current_value.tap { |record| record.update!(**input_argument.params) }
|
221
|
+
rescue ::ActiveRecord::RecordInvalid => error
|
222
|
+
errors_to_extensions(error.record.errors)
|
223
|
+
raise
|
224
|
+
end
|
225
|
+
|
226
|
+
# The perform step for the +delete+ based mutation
|
227
|
+
def destroy_record
|
228
|
+
!!current_value.destroy!
|
229
|
+
rescue ::ActiveRecord::RecordInvalid => error
|
230
|
+
errors_to_extensions(error.record.errors)
|
231
|
+
raise
|
232
|
+
end
|
233
|
+
|
234
|
+
# Get the chain result and preload the records with the resulting scope
|
171
235
|
def preload_association(association, scope = nil)
|
172
236
|
event.stop(preload(association, scope || event.last_result), layer: :object)
|
173
237
|
end
|
@@ -177,10 +241,11 @@ module Rails
|
|
177
241
|
scope = model._reflect_on_association(association).klass.default_scoped
|
178
242
|
|
179
243
|
# Apply proxied injected scopes
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
244
|
+
# TODO: Arguments comes with their proxy, so we might not need this
|
245
|
+
# proxied = event.field.try(:proxied_owner)
|
246
|
+
# scope = event.on_instance(proxied) do |instance|
|
247
|
+
# instance.inject_scopes(scope, :relation)
|
248
|
+
# end if proxied.present? && proxied <= Source::ActiveRecordSource
|
184
249
|
|
185
250
|
# Apply self defined injected scopes
|
186
251
|
inject_scopes(scope, :relation)
|
@@ -189,26 +254,21 @@ module Rails
|
|
189
254
|
# Once the records are pre-loaded due to +preload_association+, use the
|
190
255
|
# parent value and the preloader result to get the records
|
191
256
|
def parent_owned_records(collection_result = false)
|
192
|
-
data = event.data[:
|
257
|
+
data = event.data[:prepared_data]
|
193
258
|
return collection_result ? [] : nil unless data
|
194
259
|
|
195
260
|
result = data.records_by_owner[current_value] || []
|
196
261
|
collection_result ? result : result.first
|
197
262
|
end
|
198
263
|
|
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
|
264
|
+
# Expose the errors to the extensions of the response
|
265
|
+
def errors_to_extensions(errors, path = nil, format = nil)
|
266
|
+
format ||= self.class.errors_to_extensions
|
267
|
+
return unless format
|
208
268
|
|
209
|
-
|
210
|
-
|
211
|
-
|
269
|
+
path ||= [operation.name, field.gql_name].compact
|
270
|
+
hash = GraphQL.enumerate(path).reduce(request.extensions) { |h, k| h[k] ||= {} }
|
271
|
+
hash.replace(format == :messages ? errors.as_json : errors.details)
|
212
272
|
end
|
213
273
|
|
214
274
|
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,24 @@ 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.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
|
-
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
|
70
|
+
def create_type(type = nil, name = base_name, **xargs, &block)
|
71
|
+
xargs[:owner] ||= self
|
72
|
+
xargs[:namespaces] = namespaces
|
73
|
+
xargs[:assigned_to] = safe_assigned_class
|
74
|
+
superclass = xargs.delete(:superclass) || type
|
98
75
|
|
99
|
-
|
100
|
-
|
101
|
-
# Keep track of all the types created byt this source
|
102
|
-
def created_types
|
103
|
-
@created_types ||= []
|
76
|
+
GraphQL::Type.create!(self, name, superclass, **xargs, &block)
|
104
77
|
end
|
105
78
|
|
106
79
|
end
|
107
|
-
|
108
80
|
end
|
109
81
|
end
|
110
82
|
end
|
@@ -34,10 +34,10 @@ module Rails
|
|
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,13 +47,6 @@ 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
|
55
|
-
end
|
56
|
-
|
57
50
|
private
|
58
51
|
|
59
52
|
# Import all options-based settings for skipping field
|
@@ -100,29 +93,23 @@ module Rails
|
|
100
93
|
# Build all the objects associated with this source
|
101
94
|
def build!(type)
|
102
95
|
ensure_build!(type)
|
96
|
+
built << type
|
103
97
|
|
98
|
+
schema_type = Helpers::WithSchemaFields::TYPE_FIELD_CLASS.key?(type)
|
104
99
|
catch(:skip) { run_hooks(:start) } unless built?(:start)
|
105
|
-
catch(:skip) { run_hooks(type, hook_scope_for(type)) }
|
100
|
+
catch(:skip) { run_hooks(type, hook_scope_for(type, schema_type)) }
|
106
101
|
|
107
|
-
|
102
|
+
attach_fields!(type, fields_for(type)) if schema_type && fields_for?(type)
|
108
103
|
end
|
109
104
|
|
110
105
|
# Get the correct +self_object+ for the hook instance
|
111
|
-
def hook_scope_for(type)
|
106
|
+
def hook_scope_for(type, schema_type)
|
112
107
|
type = type.to_sym
|
113
|
-
|
114
|
-
|
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)
|
108
|
+
klass = schema_type ? 'WithSchemaFields::ScopedConfig' : 'AttributeDelegator'
|
109
|
+
Source::ScopedConfig.new(self, Helpers.const_get(klass).new(self, type), type)
|
121
110
|
end
|
122
111
|
|
123
112
|
end
|
124
113
|
end
|
125
114
|
end
|
126
115
|
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
|
@@ -113,6 +105,22 @@ module Rails
|
|
113
105
|
base_sources.reverse_each.find { |source| object <= source.assigned_class }
|
114
106
|
end
|
115
107
|
|
108
|
+
# Add a new description hook. You can use +throw :skip+ and skip
|
109
|
+
# parent hooks. If the class is already built, then execute the hook.
|
110
|
+
# Use the +unshift: true+ to add the hook at the beginning of the
|
111
|
+
# list, which will then be the last to run
|
112
|
+
def step(hook_name, unshift: false, &block)
|
113
|
+
raise ArgumentError, (+<<~MSG).squish unless hook_names.include?(hook_name.to_sym)
|
114
|
+
The #{hook_name.inspect} is not a valid hook method.
|
115
|
+
MSG
|
116
|
+
|
117
|
+
if built?(hook_name)
|
118
|
+
hook_scope_for(hook_name).instance_exec(&block)
|
119
|
+
else
|
120
|
+
hooks[hook_name.to_sym].public_send(unshift ? :unshift : :push, block)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
116
124
|
# Attach all defined schema fields into the schemas using the namespaces
|
117
125
|
# configured for the source
|
118
126
|
def attach_fields!(type = :all, from = self)
|
@@ -149,22 +157,6 @@ module Rails
|
|
149
157
|
segmented_skip_fields[source] += fields.flatten.compact.map(&:to_sym).to_set
|
150
158
|
end
|
151
159
|
|
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
160
|
# Creates a hook that throws a done action, preventing any parent hooks
|
169
161
|
def skip(*names)
|
170
162
|
names.each do |hook_name|
|
@@ -183,22 +175,21 @@ module Rails
|
|
183
175
|
# It's an alternative to +self.hook_names -= %i[*names]+ which
|
184
176
|
# disables a specific hook
|
185
177
|
def disable(*names)
|
186
|
-
|
178
|
+
list = names.flatten.map do |hook_name|
|
187
179
|
hook_name.to_s.singularize.to_sym
|
188
180
|
end
|
181
|
+
|
182
|
+
self.hook_names = (hook_names - list).freeze
|
189
183
|
end
|
190
184
|
|
191
185
|
# It's an alternative to +self.hook_names += %i[*names]+ which
|
192
186
|
# enables additional hooks
|
193
187
|
def enable(*names)
|
194
|
-
|
188
|
+
list = names.flatten.map do |hook_name|
|
195
189
|
hook_name.to_s.singularize.to_sym
|
196
190
|
end
|
197
|
-
end
|
198
191
|
|
199
|
-
|
200
|
-
def gql_module
|
201
|
-
name.start_with?('GraphQL::') ? module_parent : ::GraphQL
|
192
|
+
self.hook_names = (hook_names + list).freeze
|
202
193
|
end
|
203
194
|
|
204
195
|
# Add one or more fields to the list of fields that needs to be
|
@@ -236,17 +227,12 @@ module Rails
|
|
236
227
|
super if defined? super
|
237
228
|
end
|
238
229
|
|
239
|
-
#
|
240
|
-
# meaning that they are a base sources
|
230
|
+
# Constantize all the base sources that were defined in the settings
|
241
231
|
def base_sources
|
242
232
|
@@base_sources ||= GraphQL.config.sources.map(&:constantize).to_set
|
243
233
|
end
|
244
234
|
|
245
235
|
end
|
246
|
-
|
247
|
-
step(:query, &ATTACH_FIELDS_STEP)
|
248
|
-
step(:mutation, &ATTACH_FIELDS_STEP)
|
249
|
-
step(:subscription, &ATTACH_FIELDS_STEP)
|
250
236
|
end
|
251
237
|
end
|
252
238
|
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
|
@@ -65,6 +65,11 @@ module Rails
|
|
65
65
|
raise NotImplementedError, +"#{self.class.name} does not implement remove"
|
66
66
|
end
|
67
67
|
|
68
|
+
# Marks that a subscription has received an update
|
69
|
+
def update!(item)
|
70
|
+
raise NotImplementedError, +"#{self.class.name} does not implement update!"
|
71
|
+
end
|
72
|
+
|
68
73
|
# Check if a given sid or instance is stored
|
69
74
|
def has?(item)
|
70
75
|
raise NotImplementedError, +"#{self.class.name} does not implement has?"
|
@@ -123,21 +128,12 @@ module Rails
|
|
123
128
|
def hash_for(value, klass = nil)
|
124
129
|
if !klass.nil?
|
125
130
|
klass.hash ^ value.hash
|
126
|
-
elsif extract_class_from?(value)
|
127
|
-
value.class.hash ^ value.id.hash
|
128
131
|
elsif value.is_a?(Numeric)
|
129
132
|
value
|
130
133
|
else
|
131
134
|
value.hash
|
132
135
|
end
|
133
136
|
end
|
134
|
-
|
135
|
-
# Check if ActiveRecord::Base is available and then if the object
|
136
|
-
# provided is an instance of it, so that the serialize can work
|
137
|
-
# correctly
|
138
|
-
def extract_class_from?(value)
|
139
|
-
defined?(ActiveRecord) && value.is_a?(ActiveRecord::Base)
|
140
|
-
end
|
141
137
|
end
|
142
138
|
end
|
143
139
|
end
|