Skip to content

Commit 11d11df

Browse files
committed
Switch to GraphQL's Dataloader
1 parent 3b63e30 commit 11d11df

File tree

9 files changed

+239
-230
lines changed

9 files changed

+239
-230
lines changed

Gemfile.lock

Lines changed: 160 additions & 137 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
# GraphQL::FancyLoader
22

3-
FancyLoader (built on top of the [graphql-batch][graphql-batch] gem) efficiently batches queries
3+
> [!NOTE]
4+
> This is a fork of the original [hummingbird-me/graphql-fancy-loader](https://github.com/hummingbird-me/graphql-fancy-loader) that has been migrated to use graphql-ruby's built-in dataloader instead of graphql-batch. If you need the original graphql-batch implementation, please use the upstream repository.
5+
6+
FancyLoader (built on top of graphql-ruby's dataloader) efficiently batches queries
47
using postgres window functions to allow advanced features such as orders, limits, pagination, and
58
authorization scoping. Built on top of Arel, FancyLoader is highly extensible and capable of
69
handling complex sorts (including sorting based on a join) with minimal effort and high performance.
710

811
We use FancyLoader in production to power large swaths of the Kitsu GraphQL API.
912

10-
[graphql-batch]: https://github.com/Shopify/graphql-batch
11-
1213
## Installation
1314

1415
Add this line to your application's Gemfile:
1516

1617
```ruby
17-
gem 'graphql-fancy_loader'
18+
gem 'graphql-fancy_loader', github: 'metafy-gg/graphql-fancy-loader'
1819
```
1920

2021
And then execute:
@@ -23,12 +24,6 @@ And then execute:
2324
$ bundle install
2425
```
2526

26-
Or install it yourself as:
27-
28-
```
29-
$ gem install graphql-fancy_loader
30-
```
31-
3227
## Basic Usage
3328

3429
### Defining a Loader
@@ -54,7 +49,7 @@ end
5449

5550
This loader class provides two primary methods: `.sort_argument` and `.connection_for`.
5651
`.sort_argument` gives you a convenient auto-generated sort field which allows for multiple sorts.
57-
`.connection_for` is a wrapper around graphql-batch which returns a connection. Pagination will be
52+
`.connection_for` is a wrapper around graphql-ruby's dataloader which returns a connection. Pagination will be
5853
automatically applied to this connection by the graphql gem, so you just need to pass in your other
5954
options:
6055

@@ -77,8 +72,7 @@ end
7772

7873
### Testing the Loader
7974

80-
Testing a FancyLoader is pretty much exactly the same as testing any other graphql-batch Loader
81-
class:
75+
Testing a FancyLoader works with graphql-ruby's dataloader pattern:
8276

8377
```ruby
8478
RSpec.describe Loaders::PostsLoader do
@@ -90,13 +84,11 @@ RSpec.describe Loaders::PostsLoader do
9084
let(:sort) { [{ on: :created_at, direction: :desc }] }
9185

9286
it 'loads all the posts for a user' do
93-
posts = GraphQL::Batch.batch do
94-
described_class.connection_for({
95-
find_by: :user_id,
96-
sort: sort,
97-
context: context
98-
}, user.id).nodes
99-
end
87+
posts = described_class.connection_for({
88+
find_by: :user_id,
89+
sort: sort,
90+
context: context
91+
}, user.id).nodes
10092

10193
expect(posts.count).to eq(user.posts.count)
10294
end
@@ -208,7 +200,7 @@ will create a git tag for the version, push git commits and tags, and push the `
208200
## Contributing
209201

210202
Bug reports and pull requests are welcome on GitHub at
211-
https://github.com/hummingbird-me/graphql-fancy-loader.
203+
https://github.com/metafy-gg/graphql-fancy-loader.
212204

213205
## License
214206

graphql-fancy_loader.gemspec

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,34 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
66
require 'graphql/fancy_loader/version'
77

88
Gem::Specification.new do |spec|
9-
spec.name = 'graphql-fancy_loader'
10-
spec.version = GraphQL::FancyLoader::VERSION
11-
spec.authors = ['Toyhammered', 'Emma Lejeck']
12-
spec.email = ['[email protected]']
9+
spec.name = 'graphql-fancy_loader'
10+
spec.version = GraphQL::FancyLoader::VERSION
11+
spec.authors = ['Toyhammered', 'Emma Lejeck', 'Thomas McNiven']
12+
1313

14-
spec.summary = 'FancyLoader efficiently batches queries using postgres window functions to allow advanced features such as orders, limits, pagination, and authorization scoping.'
15-
spec.description = 'FancyLoader (built on top of the graphql-batch gem) efficiently batches queries using postgres window functions to allow advanced features such as orders, limits, pagination, and authorization scoping. Built on top of Arel, FancyLoader is highly extensible and capable of handling complex sorts (including sorting based on a join) with minimal effort and high performance.'
16-
spec.homepage = 'https://github.com/hummingbird-me/graphql-fancy-loader'
17-
spec.license = 'Apache-2.0'
14+
spec.summary = 'FancyLoader efficiently batches queries using postgres window functions to allow advanced features such as orders, limits, pagination, and authorization scoping.'
15+
spec.description = "FancyLoader (built on top of graphql-ruby's dataloader) efficiently batches queries using postgres window functions to allow advanced features such as orders, limits, pagination, and authorization scoping. Built on top of Arel, FancyLoader is highly extensible and capable of handling complex sorts (including sorting based on a join) with minimal effort and high performance."
16+
spec.homepage = 'https://github.com/metafy-gg/graphql-fancy-loader'
17+
spec.license = 'Apache-2.0'
1818

1919
spec.metadata['homepage_uri'] = spec.homepage
20-
spec.metadata['source_code_uri'] = 'https://github.com/hummingbird-me/graphql-fancy-loader'
20+
spec.metadata['source_code_uri'] = 'https://github.com/metafy-gg/graphql-fancy-loader'
2121
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
2222

2323
# Specify which files should be added to the gem when it is released.
2424
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
2525
spec.files = Dir.chdir(File.expand_path(__dir__)) do
2626
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
2727
end
28-
spec.bindir = 'exe'
29-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28+
spec.bindir = 'exe'
29+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
3030
spec.require_paths = ['lib']
3131

3232
spec.add_runtime_dependency 'activesupport', '>= 5.0', '< 9.0'
33-
spec.add_runtime_dependency 'graphql', '>= 1.3', '< 3'
34-
spec.add_runtime_dependency 'graphql-batch', '>= 0.4.3', '< 1'
33+
spec.add_runtime_dependency 'graphql', '>= 1.8', '< 3'
3534

36-
spec.add_development_dependency 'bundler', '~> 2.0'
37-
spec.add_development_dependency 'pry-byebug', '~> 3.10'
35+
spec.add_development_dependency 'bundler', '>= 2.0'
36+
spec.add_development_dependency 'pry-byebug', '~> 3.11'
3837
spec.add_development_dependency 'rake', '>= 13.2'
3938
spec.add_development_dependency 'rspec', '~> 3.13'
4039
spec.add_development_dependency 'simplecov', '~> 0.22'
@@ -43,8 +42,8 @@ Gem::Specification.new do |spec|
4342
spec.add_development_dependency 'bootsnap', '~> 1.18'
4443
spec.add_development_dependency 'database_cleaner', '~> 2.1'
4544
spec.add_development_dependency 'factory_bot_rails', '~> 6.4'
46-
spec.add_development_dependency 'listen', '~> 3.9'
45+
spec.add_development_dependency 'listen', '>= 3.9'
4746
spec.add_development_dependency 'pg', '~> 1.5'
48-
spec.add_development_dependency 'rails', '8.0'
49-
spec.add_development_dependency 'rspec-rails', '~> 7.0'
47+
spec.add_development_dependency 'rails', '>= 8.0'
48+
spec.add_development_dependency 'rspec-rails', '>= 7.0'
5049
end

lib/graphql/fancy_connection.rb

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,64 +9,61 @@ def initialize(loader, args, key, **super_args)
99
super(nil, **super_args)
1010
end
1111

12-
# @return [Promise<Array<ApplicationRecord>>]
12+
# @return [Array<ApplicationRecord>]
1313
def nodes
14+
resolved_nodes = base_nodes
1415
if @then
15-
base_nodes.then(@then)
16+
@then.call(resolved_nodes)
1617
else
17-
base_nodes
18+
resolved_nodes
1819
end
1920
end
2021

2122
def edges
22-
@edges ||= nodes.then do |nodes|
23-
nodes.map { |n| @edge_class.new(n, self) }
23+
@edges ||= begin
24+
resolved_nodes = nodes
25+
resolved_nodes.map { |n| @edge_class.new(n, self) }
2426
end
2527
end
2628

27-
# @return [Promise<Integer>]
29+
# @return [Integer]
2830
def total_count
29-
base_nodes.then do |results|
30-
if results.first
31-
results.first.attributes['total_count']
32-
else
33-
0
34-
end
31+
results = base_nodes
32+
if results.first
33+
results.first.attributes['total_count']
34+
else
35+
0
3536
end
3637
end
3738

38-
# @return [Promise<Boolean>]
39+
# @return [Boolean]
3940
def has_next_page # rubocop:disable Naming/PredicateName
40-
base_nodes.then do |results|
41-
if results.last
42-
results.last.attributes['row_number'] < results.last.attributes['total_count']
43-
else
44-
false
45-
end
41+
results = base_nodes
42+
if results.last
43+
results.last.attributes['row_number'] < results.last.attributes['total_count']
44+
else
45+
false
4646
end
4747
end
4848

49-
# @return [Promise<Boolean>]
49+
# @return [Boolean]
5050
def has_previous_page # rubocop:disable Naming/PredicateName
51-
base_nodes.then do |results|
52-
if results.first
53-
results.first.attributes['row_number'] > 1
54-
else
55-
false
56-
end
51+
results = base_nodes
52+
if results.first
53+
results.first.attributes['row_number'] > 1
54+
else
55+
false
5756
end
5857
end
5958

6059
def start_cursor
61-
base_nodes.then do |results|
62-
cursor_for(results.first)
63-
end
60+
results = base_nodes
61+
cursor_for(results.first)
6462
end
6563

6664
def end_cursor
67-
base_nodes.then do |results|
68-
cursor_for(results.last)
69-
end
65+
results = base_nodes
66+
cursor_for(results.last)
7067
end
7168

7269
def cursor_for(item)
@@ -81,7 +78,7 @@ def then(&block)
8178
private
8279

8380
def base_nodes
84-
@base_nodes ||= @loader.for(**loader_args).load(@key)
81+
@base_nodes ||= context.dataloader.with(@loader, **loader_args).load(@key)
8582
end
8683

8784
def after_offset

lib/graphql/fancy_loader.rb

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
# To use +FancyLoader+, you'll make a subclass to define your sorts and source model. You can then
66
# create a field which uses your subclass to load data.
77

8-
require 'graphql/batch'
8+
require 'graphql/dataloader'
99
require 'active_support'
1010
require 'active_support/concern'
1111
require 'active_support/configurable'
1212
require 'active_support/core_ext/class/attribute'
1313

14+
require 'graphql/fancy_loader/version'
1415
require 'graphql/fancy_loader/dsl'
1516

1617
module GraphQL
17-
class FancyLoader < GraphQL::Batch::Loader
18+
class FancyLoader < GraphQL::Dataloader::Source
1819
include ActiveSupport::Configurable
19-
include GraphQL::FancyLoader::DSL
20+
include DSL
2021

2122
config_accessor :middleware
2223

@@ -61,8 +62,8 @@ def initialize(find_by:, sort:, before: nil, after: 0, first: nil, last: nil, wh
6162
end
6263

6364
# Perform the loading. Uses {GraphQL::FancyLoader::QueryGenerator} to build a query, then groups
64-
# the results by the @find_by column, then fulfills all the Promises.
65-
def perform(keys)
65+
# the results by the @find_by column, then returns the grouped results.
66+
def fetch(keys)
6667
query = QueryGenerator.new(
6768
model: model,
6869
find_by: @find_by,
@@ -78,9 +79,7 @@ def perform(keys)
7879
).query
7980

8081
results = query.to_a.group_by { |rec| rec[@find_by] }
81-
keys.each do |key|
82-
fulfill(key, results[key] || [])
83-
end
82+
keys.map { |key| results[key] || [] }
8483
end
8584

8685
private
@@ -100,6 +99,5 @@ def sort
10099
require 'graphql/fancy_loader/query_generator'
101100
require 'graphql/fancy_loader/rank_query_generator'
102101
require 'graphql/fancy_loader/type_generator'
103-
require 'graphql/fancy_loader/version'
104102
# Middleware
105103
require 'graphql/fancy_loader/pundit_middleware'

lib/graphql/fancy_loader/dsl.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module GraphQL
2-
class FancyLoader < GraphQL::Batch::Loader
2+
class FancyLoader
33
module DSL
44
extend ActiveSupport::Concern
55

lib/graphql/fancy_loader/version.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
module GraphQL
2-
# HACK: This allows us to import the version number in the gemspec
32
class FancyLoader < (begin
4-
require 'graphql/batch'
5-
GraphQL::Batch::Loader
3+
require 'graphql/dataloader'
4+
GraphQL::Dataloader::Source
65
rescue LoadError
76
BasicObject
87
end)
9-
VERSION = '0.1.6'.freeze # .3
8+
VERSION = '0.2.0'.freeze
109
end
1110
end

spec/dummy/config/database.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
default: &default
22
adapter: postgresql
33
encoding: unicode
4+
host: localhost
5+
port: 5432
46
prepared_statements: false
57
advisory_locks: false
68
<% if ENV.include?('DATABASE_URL') %>

spec/dummy/db/schema.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,23 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2021_10_17_215654) do
14-
13+
ActiveRecord::Schema[8.0].define(version: 2021_10_17_215654) do
1514
# These are extensions that must be enabled in order to support this database
16-
enable_extension "plpgsql"
15+
enable_extension "pg_catalog.plpgsql"
1716

1817
create_table "posts", force: :cascade do |t|
1918
t.bigint "user_id"
2019
t.string "title", null: false
2120
t.string "description"
22-
t.datetime "created_at", precision: 6, null: false
23-
t.datetime "updated_at", precision: 6, null: false
21+
t.datetime "created_at", null: false
22+
t.datetime "updated_at", null: false
2423
t.index ["user_id"], name: "index_posts_on_user_id"
2524
end
2625

2726
create_table "users", force: :cascade do |t|
2827
t.string "email", null: false
29-
t.datetime "created_at", precision: 6, null: false
30-
t.datetime "updated_at", precision: 6, null: false
28+
t.datetime "created_at", null: false
29+
t.datetime "updated_at", null: false
3130
end
3231

3332
add_foreign_key "posts", "users"

0 commit comments

Comments
 (0)