Skip to content

Commit

Permalink
add graphql types and a test
Browse files Browse the repository at this point in the history
i expected the test to generate SQL queries like:
  User Load (0.1ms)  SELECT "users".* FROM "users" LIMIT ?  [["LIMIT", 2]]
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (?, ?)  [["user_id", 1], ["user_id", 2]]
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ?  [["user_id", 1]]
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ?  [["user_id", 2]]

which would reproduce the bug in Shopify/graphql-batch#114, but instead i got:
  User Load (0.1ms)  SELECT "users".* FROM "users" LIMIT ?  [["LIMIT", 2]]
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (?, ?)  [["user_id", 1], ["user_id", 2]]

which is correct. going to try downgrading versions / changing the association loader to see why i can't reproduce in this test repo
  • Loading branch information
anujbiyani committed Dec 2, 2020
1 parent d2c0084 commit 3241113
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 7 deletions.
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ GEM
globalid (0.4.2)
activesupport (>= 4.2.0)
graphql (1.11.6)
graphql-batch (0.3.9)
graphql (>= 0.8, < 2)
graphql-batch (0.4.3)
graphql (>= 1.3, < 2)
promise.rb (~> 0.7.2)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
Expand Down
48 changes: 48 additions & 0 deletions app/graphql/association_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class AssociationLoader < GraphQL::Batch::Loader
def self.validate(model, association_name)
new(model, association_name)
nil
end

def initialize(model, association_name)
@model = model
@association_name = association_name
validate
end

def load(record)
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
return Promise.resolve(read_association(record)) if association_loaded?(record)
super
end

# We want to load the associations on all records, even if they have the same id
def cache_key(record)
record.object_id
end

def perform(records)
preload_association(records)
records.each { |record| fulfill(record, read_association(record)) }
end

private

def validate
unless @model.reflect_on_association(@association_name)
raise ArgumentError, "No association #{@association_name} on #{@model}"
end
end

def preload_association(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
end

def read_association(record)
record.public_send(@association_name)
end

def association_loaded?(record)
record.association(@association_name).loaded?
end
end
25 changes: 25 additions & 0 deletions app/graphql/record_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class RecordLoader < GraphQL::Batch::Loader
def initialize(model, column: model.primary_key, where: nil)
@model = model
@column = column.to_s
@column_type = model.type_for_attribute(@column)
@where = where
end

def load(key)
super(@column_type.cast(key))
end

def perform(keys)
query(keys).each { |record| fulfill(record.public_send(@column), record) }
keys.each { |key| fulfill(key, nil) unless fulfilled?(key) }
end

private

def query(keys)
scope = @model
scope = scope.where(@where) if @where
scope.where(@column => keys)
end
end
7 changes: 7 additions & 0 deletions app/graphql/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'graphql/batch'

class Schema < GraphQL::Schema
query Types::QueryType

use GraphQL::Batch
end
12 changes: 12 additions & 0 deletions app/graphql/types/post_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false

field :user, Types::UserType, null: true
def user
AssociationLoader.for(Post, :user).load(object)
end
end
end
8 changes: 3 additions & 5 deletions app/graphql/types/query_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ class QueryType < Types::BaseObject
# Add root-level fields here.
# They will be entry points for queries on your schema.

# TODO: remove me
field :test_field, String, null: false,
description: "An example field added by the generator"
def test_field
"Hello World!"
field :users, Types::UserType.connection_type, null: false
def users
User.all
end
end
end
13 changes: 13 additions & 0 deletions app/graphql/types/user_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false

field :posts, Types::PostType.connection_type, null: true
def posts
# object.posts
AssociationLoader.for(User, :posts).load(object)
end
end
end
30 changes: 30 additions & 0 deletions spec/graphql/query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'rails_helper'

RSpec.describe 'Query tests' do
def execute_query(query)
Schema.execute(
query,
variables: {},
context: {},
).to_h.with_indifferent_access
end

it 'something' do
FactoryBot.create(:post)
FactoryBot.create(:post)

query = <<~GRAPHQL
query {
users(first: 2) {
nodes {
posts {
nodes { id }
}
}
}
}
GRAPHQL

p execute_query(query)
end
end

0 comments on commit 3241113

Please sign in to comment.