github-community-projects / graphql-client Goto Github PK
View Code? Open in Web Editor NEWA Ruby library for declaring, composing and executing GraphQL queries
License: MIT License
A Ruby library for declaring, composing and executing GraphQL queries
License: MIT License
It would be nice to be able to fully customize the HTTP client, for example to take advantage of something like async-http.
This should be simple enough to implement by pulling the direct calls to Net::HTTP
out into another object, thus declaring a contract that this library needs from an HTTP client, then allowing to inject another than the default instance of this object (adapter pattern).
Below is a piece of code that used to work fine until v0.16.0 and stopped since v0.17.0. Tested with Ruby 3.2.2.
We use similar code to make the objects returned from GraphQL queries work with Rails' route helpers and for that we need to add #to_param
to our type definitions.
In a Rails project #to_param
is defined on the base Object
class and by default returns self.to_s
(source).
The objects returned from queries inherit from GraphQL::Client::Schema::ObjectClass
which inherits from Object
. So when we call #to_param
on an object returned from the query it gets dispatched as Object#to_param
and doesn't end up in GraphQL::Client::Schema::ObjectClass#method_missing
. Thus instead of IDs of records we get strings like "#<#<Module:0x00000001062529f0>::User:0x0000000106316d50>"
.
It looks that in v0.16.0 all the typed fields were defined as methods on the query objects and since v0.17.0 the library moved to method_missing
dispatch. That's why it's no longer possible to use fields that correspond to instance methods of Object
, like #to_param
.
#!/usr/bin/env ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "graphql", "1.13.2"
# gem "graphql-client", "0.16.0" # <--- this works as expected
gem "graphql-client", "0.21.0"
gem "activesupport", "7.1.3.2"
end
require "graphql"
require "graphql/client"
require "active_support/core_ext/object/to_query"
module MyData
User = Data.define(:id)
end
module MyTypes
class User < GraphQL::Schema::Object
field :id, ID, null: false
field :to_param, String, null: false, resolver_method: :resolve_to_param
def resolve_to_param
"user-#{object.id}"
end
end
class Query < GraphQL::Schema::Object
field :current_user, User, null: false
def current_user
MyData::User.new(id: 1)
end
end
end
class MySchema < GraphQL::Schema
query(MyTypes::Query)
end
client = GraphQL::Client.new(schema: MySchema, execute: MySchema)
TEST_QUERY = client.parse(<<~GRAPHQL)
query {
currentUser {
toParam
}
}
GRAPHQL
current_user = client.query(TEST_QUERY).data.current_user
puts "current_user is #{current_user.inspect}"
puts "Expecting #to_param to return \"user-1\", got: #{current_user.to_param.inspect}"
@rmosolgo any chance we can get a new release soon with the latest changes?
We use graphql-ruby and graphql-client to talk to each other. The graphql-ruby
library defines a few extra types beyond the standard GraphQL types, e.g. ISO8601Date
, ISO8601DateTime
, etc, which we make use of.
In the client we've found that the values of these fields are not getting automatically cast back to Date
/ Time
objects, like they do for other types of object.
I was wondering what might be the best way to get automatic casting for these types. At the moment the only way I can see it might work is by monkey-patching the GraphQL::Schema::BUILT_IN_TYPES
hash, which is a bit ๐คข .
module GraphQL
class Schema
BUILT_IN_TYPES['ISO8601Date'] = GraphQL::Types::ISO8601Date
BUILT_IN_TYPES['ISO8601DateTime'] = GraphQL::Types::ISO8601DateTime
end
end
Is there a better way of managing this? I can imagine that other GraphQL schemas might have other sorts of custom types, but then how should they be re-interpreted by the client?
Incidentally the tests don't tickle this situation, as the schema is defined using the DSL. To get the datetime/date parsing tests to fail, change test/test_query_result.rb to dump/reload the schema:
ReloadedSchema = GraphQL::Client.load_schema(Schema.execute(GraphQL::Introspection.query(include_deprecated_args: true, include_schema_description: true, include_specified_by_url: true, include_is_repeatable: true)))
def setup
@client = GraphQL::Client.new(schema: ReloadedSchema, execute: Schema, enforce_collocated_callers: true)
end
Then the test fails:
bundle exec rake test TEST=test/test_query_result.rb
Run options: --seed 9819
# Running:
F.........F.......F...................
Finished in 0.077330s, 491.4005 runs/s, 1551.7910 assertions/s.
1) Failure:
TestQueryResult#test_interface_within_union_values [test/test_query_result.rb:592]:
Expected: 1970-01-01 00:00:01 UTC
Actual: "1970-01-01T01:00:01+01:00"
2) Failure:
TestQueryResult#test_date_scalar_casting [test/test_query_result.rb:628]:
--- expected
+++ actual
@@ -1 +1,3 @@
-#<Date: 1970-01-01 ((2440588j,0s,0n),+0s,2299161j)>
+# encoding: US-ASCII
+# valid: true
+"1970-01-01"
3) Failure:
TestQueryResult#test_datetime_scalar_casting [test/test_query_result.rb:610]:
Expected: 1970-01-01 01:00:00 +0100
Actual: "1970-01-01T01:00:00+01:00"
38 runs, 120 assertions, 3 failures, 0 errors, 0 skips
rake aborted!
Adding the "monkey patch" above then re-fixes the tests as the casting happens automatically.
This all feels a bit gross! Is there a better way?
Thanks!
Our release step does publish to RubyGems, but a later step fails. Here's the bug report on the action segiddins/rubygems-await#44
We'll need whatever solution or work-around comes up there.
Hey @rmosolgo ๐ ,
GraphQL::Client::Schema::ObjectType
requires "graphql/client/schema/possible_types"
and GraphQL::Client::Schema::PossibleTypes
requires "graphql/client/schema/object_type"
I think we need to fix this. Otherwise warnings like this will appear
/usr/local/bundle/gems/zeitwerk-2.6.13/lib/zeitwerk/kernel.rb:34: warning: /usr/local/bundle/gems/zeitwerk-2.6.13/lib/zeitwerk/kernel.rb:34: warning: loading in progress, circular require considered harmful - /usr/local/bundle/gems/graphql-client-0.21.0/lib/graphql/client/schema/object_type.rb
Thanks
best regards
This library has been around a long time, and it's a hard dependency for a lot of projects. I'd like to release a 1.0.0 version, and before that, I have at least a few things in mind:
If anyone has other suggestions for 1.0, please share them in a comment below.
I have the following GraphQL code:
#Creates auth token needed to access the API
mutation GenerateKey($email: String!, $password: String!) {
authenticate(email: $email, password: $password) {
success
errors {messages, field}
result {key}
}
}
#Send out email
mutation ForgotPassword($email: String!) {
forgotPassword(email: $email) {
success
errors {messages, field}
}
}
#Updates password, Key is not the auth token it comes from the reset email
mutation ResetPassword($email: String!, $key: String!, $password: String!) {
resetPassword(email: $email, key: $key, password: $password) {
success
errors {messages, field}
}
}
I defined GraphQL client as client
and I parse the code above:
mutations = client.parse(graphql_code)
, which returns a Module with three constants: GenerateKey
,ForgotPassword
,ResetPassword
- GraphQL::Client::OperationDefinition
objects.
I couldn't execute this mutation with client.query(mutations.const_get(:GenerateKey), variables: { email: '[email protected]', password: 'my_pass'})
, as I received the following error:
#<GraphQL::Client::Errors @messages={"data"=>["Syntax Error: Expected '$', found Name 'email'."]} @details={"data"=>[{"message"=>"Syntax Error: Expected '$', found Name 'email'.", "locations"=>[{"line"=>2, "column"=>16}], "normalizedPath"=>["data"]}]}>
So I took a look at executed query using mutations.const_get(:GenerateKey).document.to_query_string
and received:
mutation #<Module:0x0000000107b5ef70>__GenerateKey($email: String!, $password: String!) {
authenticate(email: $email, password: $password) {
success
errors {
messages
field
}
result {
key
}
}
}
#<Module:0x0000000107b5ef70>__GenerateKey
mutation name is not a valid GraphQL syntax. Is there a way to workaround it?
After updating dependencies in a project using this gem we have begun to see failures during query parsing:
Caused by NoMethodError: undefined method 'map' for nil
from...gems/graphql-client-0.22.0/lib/graphql/client/schema/interface_type.rb:24:in `define_class'
At a(n uninformed) glance, this appears to be somehow related to graphql 2.3.5 switching from string-based to class-based keys here:
Using graphql 2.3.4
[3] pry(#<Module:0x00000001363208c0>::Actor)> definition.client.possible_types
=> {"__Schema"=>[#<Class:0x000000012ff71680>],
"__Type"=>[#<Class:0x000000012ff71360>],
"__Field"=>[#<Class:0x000000012ff71040>],
"__Directive"=>[#<Class:0x000000012ff70d20>],
...
Using graphql 2.3.5:
[2] pry(#<Module:0x00000001363208c0>::Actor)> definition.client.possible_types
=> {#<Class:0x0000000136325640>=>[#<Class:0x0000000136325640>],
#<Class:0x0000000136325280>=>[#<Class:0x0000000136325280>],
#<Class:0x0000000136324f60>=>[#<Class:0x0000000136324f60>],
#<Class:0x0000000136324c40>=>[#<Class:0x0000000136324c40>],
#<Class:0x0000000136324920>=>[#<Class:0x0000000136324920>],
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.