From 13a7b90048f264c7fc4d1ab41a99c9f58f7521cf Mon Sep 17 00:00:00 2001 From: Carlos Silva Date: Thu, 31 Jul 2025 01:25:25 -0300 Subject: [PATCH 1/2] Make sure associations still work when accessed within a non prepared scope --- lib/rails/graphql/request/strategy.rb | 17 +++++++++++------ .../graphql/source/active_record_source.rb | 6 ++++++ lib/rails/graphql/version.rb | 2 +- test/assets/sqlite.gql | 6 ++++++ test/integration/schemas/sqlite.rb | 12 ++++++++++++ test/integration/sqlite/star_wars_query_test.rb | 9 +++++++++ 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/lib/rails/graphql/request/strategy.rb b/lib/rails/graphql/request/strategy.rb index ed13ed0..f4da454 100644 --- a/lib/rails/graphql/request/strategy.rb +++ b/lib/rails/graphql/request/strategy.rb @@ -131,8 +131,8 @@ def resolve_data_for(field, args) return unless args.size.zero? if field.try(:dynamic_resolver?) - prepared = prepared_data_for(field) - args << Event.trigger(:resolve, field, self, prepared_data: prepared, &field.resolver) + extra = prepared_data_for(field) { |data| { prepared: data } } || EMPTY_HASH + args << Event.trigger(:resolve, field, self, **extra, &field.resolver) elsif field.prepared_data? args << prepared_data_for(field) else @@ -207,10 +207,15 @@ def safe_store_data(field, value = nil) # Get the prepared data for the given +field+, getting ready for # resolve, while ensuring to check prepared data on request def prepared_data_for(field) - return @data_pool[field] unless field.prepared_data? - - prepared = request.prepared_data_for(field).next - prepared unless prepared === PreparedData::NULL + if field.prepared_data? + prepared = request.prepared_data_for(field).next + prepared = nil if prepared === PreparedData::NULL + block_given? ? yield(prepared) : prepared + elsif !block_given? + @data_pool[field] + elsif @data_pool.key?(field) + yield(@data_pool[field]) + end end # Simply run the organize step for compilation diff --git a/lib/rails/graphql/source/active_record_source.rb b/lib/rails/graphql/source/active_record_source.rb index f3dd171..ef7f428 100644 --- a/lib/rails/graphql/source/active_record_source.rb +++ b/lib/rails/graphql/source/active_record_source.rb @@ -256,6 +256,12 @@ def build_association_scope(association) # Once the records are pre-loaded due to +preload_association+, use the # parent value and the preloader result to get the records def parent_owned_records(collection_result = false) + # The absence of the prepared data key means we got to a point that we + # don't know the result of the association, so we simply call it + unless event.data.key?(:prepared_data) + return current_value.public_send(field.method_name) + end + data = event.data[:prepared_data] return collection_result ? [] : nil unless data diff --git a/lib/rails/graphql/version.rb b/lib/rails/graphql/version.rb index ed8ecd8..a757a5f 100644 --- a/lib/rails/graphql/version.rb +++ b/lib/rails/graphql/version.rb @@ -14,7 +14,7 @@ def self.version module VERSION MAJOR = 1 MINOR = 0 - TINY = 5 + TINY = 6 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') diff --git a/test/assets/sqlite.gql b/test/assets/sqlite.gql index cd60988..9f8a7e9 100644 --- a/test/assets/sqlite.gql +++ b/test/assets/sqlite.gql @@ -226,6 +226,10 @@ type LiteShip { name: String } +type Sample { + faction: LiteFaction! +} + type _Mutation { createLiteBase(liteBase: LiteBaseInput!): LiteBase! @@ -262,6 +266,8 @@ type _Query { liteShip(id: ID!): LiteShip! liteShips: [LiteShip!]! + + sample: Sample! } """ diff --git a/test/integration/schemas/sqlite.rb b/test/integration/schemas/sqlite.rb index 7a33c44..b7fadc7 100644 --- a/test/integration/schemas/sqlite.rb +++ b/test/integration/schemas/sqlite.rb @@ -95,4 +95,16 @@ def greeting end source LiteShip + + object 'Sample' do + field :faction, 'LiteFaction', null: false + end + + query_fields do + field :sample, 'Sample', null: false + end + + def sample + { faction: LiteFaction.last } + end end diff --git a/test/integration/sqlite/star_wars_query_test.rb b/test/integration/sqlite/star_wars_query_test.rb index 1eb7f6f..068e70d 100644 --- a/test/integration/sqlite/star_wars_query_test.rb +++ b/test/integration/sqlite/star_wars_query_test.rb @@ -88,4 +88,13 @@ def test_query_methods_precedence query EmpireFleet { liteFaction(id: "2") { greeting } } GQL end + + def test_nested_non_prepared_source + bases = named_list('Death Star', 'Shield Generator', 'Headquarters') + sample = { sample: { faction: { name: 'Galactic Empire', bases: bases } } } + + assert_result({ data: sample }, <<~GQL) + query SampleFaction { sample { faction { name bases { name } } } } + GQL + end end From be55a62cb0d654dba2829d1e3ee44235dbefd66b Mon Sep 17 00:00:00 2001 From: Carlos Silva Date: Mon, 25 Aug 2025 14:47:54 -0300 Subject: [PATCH 2/2] Improve non-prepared data code --- Gemfile.lock | 2 +- lib/rails/graphql/request/strategy.rb | 15 +++++++-------- test/integration/schemas/mysql.rb | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7963560..edef289 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rails-graphql (1.0.5) + rails-graphql (1.0.6) rails (>= 6.0) GEM diff --git a/lib/rails/graphql/request/strategy.rb b/lib/rails/graphql/request/strategy.rb index f4da454..556e8ce 100644 --- a/lib/rails/graphql/request/strategy.rb +++ b/lib/rails/graphql/request/strategy.rb @@ -131,7 +131,8 @@ def resolve_data_for(field, args) return unless args.size.zero? if field.try(:dynamic_resolver?) - extra = prepared_data_for(field) { |data| { prepared: data } } || EMPTY_HASH + extra = prepared_data_for(field, with_null: true) + extra = extra === PreparedData::NULL ? EMPTY_HASH : { prepared: extra } args << Event.trigger(:resolve, field, self, **extra, &field.resolver) elsif field.prepared_data? args << prepared_data_for(field) @@ -206,15 +207,13 @@ def safe_store_data(field, value = nil) # Get the prepared data for the given +field+, getting ready for # resolve, while ensuring to check prepared data on request - def prepared_data_for(field) + def prepared_data_for(field, with_null: false) if field.prepared_data? - prepared = request.prepared_data_for(field).next - prepared = nil if prepared === PreparedData::NULL - block_given? ? yield(prepared) : prepared - elsif !block_given? - @data_pool[field] + request.prepared_data_for(field).next elsif @data_pool.key?(field) - yield(@data_pool[field]) + @data_pool[field] + elsif with_null + PreparedData::NULL end end diff --git a/test/integration/schemas/mysql.rb b/test/integration/schemas/mysql.rb index 3dca27e..4bc4917 100644 --- a/test/integration/schemas/mysql.rb +++ b/test/integration/schemas/mysql.rb @@ -6,7 +6,7 @@ class MySQLRecord < ActiveRecord::Base establish_connection( name: 'mysql', adapter: 'mysql2', - host: ENV.fetch('GQL_MYSQL_HOST', 'localhost'), + host: ENV.fetch('GQL_MYSQL_HOST', '127.0.0.1'), database: ENV.fetch('GQL_MYSQL_DATABASE', 'starwars'), username: ENV.fetch('GQL_MYSQL_USERNAME', 'root'), password: ENV['GQL_MYSQL_PASSWORD'],