Skip to content

Commit

Permalink
Refactor: move where datastore queries get built.
Browse files Browse the repository at this point in the history
Rather than building them in `GraphQLAdapter` when a resolver yields,
build them in the resolver that need it.

This paves the way for a larger refactoring I'm working on.
  • Loading branch information
myronmarston committed Feb 8, 2025
1 parent 5b514d4 commit faa4890
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 27 deletions.
6 changes: 4 additions & 2 deletions elasticgraph-graphql/lib/elastic_graph/graphql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ def schema
require "elastic_graph/graphql/resolvers/graphql_adapter"
Resolvers::GraphQLAdapter.new(
schema: schema,
query_adapter: resolver_query_adapter,
runtime_metadata: runtime_metadata,
resolvers: graphql_resolvers
)
Expand Down Expand Up @@ -171,11 +170,14 @@ def graphql_resolvers
require "elastic_graph/graphql/resolvers/nested_relationships"

nested_relationships = Resolvers::NestedRelationships.new(
resolver_query_adapter: resolver_query_adapter,
schema_element_names: runtime_metadata.schema_element_names,
logger: logger
)

list_records = Resolvers::ListRecords.new
list_records = Resolvers::ListRecords.new(
resolver_query_adapter: resolver_query_adapter
)

get_record_field_value = Resolvers::GetRecordFieldValue.new(
schema_element_names: runtime_metadata.schema_element_names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ module Resolvers
# our resolvers. Responsible for routing a resolution request to the appropriate
# resolver.
class GraphQLAdapter
def initialize(schema:, query_adapter:, runtime_metadata:, resolvers:)
def initialize(schema:, runtime_metadata:, resolvers:)
@schema = schema
@query_adapter = query_adapter
@resolvers = resolvers

scalar_types_by_name = runtime_metadata.scalar_types_by_name
Expand Down Expand Up @@ -56,9 +55,7 @@ def call(parent_type, field, object, args, context)
ERROR
end

result = resolver.resolve(field: schema_field, object: object, args: args, context: context, lookahead: lookahead) do
@query_adapter.build_query_from(field: schema_field, args: args, lookahead: lookahead, context: context)
end
result = resolver.resolve(field: schema_field, object: object, args: args, context: context, lookahead: lookahead)

# Give the field a chance to coerce the result before returning it. Initially, this is only used to deal with
# enum value overrides (e.g. so that if `DayOfWeek.MONDAY` has been overridden to `DayOfWeek.MON`, we can coerce
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ class GraphQL
module Resolvers
# Responsible for fetching a a list of records of a particular type
class ListRecords
def initialize(resolver_query_adapter:)
@resolver_query_adapter = resolver_query_adapter
end

def can_resolve?(field:, object:)
field.parent_type.name == :Query && field.type.collection?
end

def resolve(field:, context:, lookahead:, **)
query = yield
def resolve(field:, context:, lookahead:, args:, object:)
query = @resolver_query_adapter.build_query_from(field: field, args: args, lookahead: lookahead, context: context)
response = QuerySource.execute_one(query, for_context: context)
RelayConnection.maybe_wrap(response, field: field, context: context, lookahead: lookahead, query: query)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module Resolvers
#
# Most of the logic for this lives in ElasticGraph::Schema::RelationJoin.
class NestedRelationships
def initialize(schema_element_names:, logger:)
def initialize(resolver_query_adapter:, schema_element_names:, logger:)
@resolver_query_adapter = resolver_query_adapter
@schema_element_names = schema_element_names
@logger = logger
end
Expand All @@ -26,15 +27,17 @@ def can_resolve?(field:, object:)
!!field.relation_join
end

def resolve(object:, field:, context:, lookahead:, **)
def resolve(object:, field:, context:, lookahead:, args:)
log_warning = ->(**options) { log_field_problem_warning(field: field, **options) }
join = field.relation_join
id_or_ids = join.extract_id_or_ids_from(object, log_warning)
filters = [
build_filter(join.filter_id_field_name, nil, join.foreign_key_nested_paths, Array(id_or_ids)),
join.additional_filter
].reject(&:empty?)
query = yield.merge_with(filters: filters)
query = @resolver_query_adapter
.build_query_from(field: field, args: args, lookahead: lookahead, context: context)
.merge_with(filters: filters)

response =
case id_or_ids
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module ElasticGraph
class GraphQLAdapter
def initialize: (
schema: Schema,
query_adapter: Resolvers::QueryAdapter,
runtime_metadata: SchemaArtifacts::RuntimeMetadata::Schema,
resolvers: ::Array[Resolvers::_Resolver]
) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module ElasticGraph
module Resolvers
class ListRecords
include _Resolver

def initialize: (resolver_query_adapter: Resolvers::QueryAdapter) -> void
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module ElasticGraph
include _Resolver

def initialize: (
resolver_query_adapter: Resolvers::QueryAdapter,
schema_element_names: SchemaArtifacts::RuntimeMetadata::SchemaElementNames,
logger: ::Logger
) -> void
Expand Down
32 changes: 19 additions & 13 deletions elasticgraph-graphql/spec/support/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@
# frozen_string_literal: true

require "elastic_graph/graphql/query_details_tracker"
require "elastic_graph/graphql/resolvers/query_adapter"
require "elastic_graph/graphql/resolvers/query_source"
require "graphql"

module ResolverHelperMethods
def resolve(type_name, field_name, document = nil, **options)
def resolve(type_name, field_name, document = nil, lookahead: nil, query_overrides: {}, **options)
query_override_adapter.query_overrides = query_overrides
field = graphql.schema.field_named(type_name, field_name)
query_overrides = options.fetch(:query_overrides) { {} }
args = field.args_to_schema_form(options.except(:query_overrides, :lookahead))
lookahead = options[:lookahead] || GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
args = field.args_to_schema_form(options)
lookahead ||= GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
query_details_tracker = ElasticGraph::GraphQL::QueryDetailsTracker.empty

::GraphQL::Dataloader.with_dataloading do |dataloader|
Expand All @@ -33,24 +32,27 @@ def resolve(type_name, field_name, document = nil, **options)

expect(document).to satisfy { |doc| resolver.can_resolve?(field: field, object: doc) }

query = nil
query_builder = -> {
query ||= query_adapter.build_query_from(field: field, lookahead: lookahead, args: args, context: context).with(**query_overrides)
}

begin
# In the 2.1.0 release of the GraphQL gem, `GraphQL::Pagination::Connection#initialize` expects a particular thread local[^1].
# Here we initialize the thread local in a similar way to how the GraphQL gem does it[^2].
#
# [^1]: https://github.com/rmosolgo/graphql-ruby/blob/v2.1.0/lib/graphql/pagination/connection.rb#L94-L96
# [^2]: https://github.com/rmosolgo/graphql-ruby/blob/v2.1.0/lib/graphql/execution/interpreter/runtime.rb#L935-L941
::Thread.current[:__graphql_runtime_info] = ::Hash.new { |h, k| h[k] = ::GraphQL::Execution::Interpreter::Runtime::CurrentState.new }
resolver.resolve(field: field, object: document, context: context, args: args, lookahead: lookahead, &query_builder)
resolver.resolve(field: field, object: document, context: context, args: args, lookahead: lookahead)
ensure
::Thread.current[:__graphql_runtime_info] = nil
end
end
end

class QueryOverrideAdapter
attr_accessor :query_overrides

def call(query:, **)
query.merge_with(**query_overrides)
end
end
end

# Provides support for integration testing resolvers. It assumes:
Expand Down Expand Up @@ -80,6 +82,8 @@ def resolve(type_name, field_name, document = nil, **options)
deps[dep_name] =
if dep_name == :schema_element_names
graphql.runtime_metadata.schema_element_names
elsif dep_name == :resolver_query_adapter
resolver_query_adapter
else
graphql.public_send(dep_name)
end
Expand All @@ -88,10 +92,12 @@ def resolve(type_name, field_name, document = nil, **options)
described_class.new(**dependencies)
end

let(:query_adapter) do
let(:query_override_adapter) { ResolverHelperMethods::QueryOverrideAdapter.new }

let(:resolver_query_adapter) do
ElasticGraph::GraphQL::Resolvers::QueryAdapter.new(
datastore_query_builder: graphql.datastore_query_builder,
datastore_query_adapters: graphql.datastore_query_adapters
datastore_query_adapters: graphql.datastore_query_adapters + [query_override_adapter]
)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ module Resolvers
it "raises a clear error when no resolver can be found" do
adapter = GraphQLAdapter.new(
schema: schema,
query_adapter: graphql.resolver_query_adapter,
runtime_metadata: graphql.runtime_metadata,
resolvers: graphql.graphql_resolvers
)
Expand Down

0 comments on commit faa4890

Please sign in to comment.