rails-graphql 1.0.3 → 1.0.6
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/lib/gql_parser.so +0 -0
- data/lib/rails/graphql/config.rb +2 -0
- data/lib/rails/graphql/field/output_field.rb +4 -0
- data/lib/rails/graphql/helpers/attribute_delegator.rb +8 -1
- data/lib/rails/graphql/helpers/with_arguments.rb +5 -0
- data/lib/rails/graphql/railties/log_subscriber.rb +2 -1
- data/lib/rails/graphql/request/backtrace.rb +4 -1
- data/lib/rails/graphql/request/component/field.rb +1 -1
- data/lib/rails/graphql/request/steps/organizable.rb +2 -2
- data/lib/rails/graphql/request/strategy.rb +11 -7
- data/lib/rails/graphql/source/active_record_source.rb +6 -0
- data/lib/rails/graphql/source/base.rb +30 -2
- data/lib/rails/graphql/uri.rb +2 -0
- data/lib/rails/graphql/version.rb +1 -1
- data/lib/rails/graphql.rb +1 -0
- data/test/assets/sqlite.gql +10 -0
- data/test/config.rb +1 -0
- data/test/graphql/source_test.rb +10 -0
- data/test/integration/memory/star_wars_query_test.rb +14 -0
- data/test/integration/memory/star_wars_validation_test.rb +14 -1
- data/test/integration/schemas/mysql.rb +1 -1
- data/test/integration/schemas/sqlite.rb +21 -2
- data/test/integration/sqlite/star_wars_query_test.rb +16 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d18315ad503f122d67555202db0417507d7b5d72ecf5c060396bf7974b7cf826
|
4
|
+
data.tar.gz: 3688696c6d83cd9aebe2be3024906292bbdd291f3fb4b272bb5b1841714ba10e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79d8b337fd5ef5caaf93a6bc8dfc4a21cc6f7f5f5633ab1b9e4076bef0c90c163792283fd2b0cd02063c3111bf2a569e4909130e04e419455a8f9c3d80dc744d
|
7
|
+
data.tar.gz: 58d5dbe146a65ed87987b05d5e00ba14bb3a22ded1ba300f27bb08ef2f36783bcabfe42f9c2550187f01d286341387ac17acec51d79ee00fccfc7a34783d5c5e
|
data/lib/gql_parser.so
CHANGED
Binary file
|
data/lib/rails/graphql/config.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Rails
|
4
4
|
module GraphQL
|
5
|
+
AR710 = (ActiveRecord.gem_version >= Gem::Version.new('7.1.0'))
|
6
|
+
|
5
7
|
configure do |config|
|
6
8
|
# This helps to keep track of when things were cached and registered. Cached
|
7
9
|
# objects with mismatching versions need to be upgraded or simply reloaded.
|
@@ -5,13 +5,20 @@ module Rails
|
|
5
5
|
module Helpers
|
6
6
|
# This is an extra magic on top of the delegator class from the standard
|
7
7
|
# lib that allows fetching a specific property of the delegated object
|
8
|
-
class AttributeDelegator <
|
8
|
+
class AttributeDelegator < ::BasicObject
|
9
|
+
undef_method :==
|
10
|
+
undef_method :equal?
|
11
|
+
|
9
12
|
def initialize(obj = nil, attribute = nil, cache: true, &block)
|
10
13
|
@delegate_sd_attr = attribute
|
11
14
|
@delegate_sd_obj = block.presence || obj
|
12
15
|
@delegate_cache = cache
|
13
16
|
end
|
14
17
|
|
18
|
+
def raise(*args)
|
19
|
+
::Object.send(:raise, *args)
|
20
|
+
end
|
21
|
+
|
15
22
|
private
|
16
23
|
|
17
24
|
def respond_to_missing?(method_name, include_private = false)
|
@@ -100,6 +100,11 @@ module Rails
|
|
100
100
|
defined?(@arguments) && @arguments.key?(name)
|
101
101
|
end
|
102
102
|
|
103
|
+
# Check if any argument has been defined at all
|
104
|
+
def has_arguments?
|
105
|
+
defined?(@arguments) && !@arguments.empty?
|
106
|
+
end
|
107
|
+
|
103
108
|
# Validate all the arguments to make sure the definition is valid
|
104
109
|
def validate!(*)
|
105
110
|
super if defined? super
|
@@ -98,7 +98,8 @@ module Rails
|
|
98
98
|
parts = [' GraphQL', suffix.presence, event.payload[:name]]
|
99
99
|
parts << "(#{duration}ms)" unless duration.zero?
|
100
100
|
|
101
|
-
|
101
|
+
style = AR710 ? { bold: true } : true
|
102
|
+
color(parts.compact.join(' '), MAGENTA, style)
|
102
103
|
end
|
103
104
|
|
104
105
|
def debug_variables(vars)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_support/parameter_filter'
|
4
|
+
|
3
5
|
module Rails
|
4
6
|
module GraphQL
|
5
7
|
class Request
|
@@ -130,7 +132,8 @@ module Rails
|
|
130
132
|
return '{}' if value.blank?
|
131
133
|
|
132
134
|
request.cache(:backtrace_arguments_filter) do
|
133
|
-
|
135
|
+
filters = GraphQL.config.filter_parameters || EMPTY_ARRAY
|
136
|
+
ActiveSupport::ParameterFilter.new(filters)
|
134
137
|
end.filter(value)
|
135
138
|
end
|
136
139
|
|
@@ -16,7 +16,7 @@ module Rails
|
|
16
16
|
delegate :decorate, to: :type_klass
|
17
17
|
delegate :operation, :variables, :request, to: :parent
|
18
18
|
delegate :method_name, :resolver, :performer, :type_klass, :leaf_type?,
|
19
|
-
:dynamic_resolver?, :mutation?, to: :field
|
19
|
+
:dynamic_resolver?, :has_arguments?, :mutation?, to: :field
|
20
20
|
|
21
21
|
attr_reader :name, :alias_name, :parent, :field, :arguments, :current_object
|
22
22
|
|
@@ -118,9 +118,9 @@ module Rails
|
|
118
118
|
# Helper parser for arguments that also collect necessary variables
|
119
119
|
# Default values forces this method to run even without nodes
|
120
120
|
def parse_arguments(nodes)
|
121
|
-
return @arguments = EMPTY_HASH if nodes.blank?
|
121
|
+
return @arguments = EMPTY_HASH if nodes.blank? && !self.has_arguments?
|
122
122
|
|
123
|
-
args = nodes
|
123
|
+
args = nodes&.each_with_object({}) do |(name, value, var_name), hash|
|
124
124
|
hash[name.to_s] = var_name.nil? ? value : var_name
|
125
125
|
end
|
126
126
|
|
@@ -131,8 +131,9 @@ module Rails
|
|
131
131
|
return unless args.size.zero?
|
132
132
|
|
133
133
|
if field.try(:dynamic_resolver?)
|
134
|
-
|
135
|
-
|
134
|
+
extra = prepared_data_for(field, with_null: true)
|
135
|
+
extra = extra === PreparedData::NULL ? EMPTY_HASH : { prepared: extra }
|
136
|
+
args << Event.trigger(:resolve, field, self, **extra, &field.resolver)
|
136
137
|
elsif field.prepared_data?
|
137
138
|
args << prepared_data_for(field)
|
138
139
|
else
|
@@ -206,11 +207,14 @@ module Rails
|
|
206
207
|
|
207
208
|
# Get the prepared data for the given +field+, getting ready for
|
208
209
|
# resolve, while ensuring to check prepared data on request
|
209
|
-
def prepared_data_for(field)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
210
|
+
def prepared_data_for(field, with_null: false)
|
211
|
+
if field.prepared_data?
|
212
|
+
request.prepared_data_for(field).next
|
213
|
+
elsif @data_pool.key?(field)
|
214
|
+
@data_pool[field]
|
215
|
+
elsif with_null
|
216
|
+
PreparedData::NULL
|
217
|
+
end
|
214
218
|
end
|
215
219
|
|
216
220
|
# Simply run the organize step for compilation
|
@@ -256,6 +256,12 @@ module Rails
|
|
256
256
|
# Once the records are pre-loaded due to +preload_association+, use the
|
257
257
|
# parent value and the preloader result to get the records
|
258
258
|
def parent_owned_records(collection_result = false)
|
259
|
+
# The absence of the prepared data key means we got to a point that we
|
260
|
+
# don't know the result of the association, so we simply call it
|
261
|
+
unless event.data.key?(:prepared_data)
|
262
|
+
return current_value.public_send(field.method_name)
|
263
|
+
end
|
264
|
+
|
259
265
|
data = event.data[:prepared_data]
|
260
266
|
return collection_result ? [] : nil unless data
|
261
267
|
|
@@ -41,7 +41,9 @@ module Rails
|
|
41
41
|
# to the +::GraphQL+ namespace with the addition of any namespace of
|
42
42
|
# the current class
|
43
43
|
def object
|
44
|
-
@object ||= create_type(superclass: object_class, gql_name: object_name)
|
44
|
+
@object ||= create_type(superclass: object_class, gql_name: object_name).tap do |t|
|
45
|
+
t.include(const_get(:ObjectMethods)) if const_defined?(:ObjectMethods, false)
|
46
|
+
end
|
45
47
|
end
|
46
48
|
|
47
49
|
# Return the GraphQL input type associated with the source. It will
|
@@ -49,7 +51,9 @@ module Rails
|
|
49
51
|
# to the +::GraphQL+ namespace with the addition of any namespace of
|
50
52
|
# the current class
|
51
53
|
def input
|
52
|
-
@input ||= create_type(superclass: input_class, gql_name: input_name)
|
54
|
+
@input ||= create_type(superclass: input_class, gql_name: input_name).tap do |t|
|
55
|
+
t.include(const_get(:InputMethods)) if const_defined?(:InputMethods, false)
|
56
|
+
end
|
53
57
|
end
|
54
58
|
|
55
59
|
protected
|
@@ -77,6 +81,30 @@ module Rails
|
|
77
81
|
GraphQL::Type.create!(self, name, superclass, **xargs, &block)
|
78
82
|
end
|
79
83
|
|
84
|
+
# Allow setting methods on the source object via a proper module
|
85
|
+
def object_methods(&block)
|
86
|
+
type_extend_with_module(:object, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Allow setting methods on the source input via a proper module
|
90
|
+
def input_methods(&block)
|
91
|
+
type_extend_with_module(:input, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Allows adding methods to a type by properly providing a module
|
97
|
+
# and adding it to the type
|
98
|
+
def type_extend_with_module(type, &block)
|
99
|
+
mod_name = :"#{type.to_s.classify}Methods"
|
100
|
+
mod = const_get(mod_name) if const_defined?(mod_name, false)
|
101
|
+
mod ||= const_set(mod_name, Module.new).tap do |m|
|
102
|
+
instance_variable_get("@#{type}")&.include(m)
|
103
|
+
end
|
104
|
+
|
105
|
+
mod.module_eval(&block)
|
106
|
+
end
|
107
|
+
|
80
108
|
end
|
81
109
|
end
|
82
110
|
end
|
data/lib/rails/graphql/uri.rb
CHANGED
data/lib/rails/graphql.rb
CHANGED
data/test/assets/sqlite.gql
CHANGED
@@ -171,6 +171,8 @@ input LiteFactionInput {
|
|
171
171
|
|
172
172
|
basesAttributes: [LiteBaseInput!]
|
173
173
|
|
174
|
+
greeting: String
|
175
|
+
|
174
176
|
name: String
|
175
177
|
|
176
178
|
shipsAttributes: [LiteShipInput!]
|
@@ -206,6 +208,8 @@ type LiteFaction {
|
|
206
208
|
|
207
209
|
bases: [LiteBase!]!
|
208
210
|
|
211
|
+
greeting: String
|
212
|
+
|
209
213
|
name: String
|
210
214
|
|
211
215
|
ships: [LiteShip!]!
|
@@ -222,6 +226,10 @@ type LiteShip {
|
|
222
226
|
name: String
|
223
227
|
}
|
224
228
|
|
229
|
+
type Sample {
|
230
|
+
faction: LiteFaction!
|
231
|
+
}
|
232
|
+
|
225
233
|
type _Mutation {
|
226
234
|
createLiteBase(liteBase: LiteBaseInput!): LiteBase!
|
227
235
|
|
@@ -258,6 +266,8 @@ type _Query {
|
|
258
266
|
liteShip(id: ID!): LiteShip!
|
259
267
|
|
260
268
|
liteShips: [LiteShip!]!
|
269
|
+
|
270
|
+
sample: Sample!
|
261
271
|
}
|
262
272
|
|
263
273
|
"""
|
data/test/config.rb
CHANGED
data/test/graphql/source_test.rb
CHANGED
@@ -129,6 +129,9 @@ class GraphQL_SourceTest < GraphQL::TestCase
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def test_disable
|
132
|
+
# class_attribute changed and the stub does not work as expected
|
133
|
+
return skip if rails8?
|
134
|
+
|
132
135
|
described_class.stub(:hook_names, Set[:start]) do
|
133
136
|
assert_includes(described_class.hook_names, :start)
|
134
137
|
described_class.send(:disable, 'starts')
|
@@ -137,6 +140,9 @@ class GraphQL_SourceTest < GraphQL::TestCase
|
|
137
140
|
end
|
138
141
|
|
139
142
|
def test_enable
|
143
|
+
# class_attribute changed and the stub does not work as expected
|
144
|
+
return skip if rails8?
|
145
|
+
|
140
146
|
described_class.stub(:hook_names, Set[]) do
|
141
147
|
refute_includes(described_class.hook_names, :start)
|
142
148
|
described_class.send(:enable, 'starts')
|
@@ -157,4 +163,8 @@ class GraphQL_SourceTest < GraphQL::TestCase
|
|
157
163
|
def source_const
|
158
164
|
Rails::GraphQL::Source
|
159
165
|
end
|
166
|
+
|
167
|
+
def rails8?
|
168
|
+
ActiveSupport.gem_version >= Gem::Version.new('8')
|
169
|
+
end
|
160
170
|
end
|
@@ -132,6 +132,20 @@ class Integration_Memory_StarWarsQueryTest < GraphQL::IntegrationTestCase
|
|
132
132
|
GQL
|
133
133
|
end
|
134
134
|
|
135
|
+
def test_query_with_field_argument_and_default_value
|
136
|
+
argument = SCHEMA.find_type('Human')[:greeting].arguments[:name]
|
137
|
+
argument.stub_ivar(:@default, 'You') do
|
138
|
+
assert(argument.inspect, "name: String! = 'You'")
|
139
|
+
|
140
|
+
vader = { name: 'Han Solo', greeting: 'Hello You!' }
|
141
|
+
assert_result({ data: { human: vader } }, <<~GQL)
|
142
|
+
query WithFieldArgument {
|
143
|
+
human(id: "1002") { name greeting }
|
144
|
+
}
|
145
|
+
GQL
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
135
149
|
def test_query_with_fragment
|
136
150
|
luke = { name: 'Luke Skywalker', homePlanet: 'Tatooine' }
|
137
151
|
leia = { name: 'Leia Organa', homePlanet: 'Alderaan' }
|
@@ -27,8 +27,21 @@ class Integration_Memory_StarWarsValidationTest < GraphQL::IntegrationTestCase
|
|
27
27
|
locations: [{ line: 2, column: 1 }],
|
28
28
|
}]
|
29
29
|
|
30
|
-
assert_result(errors, <<~
|
30
|
+
assert_result(errors, <<~QUERY, dig: 'errors')
|
31
31
|
query DroidFieldInFragment { hero { name ... on Droid { primaryFunction
|
32
|
+
QUERY
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_fields_with_required_arguments
|
36
|
+
errors = [{
|
37
|
+
message: 'Invalid arguments for human field: the "id" argument can not be null.',
|
38
|
+
locations: [{ line: 1, column: 19 }, { line: 1, column: 33 }],
|
39
|
+
path: %w[HumanName human],
|
40
|
+
extensions: { stage: 'organize', exception: 'Rails::GraphQL::ArgumentsError' },
|
41
|
+
}]
|
42
|
+
|
43
|
+
assert_result(errors, <<~GQL, dig: 'errors')
|
44
|
+
query HumanName { human { name } }
|
32
45
|
GQL
|
33
46
|
end
|
34
47
|
|
@@ -6,7 +6,7 @@ class MySQLRecord < ActiveRecord::Base
|
|
6
6
|
establish_connection(
|
7
7
|
name: 'mysql',
|
8
8
|
adapter: 'mysql2',
|
9
|
-
host: ENV.fetch('GQL_MYSQL_HOST', '
|
9
|
+
host: ENV.fetch('GQL_MYSQL_HOST', '127.0.0.1'),
|
10
10
|
database: ENV.fetch('GQL_MYSQL_DATABASE', 'starwars'),
|
11
11
|
username: ENV.fetch('GQL_MYSQL_USERNAME', 'root'),
|
12
12
|
password: ENV['GQL_MYSQL_PASSWORD'],
|
@@ -14,6 +14,7 @@ end
|
|
14
14
|
SQLiteRecord.connection.instance_eval do
|
15
15
|
create_table 'lite_factions', force: :cascade do |t|
|
16
16
|
t.string 'name'
|
17
|
+
t.string 'greeting'
|
17
18
|
end
|
18
19
|
|
19
20
|
create_table 'lite_bases', force: :cascade do |t|
|
@@ -35,8 +36,8 @@ class LiteFaction < SQLiteRecord
|
|
35
36
|
accepts_nested_attributes_for :bases
|
36
37
|
accepts_nested_attributes_for :ships
|
37
38
|
|
38
|
-
REBELS = create!(name: 'Alliance to Restore the Republic')
|
39
|
-
EMPIRE = create!(name: 'Galactic Empire')
|
39
|
+
REBELS = create!(name: 'Alliance to Restore the Republic', greeting: 'Hello %s!')
|
40
|
+
EMPIRE = create!(name: 'Galactic Empire', greeting: 'Hi %s!')
|
40
41
|
end
|
41
42
|
|
42
43
|
class LiteBase < SQLiteRecord
|
@@ -79,6 +80,12 @@ class StartWarsSqliteSchema < GraphQL::Schema
|
|
79
80
|
with_options on: 'liteFactions' do
|
80
81
|
scoped_argument(:order) { |o| order(name: o) }
|
81
82
|
end
|
83
|
+
|
84
|
+
object_methods do
|
85
|
+
def greeting
|
86
|
+
format(current.greeting, 'you')
|
87
|
+
end
|
88
|
+
end
|
82
89
|
end
|
83
90
|
|
84
91
|
source LiteBase do
|
@@ -88,4 +95,16 @@ class StartWarsSqliteSchema < GraphQL::Schema
|
|
88
95
|
end
|
89
96
|
|
90
97
|
source LiteShip
|
98
|
+
|
99
|
+
object 'Sample' do
|
100
|
+
field :faction, 'LiteFaction', null: false
|
101
|
+
end
|
102
|
+
|
103
|
+
query_fields do
|
104
|
+
field :sample, 'Sample', null: false
|
105
|
+
end
|
106
|
+
|
107
|
+
def sample
|
108
|
+
{ faction: LiteFaction.last }
|
109
|
+
end
|
91
110
|
end
|
@@ -81,4 +81,20 @@ class Integration_SQLite_StarWarsQueryTest < GraphQL::IntegrationTestCase
|
|
81
81
|
query AllBases($order: String!) { liteBases(order: $order) { name } }
|
82
82
|
GQL
|
83
83
|
end
|
84
|
+
|
85
|
+
def test_query_methods_precedence
|
86
|
+
faction = { greeting: 'Hi you!' }
|
87
|
+
assert_result({ data: { liteFaction: faction } }, <<~GQL)
|
88
|
+
query EmpireFleet { liteFaction(id: "2") { greeting } }
|
89
|
+
GQL
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_nested_non_prepared_source
|
93
|
+
bases = named_list('Death Star', 'Shield Generator', 'Headquarters')
|
94
|
+
sample = { sample: { faction: { name: 'Galactic Empire', bases: bases } } }
|
95
|
+
|
96
|
+
assert_result({ data: sample }, <<~GQL)
|
97
|
+
query SampleFaction { sample { faction { name bases { name } } } }
|
98
|
+
GQL
|
99
|
+
end
|
84
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Silva
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -404,7 +404,7 @@ metadata:
|
|
404
404
|
source_code_uri: https://github.com/virtualshield/rails-graphql
|
405
405
|
bug_tracker_uri: https://github.com/virtualshield/rails-graphql/issues
|
406
406
|
changelog_uri: https://github.com/virtualshield/rails-graphql/blob/master/CHANGELOG.md
|
407
|
-
post_install_message:
|
407
|
+
post_install_message:
|
408
408
|
rdoc_options:
|
409
409
|
- "--title"
|
410
410
|
- GraphQL server for Rails
|
@@ -421,8 +421,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
421
421
|
- !ruby/object:Gem::Version
|
422
422
|
version: '0'
|
423
423
|
requirements: []
|
424
|
-
rubygems_version: 3.
|
425
|
-
signing_key:
|
424
|
+
rubygems_version: 3.4.19
|
425
|
+
signing_key:
|
426
426
|
specification_version: 4
|
427
427
|
summary: GraphQL meets RoR with the most Ruby-like DSL
|
428
428
|
test_files:
|