
Security News
Follow-up and Clarification on Recent Malicious Ruby Gems Campaign
A clarification on our recent research investigating 60 malicious Ruby gems.
graphql-fancy_loader
Advanced tools
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.
We use FancyLoader in production to power large swaths of the Kitsu GraphQL API.
Add this line to your application's Gemfile:
gem 'graphql-fancy_loader'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install graphql-fancy_loader
FancyLoader isn't meant to be called directly. Instead, you create a subclass which defines how to load a given model, and what sorts to expose on it.
For example, given the following schema:
You might create a PostsLoader like this:
# graphql/loaders/posts_loader
class Loaders::PostsLoader < GraphQL::FancyLoader
from Post
sort :created_at
end
This loader class provides two primary methods: .sort_argument
and .connection_for
.
.sort_argument
gives you a convenient auto-generated sort field which allows for multiple sorts.
.connection_for
is a wrapper around graphql-batch which returns a connection. Pagination will be
automatically applied to this connection by the graphql gem, so you just need to pass in your other
options:
# graphql/types/user.rb
class Types::User < Types::BaseObject
field :posts, Types::Post.connection_type, null: false do
argument :sort, Loaders::PostsLoader.sort_argument, required: false
end
# Default sorts are just default parameters!
def posts(sort: [{ on: :created_at, direction: :desc }])
Loaders::PostsLoader.connection_for({
find_by: :user_id,
sort: sort
}, object.id)
end
end
Testing a FancyLoader is pretty much exactly the same as testing any other graphql-batch Loader class:
RSpec.describe Loaders::PostsLoader do
let!(:user) { create(:user) }
let!(:posts) { create_list(:post, 10, user: user) }
let(:context) do
GraphQL::Query::Context.new(query: OpenStruct.new(schema: YourSchema), values: nil, object: nil)
end
let(:sort) { [{ on: :created_at, direction: :desc }] }
it 'loads all the posts for a user' do
posts = GraphQL::Batch.batch do
described_class.connection_for({
find_by: :user_id,
sort: sort,
context: context
}, user.id).nodes
end
expect(posts.count).to eq(user.posts.count)
end
end
To provide authorization, FancyLoader allows for setting middleware that can modify the query before it's run. These middleware receive an ActiveRecord::Relation and are expected to return an ActiveRecord::Relation.
FancyLoader currently ships with one middleware, integrating Pundit authorization.
The Pundit middleware automatically applies Pundit scopes. This does not apply #show?
, so make
sure your Pundit scope is set up correctly!
To provide the current_user
to the Pundit middleware, set it on the GraphQL context
and then
tell the middleware which key it should check:
# initializers/graphql_fancy_loader.rb
GraphQL::FancyLoader.configure do |config|
config.middleware = [GraphQL::FancyLoader::PunditMiddleware.new(key: :token)]
end
modify_query
The modify_query
declaration is a general-purpose escape-hatch for customizing the query. Similar
to the Middleware system, this allows you to modify the query before it is sent, but unlike the
Middleware, this will give you access to the actual Arel query we've generated. This lambda is run
in the context of the QueryGenerator, which means you can access instance variables such as
context
.
A common usage for this is to take a sparse-ranked column (such as one managed by ranked-model)
and create a column with the human-friendly rank value. This is, in fact, so common that we provide
a helper utility, RankQueryGenerator
! This handy little class generates an Arel column with a
_rank
suffix (customizable) that makes it look like a normal 1-indexed list!
class Loaders::InstallmentsLoader < Loaders::FancyLoader
from Installment
modify_query ->(query) {
release_rank = GraphQL::FancyLoader::RankQueryGenerator.new(
column: :release_order,
partition_by: @find_by,
table: table
).arel
query.project(release_rank)
}
sort :release_order
end
sort transform:
and sort on:
You can also modify sorts by supplying procs for the transform:
and on:
parameters.
transform:
is run when the sort is applied, and allows you to modify the generated query (like a
modify_query
that only runs when the sort is present). This is useful if you need to join another
table for your sort.
on:
is called to determine which Arel field to sort by. By default, the sort will just use the
same column as its own name. With on:
you can use fields from a joined table, or even generate a
virtual column for sorting!
class Loaders::PostLikesLoader < Loaders::FancyLoader
from PostLike
sort :following,
transform: ->(ast, context) {
follows = Follow.arel_table
likes = PostLike.arel_table
condition = follows[:followed_id].eq(likes[:user_id]).and(
follows[:follower_id].eq(context[:current_user].id)
)
ast.join(follows, Arel::Nodes::OuterJoin).on(condition)
},
on: -> { Follow.arel_table[:id] }
sort :created_at
end
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run
the tests. You can also run bin/console
for an interactive prompt that will allow you to
experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new
version, update the version number in version.rb
, and then run bundle exec rake release
, which
will create a git tag for the version, push git commits and tags, and push the .gem
file to
rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/hummingbird-me/graphql-fancy-loader.
The gem is available as open source under the terms of the Apache-2.0 License.
FAQs
Unknown package
We found that graphql-fancy_loader demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.
Security News
ESLint now supports parallel linting with a new --concurrency flag, delivering major speed gains and closing a 10-year-old feature request.
Research
/Security News
A malicious Go module posing as an SSH brute forcer exfiltrates stolen credentials to a Telegram bot controlled by a Russian-speaking threat actor.