Add DSL-only mode: to_query_string + spread#115
Conversation
- Client#to_query_string(**kargs, &block): builds full query document from DSL block and returns a String without touching Faraday or HTTP. - Query#spread(fragment_name): emits ...FragmentName in the query string, replacing the deprecated ___Const graphql-client convention. - spec_helper.rb: rescue LoadError on byebug require (Windows Ruby 3.2). - CLAUDE.md: AI agent instructions for this fork.
static_client_query_spec: add schema_path: 'spec/support/fixtures/invoice_api.json'
The client was fetching the schema from the live URL at module load time
(before WebMock is active), causing 403 in CI. Using a local schema file
avoids the network call and matches the pattern in webmock_client_query_spec.
client_dsl_spec: nodes { } → nodes do end (Style/BlockDelimiters)
query.rb: add rubocop:disable Metrics/ClassLength
Class grew past 100 lines after DSL additions (spread, fragment resolution).
spec_helper.rb: add rubocop:disable Lint/HandleExceptions on rescue LoadError
The empty rescue is intentional — byebug unavailable on some platforms.
Note: faraday_adapter_spec.rb:147 fails locally on Windows (TCP error message
format differs from Linux) but passes in CI on ubuntu-latest. Pre-existing.
On Linux, Errno::ECONNREFUSED.new(msg).message prepends 'Connection refused - '. On Windows, the OS prepends a different string. Use a regex that matches the core error_message content rather than the exact platform-specific prefix.
AI agent instructions are local-only and not relevant to the upstream repo. CLAUDE.md is removed from git tracking but kept in the working directory. Added to .gitignore so future sessions don't accidentally commit it.
Root cause: on Ruby 3.1, WebMock's Faraday middleware intercepts requests before they reach Faraday::Adapter::Rack, returning 403 for any URL without a stub. This caused 25 CI failures on that Ruby version. spec/support/context/dummy_client.rb: Replace Faraday::Adapter::Rack with stub_request.to_rack(app). WebMock routes both schema introspection and query execution through the Sinatra dummy app at the WebMock level (before Faraday dispatch). Removed schema_path: so graphql-client gets the full live schema (including test fields: executionErrorInvoice, partialSuccess etc.). spec/graphlient/static_client_query_spec.rb: Same to_rack approach for execution tests (schema_path kept here since the module constants are created at file-load time, before any before block). Removed Faraday::Adapter::Rack from the static client constructor. spec/graphlient/client_schema_spec.rb: Use include instead of eq for error message — Faraday 2.x appends the URL to the message; include matches on both old and new formats. 73 examples, 0 failures
spec/support/context/dummy_client.rb: spec/graphlient/static_client_query_spec.rb: Replace em-dashes (--) with ASCII semicolons/colons in comments. Style/AsciiComments cop requires pure ASCII. Gemfile: Add mutex_m gem -- activesupport 5.x requires mutex_m which was removed from Ruby's standard library in Ruby 3.4. Adding it explicitly silences the warning and fixes the LoadError on Ruby 3.4. .github/workflows/ci.yml: Add Ruby 3.4 to the required CI matrix (was previously only in ruby-head which is allowed to fail). Ruby 3.4 is now released and should be tested.
New sections:
'Build Query Strings without Validation' -- explains the serialization-only
path (no schema load, no graphql-client validation, no HTTP), shows the
basic usage, clarifies when the string can be fed back to client.execute
(fragment-free queries only), explains why fragment spreads cannot go back
through the gem (graphql-client's constant-based fragment model), and
shows how to_query_string is the escape hatch for replacing graphql-client
with your own HTTP transport entirely.
'Fragment Spreads' -- documents Query#spread as the modern alternative to
the triple-underscore convention; covers basic usage and multiple spreads.
|
The Danger job is failing due to an expired token in the workflow — unrelated to this PR's changes. |
- All entries single-line matching file convention (no continuation lines) - ashkan18#115 entries restored to their original order and not modified - ashkan18#116 prefix added to all directives/fragments/scalars/CI entries
|
I fixed CI / Danger in #117, rebase? |
| @@ -1,5 +1,5 @@ | |||
| module Graphlient | |||
| class Query | |||
| class Query # rubocop:disable Metrics/ClassLength | |||
There was a problem hiding this comment.
run rubocop -a ; rubocop --auto-gen-config.
| * Your contribution here. | ||
| * [#113](https://github.com/ashkan18/graphlient/pull/113): Fix CI builds - [@yuki24](https://github.com/yuki24). | ||
| * [#112](https://github.com/ashkan18/graphlient/pull/112): Update graphql-client github repository links in README - [@th1988](https://github.com/th1988). | ||
| * [#115](https://github.com/ashkan18/graphlient/pull/115): `Client#to_query_string(**kargs, &block)`: builds the full GraphQL query document from the |
There was a problem hiding this comment.
Make it a 1-liner. Add to README if you need something longer.
There was a problem hiding this comment.
sure, will work on this asap
…cop to ~> 1.0 Removed two inline disable comments (Metrics/ClassLength on Query, Lint/HandleExceptions in spec_helper) in favour of the rubocop_todo.yml approach suggested by upstream maintainer. Bumped rubocop dev dependency from pinned 0.56.0 to ~> 1.0 (required for Ruby 3.2 compatibility — Psych.safe_load signature changed). TargetRubyVersion kept at 2.3 to reflect the gem's minimum supported Ruby. Ran rubocop -a (11 auto-corrections) then --auto-gen-config to regenerate .rubocop_todo.yml with current cop names (Metrics/LineLength renamed to Layout/LineLength, Style/MethodMissingSuper removed, etc.).
Danger ReportNo issues found. |
dblock
left a comment
There was a problem hiding this comment.
This looks great! Can we lock rubocop to a specific version in a future PR please, see my comment.
| module Graphlient::Client::Spec | ||
| # schema_path avoids an HTTP schema fetch at file-load time. | ||
| # No Rack adapter needed here; parsing is local and execution is handled | ||
| # by the stub_request below. |
| .rvmrc | ||
|
|
||
| # AI agent instructions — local only, not for the upstream repo | ||
| CLAUDE.md |
There was a problem hiding this comment.
We should commit an AGENTS.md!
| group :development do | ||
| gem 'byebug', platform: :ruby | ||
| gem 'rubocop', '0.56.0' | ||
| gem 'rubocop', '~> 1.0' |
There was a problem hiding this comment.
Since this is a gem and we don't check in Gemfile.lock, this should be locked to a specific version. Otherwise future CI runs will keep breaking without changes.
…#116) * feat: add Client#to_query_string and Query#spread - Client#to_query_string(**kargs, &block): builds full query document from DSL block and returns a String without touching Faraday or HTTP. - Query#spread(fragment_name): emits ...FragmentName in the query string, replacing the deprecated ___Const graphql-client convention. - spec_helper.rb: rescue LoadError on byebug require (Windows Ruby 3.2). - CLAUDE.md: AI agent instructions for this fork. * docs: move changelog entries to (Next) section, remove org-name header * fix: resolve CI failures — schema_path + rubocop offenses static_client_query_spec: add schema_path: 'spec/support/fixtures/invoice_api.json' The client was fetching the schema from the live URL at module load time (before WebMock is active), causing 403 in CI. Using a local schema file avoids the network call and matches the pattern in webmock_client_query_spec. client_dsl_spec: nodes { } → nodes do end (Style/BlockDelimiters) query.rb: add rubocop:disable Metrics/ClassLength Class grew past 100 lines after DSL additions (spread, fragment resolution). spec_helper.rb: add rubocop:disable Lint/HandleExceptions on rescue LoadError The empty rescue is intentional — byebug unavailable on some platforms. Note: faraday_adapter_spec.rb:147 fails locally on Windows (TCP error message format differs from Linux) but passes in CI on ubuntu-latest. Pre-existing. * fix: make TCP error spec cross-platform (Windows/Linux) On Linux, Errno::ECONNREFUSED.new(msg).message prepends 'Connection refused - '. On Windows, the OS prepends a different string. Use a regex that matches the core error_message content rather than the exact platform-specific prefix. * chore: gitignore .vscode/ and .idea/ editor directories * wip: directive DSL spec — pending implementation Adds a spec for directive DSL syntax: feeInCents _skip(if: :skip_fee) This should translate to @Skip(if: \) in the GraphQL query. Implementation deferred — tracked in this branch (directives-dsl-support). Base: feature/dsl-mode-only-option. * docs+spec: spread/fragment/directive README + mark directive spec pending README.md: 'Fragment Spreads in the DSL' — documents spread :Name DSL method (already implemented in feature/dsl-mode-only-option base) 'Fragment Spreads — inline definition' — shows the proposed fragment DSL (inline fragment(:Name, on: :Type) block, scoped to query call) marked 'coming in a future release' 'Directives in the DSL' — shows _skip(if:) → @Skip(if: \) proposal marked 'coming in a future release', anchored to this branch spec/graphlient/client_query_spec.rb: Directive test marked pending with explanation — shows intended behaviour without causing a CI failure while the implementation is outstanding. 74 examples, 0 failures, 1 pending. * feat: directive DSL, inline fragments, fragment definitions, custom scalars Query refactored into Query (thin shell) + Query::Serializer (composed): query/directive.rb Directive value object — returned by _name(args), placed AFTER field name in output (correct order) query/serializer.rb Main composition; includes all concern modules query/serializer/scalars.rb SCALAR_TYPES extraction + Serializer.scalar(:sym, Type) class-level custom scalar registration query/serializer/arguments.rb hash_arg/args_str/variable_string; field_args/directive_args split; variable_string bug fixed (lambda case) query/serializer/evaluator.rb evaluate() via instance_eval query/serializer/fragments.rb ___Const triple-underscore resolution query/serializer/directives.rb directive?() predicate; method_missing returns Directive Removed: query/transcript.rb (empty stub — deleted) New DSL surface: _skip(if: :x) → @Skip(if: $x) (any _name directive) _include(if: :x) → @include(if: $x) feeInCents _skip(if: :x) → feeInCents @Skip(if: $x) spread :F, _skip(if: :x) → ...F @Skip(if: $x) on(:Type) { fields } → ... on Type { fields } on(:Type, _skip(if: :x)) { } → ... on Type @Skip(if: $x) { } fragment(:F, on: :T) { } → fragment F on T { } (appended after main query) Custom scalar registration (client config block): client = Graphlient::Client.new(url) { |c| c.scalar :date, 'Date' } query(created_after: :date) { } → query($created_after: Date) Specs: query_dsl_spec.rb — 16 examples covering all new features client_query_spec.rb — directive test enabled (was pending, now live) 90 examples total, 0 failures * docs+specs: update README + add client-level DSL coverage README.md: Fragment Spreads and Definitions — remove 'proposed/coming' language, document fragment(:Name, on: :Type) as shipped, add spread+directive example Inline Fragments — new section: on(:Type) { } and on(:Type, _skip) { } Directives in the DSL — rewrite from 'coming soon' to full reference: on a field, on a spread, on an inline fragment, multiple, no-arg Custom Scalar Types — new section: c.scalar :date, 'Date' client config Table of contents updated to match all new sections spec/graphlient/client_dsl_spec.rb: to_query_string with directive — verifies @Skip in output to_query_string with inline fragment — verifies ... on Type to_query_string with fragment definition — verifies assembled output scalar registration via client block — verifies c.scalar :date, 'Date' 94 examples, 0 failures * chore: untrack CLAUDE.md + add to .gitignore AI agent instructions are local-only and not relevant to the upstream repo. CLAUDE.md is removed from git tracking but kept in the working directory. Added to .gitignore so future sessions don't accidentally commit it. * fix: use WebMock to_rack for Ruby 3.1 CI compatibility Root cause: on Ruby 3.1, WebMock's Faraday middleware intercepts requests before they reach Faraday::Adapter::Rack, returning 403 for any URL without a stub. This caused 25 CI failures on that Ruby version. spec/support/context/dummy_client.rb: Replace Faraday::Adapter::Rack with stub_request.to_rack(app). WebMock routes both schema introspection and query execution through the Sinatra dummy app at the WebMock level (before Faraday dispatch). Removed schema_path: so graphql-client gets the full live schema (including test fields: executionErrorInvoice, partialSuccess etc.). spec/graphlient/static_client_query_spec.rb: Same to_rack approach for execution tests (schema_path kept here since the module constants are created at file-load time, before any before block). Removed Faraday::Adapter::Rack from the static client constructor. spec/graphlient/client_schema_spec.rb: Use include instead of eq for error message — Faraday 2.x appends the URL to the message; include matches on both old and new formats. 73 examples, 0 failures * fix: rubocop AsciiComments + Ruby 3.4 mutex_m + add 3.4 to CI matrix spec/support/context/dummy_client.rb: spec/graphlient/static_client_query_spec.rb: Replace em-dashes (--) with ASCII semicolons/colons in comments. Style/AsciiComments cop requires pure ASCII. Gemfile: Add mutex_m gem -- activesupport 5.x requires mutex_m which was removed from Ruby's standard library in Ruby 3.4. Adding it explicitly silences the warning and fixes the LoadError on Ruby 3.4. .github/workflows/ci.yml: Add Ruby 3.4 to the required CI matrix (was previously only in ruby-head which is allowed to fail). Ruby 3.4 is now released and should be tested. * docs: document to_query_string and spread in README New sections: 'Build Query Strings without Validation' -- explains the serialization-only path (no schema load, no graphql-client validation, no HTTP), shows the basic usage, clarifies when the string can be fed back to client.execute (fragment-free queries only), explains why fragment spreads cannot go back through the gem (graphql-client's constant-based fragment model), and shows how to_query_string is the escape hatch for replacing graphql-client with your own HTTP transport entirely. 'Fragment Spreads' -- documents Query#spread as the modern alternative to the triple-underscore convention; covers basic usage and multiple spreads. * docs: update CHANGELOG for directives-dsl-support Adds entries for: directive DSL (_name convention), on(:Type) inline fragments, fragment(:Name, on:) definitions, custom scalar registration, Query::Serializer refactor, Ruby 3.1/3.4 CI fixes. * docs: fix CHANGELOG continuation line indentation (4 spaces) * docs: fix CHANGELOG continuation line indentation (4 spaces) * docs: add PR #115 reference to CHANGELOG entries * docs: clean up CHANGELOG for PR #116 - All entries single-line matching file convention (no continuation lines) - #115 entries restored to their original order and not modified - #116 prefix added to all directives/fragments/scalars/CI entries * don't change changelog * fix: close unclosed code block in Directives section of README * style: fix rubocop 0.56 offenses on feature/directives-dsl-support * chore: replace inline rubocop:disable with auto-gen-config, bump rubocop to ~> 1.0 Removed two inline disable comments (Metrics/ClassLength on Query, Lint/HandleExceptions in spec_helper) in favour of the rubocop_todo.yml approach suggested by upstream maintainer. Bumped rubocop dev dependency from pinned 0.56.0 to ~> 1.0 (required for Ruby 3.2 compatibility — Psych.safe_load signature changed). TargetRubyVersion kept at 2.3 to reflect the gem's minimum supported Ruby. Ran rubocop -a (11 auto-corrections) then --auto-gen-config to regenerate .rubocop_todo.yml with current cop names (Metrics/LineLength renamed to Layout/LineLength, Style/MethodMissingSuper removed, etc.). * Update CHANGELOG.md * correct order in change kig * fix: rubocop offenses after merge of feature/dsl-mode-only-option * chore: pin rubocop to 1.87.0 (fixed version, no pessimistic constraint) * docs: clarify inline vs external-constant fragment field access * feat: pass allow_named_fragment_access through to graphql-client * revert: remove allow_named_fragment_access passthrough (flag removed from graphql-client) * refactor: unify inline fragments under spread(on:) per #116 review Addresses @dblock's review question on #116 — use a single, consistent verb instead of a separate `on`. `spread` now covers both GraphQL forms: spread :InvoiceFields # ...InvoiceFields (named fragment spread) spread on: :PaidInvoice { ... } # ... on PaidInvoice { ... } (inline fragment) spread on: :Invoice # ... on Invoice (bare type condition) The `on:` keyword matches `fragment(name, on:)`, keeping the DSL coherent and leaving room for a future `spread(:X).skip(...)` chaining form without a breaking change. The standalone `on` DSL method is removed (introduced in #116, never released, so no compatibility impact). Guards: `spread` with neither a name nor `on:` raises; a named spread given a block raises (named spreads have no selection set). Updated specs, README, and CHANGELOG accordingly. * test: require ostruct + mutex_m before loading graphlient On newer Ruby (3.4+) ostruct and mutex_m are no longer auto-available, so the test-group deps that rely on them fail to load: graphql 1.13's compatibility specs reference OpenStruct (NameError at require time), and activesupport 5.x needs mutex_m. Both gems are already in the test Gemfile group; require them in spec_helper before graphlient is loaded transitively. Test-only — not added to the library, which must not depend on test gems. * style: satisfy rubocop in unified spread - freeze the SPREAD_* message constants (Style/MutableConstant) - extract append_inline_fragment / append_named_spread helpers so #spread is below the Metrics/PerceivedComplexity threshold
Add DSL-only mode:
to_query_string+spreadAddresses enhancement proposal: Decouple DSL from GraphQL::Client logic
What
Two new methods for building GraphQL query strings from the DSL without triggering
schema loading, HTTP, or graphql-client validation.
Client#to_query_string(&block)— serializes a DSL block to aStringandreturns it. The string can be fed back to
client.executefor fragment-free queries,or sent with your own HTTP client to replace graphql-client entirely.
Query#spread(fragment_name)— emits...FragmentNamein the query string.Cleaner alternative to the
___Consttriple-underscore convention.Why
Framework layers that need to inspect, log, or augment the query string before
sending (e.g. appending fragment definitions, custom connection pooling) need the
serialized string before graphql-client's validation runs. This also opens a fully
external HTTP path.
Changes
lib/graphlient/client.rb—to_query_stringlib/graphlient/query.rb—spreadspec/graphlient/client_dsl_spec.rb— new spec file (7 examples)README.md— Build Query Strings without Validation + Fragment Spreads sectionsCI fixes included
stub_request.to_rackreplacesFaraday::Adapter::Rackfor cross-Ruby-version reliabilitymutex_madded to Gemfile (removed from stdlib in 3.4).gitignoreupdated for.vscode/,.idea/Checklist