Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GraphQL::Current, test with ActiveRecord::QueryLogs #5034

Merged
merged 8 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add GraphQL::Current, test with ActiveRecord::QueryLogs
  • Loading branch information
rmosolgo committed Jul 20, 2024
commit e405cf3c8e2dc408b7b4bde8ac44ddc375f77ad4
1 change: 1 addition & 0 deletions lib/graphql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ class << self
require "graphql/unauthorized_field_error"
require "graphql/load_application_object_failed_error"
require "graphql/testing"
require "graphql/current"
17 changes: 17 additions & 0 deletions lib/graphql/current.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module GraphQL
module Current
def self.operation_name
Thread.current[:__graphql_runtime_info]&.keys&.first&.selected_operation_name
end

def self.field_path
Thread.current[:__graphql_runtime_info]&.values&.first&.current_field&.path
end

def self.dataloader_source_class
Fiber[:__graphql_current_dataloader_source]&.class
end
end
end
5 changes: 4 additions & 1 deletion lib/graphql/dataloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,10 @@ def spawn_source_fiber

if pending_sources
spawn_fiber do
pending_sources.each(&:run_pending_keys)
pending_sources.each do |source|
Fiber[:__graphql_current_dataloader_source] = source
source.run_pending_keys
end
end
end
end
Expand Down
119 changes: 119 additions & 0 deletions spec/integration/rails/query_logs_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
require "spec_helper"

describe "Integration with ActiveRecord::QueryLogs" do
ActiveRecord::Schema.define do
create_table :things, force: true do |t|
t.string :name
t.integer :other_thing_id
end
end

class QueryLogSchema < GraphQL::Schema
class Thing < ActiveRecord::Base
belongs_to :other_thing, class_name: "Thing"
end

class ThingSource < GraphQL::Dataloader::Source
def fetch(ids)
things = Thing.where(id: ids)
ids.map { |id| things.find { |t| t.id == id } }
end
end

class OtherThingSource < GraphQL::Dataloader::Source
def fetch(ids)
things = dataloader.with(ThingSource).load_all(ids).compact
ot_ids = things.map(&:other_thing_id).compact
ots = Thing.where(id: ot_ids).compact
ids.map { |tid|
if (thing = things.find { |t| t.id == tid })
ots.find { |ot| ot.id == thing.other_thing_id }
end
}
end
end

Thing1 = Thing.create!(name: "Fork")
Thing2 = Thing.create!(name: "Spoon", other_thing: Thing1)
Thing3 = Thing.create!(name: "Knife")

class ThingType < GraphQL::Schema::Object
field :name, String
field :other_thing, self
end

class Query < GraphQL::Schema::Object
field :some_thing, ThingType

def some_thing
Thing.find(Thing2.id)
end

field :thing, ThingType do
argument :id, ID
end

def thing(id:)
dataloader.with(ThingSource).load(id.to_i)
end

field :other_thing, ThingType do
argument :thing_id, ID
end

def other_thing(thing_id:)
dataloader.with(OtherThingSource).load(thing_id.to_i)
end
end

query(Query)
use GraphQL::Dataloader
end
before do
@prev_tags = ActiveRecord::QueryLogs.tags
ActiveRecord.query_transformers << ActiveRecord::QueryLogs
ActiveRecord::QueryLogs.tags = [{
current_graphql_operation: -> { GraphQL::Current.operation_name },
current_graphql_field: -> { GraphQL::Current.field_path },
current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
}]
end

after do
ActiveRecord::QueryLogs.tags = @prev_tags
ActiveRecord.query_transformers.delete(ActiveRecord::QueryLogs)
end

def exec_query(str)
QueryLogSchema.execute(str)
end

it "includes operation_name and field_name when configured" do
res = nil
log = with_active_record_log(colorize: false) do
res = exec_query("query OtherThingName { someThing { otherThing { name } } }")
end
assert_equal "Fork", res["data"]["someThing"]["otherThing"]["name"]
assert_includes log, "/*current_graphql_operation:OtherThingName,current_graphql_field:Query.someThing*/"
assert_includes log, "/*current_graphql_operation:OtherThingName,current_graphql_field:Thing.otherThing*/"
end

it "includes dataloader source when configured" do
res = nil
log = with_active_record_log(colorize: false) do
res = exec_query("query GetThingNames { t1: thing(id: 1) { name } t2: thing(id: 2) { name } }")
end
assert_equal ["Fork", "Spoon"], [res["data"]["t1"]["name"], res["data"]["t2"]["name"]]
assert_includes log, 'SELECT "things".* FROM "things" WHERE "things"."id" IN (?, ?) /*current_dataloader_source:QueryLogSchema::ThingSource*/'
end

it "works for nested dataloader sources" do
res = nil
log = with_active_record_log(colorize: false) do
res = exec_query("{ t1: otherThing(thingId: 1) { name } t2: otherThing(thingId: 2) { name } t5: otherThing(thingId: 5) { name } }")
end
assert_equal [nil, "Fork", nil], [res.dig("data", "t1", "name"), res.dig("data", "t2", "name"), res.dig("data", "t5")]
assert_includes log, 'SELECT "things".* FROM "things" WHERE "things"."id" IN (?, ?, ?) /*current_dataloader_source:QueryLogSchema::ThingSource*/'
assert_includes log, 'QueryLogSchema::Thing Load (0.1ms) SELECT "things".* FROM "things" WHERE "things"."id" = ? /*current_dataloader_source:QueryLogSchema::OtherThingSource*/'
end
end
8 changes: 6 additions & 2 deletions spec/integration/rails/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
require_relative "generators/base_generator_test"
require_relative "data"

def with_active_record_log
def with_active_record_log(colorize: true)
io = StringIO.new
prev_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = Logger.new(io)
yield
io.string
str = io.string
if !colorize
str.gsub!(/\e\[([;\d]+)?m/, '')
end
str
ensure
ActiveRecord::Base.logger = prev_logger
end
Expand Down
Loading