Skip to content

Schema#multiplex causes context.namespace(:interpreter)[:current_path] to be nil inside then block #3397

Closed
@DmitryTsepelev

Description

@DmitryTsepelev

Describe the bug

I found a case when context.namespace(:interpreter)[:current_path] is nil (to be precise, most of the keys inside the :interpreter hash are missing). The problem appears when Schema#multiplex is called and I try to get the current path inside .then block in promise, i.e.:

class Tweet < GraphQL::Schema::Object
    field :content, String, null: false
    field :author, Types::User, null: false

    def author
      puts "Tweet:author current_path=#{context.namespace(:interpreter)[:current_path]}"

      Batch::RecordLoader.for(::User).load(object.author_id).then do |author|
        puts "Tweet:author after batch current_path=#{context.namespace(:interpreter)[:current_path]}"
        author
      end
    end
  end

However, it works fine when Schema#execute is used.

Not sure if I should go to the graphql-batch repo with this issue.

Versions

graphql version: latest
graphql-batch version: latest

Steps to reproduce

A whole example:

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  gem "rails", "6.0.3"
  gem "pg"
  gem "graphql", "~> 1.12"
  gem "rspec-rails", "~> 4.0.1"
  gem "db-query-matchers"
  gem "ar_lazy_preload"
  gem "graphql-batch"
end

require "active_record"

class App < Rails::Application
  config.logger = Logger.new('/dev/null')
end

App.initialize!

ActiveRecord::Base.establish_connection(adapter: "postgresql", database: "nplusonedb")

ActiveRecord::Schema.define do
  enable_extension "plpgsql"

  create_table "users", force: :cascade do |t|
    t.string "nickname", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "tweets", force: :cascade do |t|
    t.text "content", null: false
    t.bigint "author_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "tweets", "users", column: "author_id"
end

class User < ActiveRecord::Base
  has_many :tweets, foreign_key: :author_id
end

class Tweet < ActiveRecord::Base
  belongs_to :author, class_name: "User"
end

module Batch
  class RecordLoader < GraphQL::Batch::Loader
    def initialize(model)
      @model = model
    end

    def perform(ids)
      @model.where(id: ids).each { |record| fulfill(record.id, record) }
      ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
    end
  end
end

module Types
  class User < GraphQL::Schema::Object
    field :nickname, String, null: false
  end

  class Tweet < GraphQL::Schema::Object
    field :content, String, null: false
    field :author, Types::User, null: false

    def author
      puts "Tweet:author current_path=#{context.namespace(:interpreter)[:current_path]}"

      Batch::RecordLoader.for(::User).load(object.author_id).then do |author|
        puts "Tweet:author after batch current_path=#{context.namespace(:interpreter)[:current_path]}"
        author
      end
    end
  end
end

class FeedResolver < GraphQL::Schema::Resolver
  type [Types::Tweet], null: false

  def resolve(limit: nil, offset: nil)
    Tweet.limit(limit).offset(offset)
  end
end

module Types
  class Query < GraphQL::Schema::Object
    field :feed, [Types::Tweet], null: false, resolver: FeedResolver do
      argument :limit, Integer, required: false
      argument :offset, Integer, required: false
    end
  end
end

class GraphqlSchema < GraphQL::Schema
  query Types::Query
  use GraphQL::Batch
end

Tweet.delete_all
User.delete_all

john = User.create(nickname: "John")
max = User.create(nickname: "Max")

john.tweets.create(content: "Hi!", created_at: Time.new(2020, 6, 12, 10))
max.tweets.create(content: "Hello!", created_at: Time.new(2020, 6, 13, 10))
john.tweets.create(content: "My second tweet is here", created_at: Time.new(2020, 6, 15, 10))
max.tweets.create(content: "The weather is nice", created_at: Time.new(2020, 6, 17, 10))

puts "\nSchema#multiplex\n"

query1 = <<~GQL
  query {
    feed(offset: 0, limit: 2) {
      content
      author {
        nickname
      }
    }
  }
GQL

query2 = <<~GQL
  query {
    feed(offset: 2, limit: 2) {
      content
      author {
        nickname
      }
    }
  }
GQL

puts GraphqlSchema.multiplex([{ query: query1 }, { query: query2 }]).map(&:to_h).inspect

puts "\nSchema#execute\n"

puts GraphqlSchema.execute(query1).to_h.inspect

Expected behavior

I want to be able to get the current path inside the promise 🙂

Actual behavior

Current path is available only inside first promise block. Here is what I see in the console:

Schema#multiplex
Tweet:author current_path=["feed", 0, "author"]
Tweet:author current_path=["feed", 1, "author"]
Tweet:author current_path=["feed", 0, "author"]
Tweet:author current_path=["feed", 1, "author"]
Tweet:author after batch current_path=["feed", 0, "author"]
Tweet:author after batch current_path=
Tweet:author after batch current_path=["feed", 0, "author"]
Tweet:author after batch current_path=
[{"data"=>{"feed"=>[{"content"=>"Hi!", "author"=>{"nickname"=>"John"}}, {"content"=>"Hello!", "author"=>{"nickname"=>"Max"}}]}}, {"data"=>{"feed"=>[{"content"=>"My second tweet is here", "author"=>{"nickname"=>"John"}}, {"content"=>"The weather is nice", "author"=>{"nickname"=>"Max"}}]}}]

Schema#execute
Tweet:author current_path=["feed", 0, "author"]
Tweet:author current_path=["feed", 1, "author"]
Tweet:author after batch current_path=["feed", 0, "author"]
Tweet:author after batch current_path=["feed", 0, "author"]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions