CacheQL
Need to cache and instrument your GraphQL code in Ruby? Look no further!
This is a collection of utilities for graphql-ruby that were collected from various places on GitHub + docs.
This code was extracted from Chatterbug. Check out the talk from Railsconf 2018 about this gem!
Installation
Add this line to your application's Gemfile:
gem 'cacheql'
And then execute:
$ bundle
Or install it yourself as:
$ gem install cacheql
Usage
There's four major parts to this gem:
Cache helpers
Need to cache a single resolve function? Wrap it in CacheQL
:
resolve CacheQL -> (obj, args, ctx) {
# run expensive operation
# this resolve function's result will be cached on obj.cache_key
}
Want to cache the entire response after GraphQL has generated it? Try this in your controller:
def execute
# other graphql stuff...
FIELDS = %w(users curriculum)
render json: CacheQL.fetch(FIELDS, query, variables) { |document|
YourSchema.execute(
document: document,
variables: variables,
context: { },
operation_name: params[:operationName]).to_h
}
end
This will cache the entire response when the query includes FIELDS
.
Loaders
These are all based off of community-written examples using graphql-batch. These will reduce N+1 queries in your GraphQL code.
Before using loaders, you'll have to use GraphQL::Batch
as a plugin in your schema:
YourSchema = GraphQL::Schema.define do
query YourQueryType
use GraphQL::Batch
end
Batch up belongs_to
calls:
# when obj has a belongs_to :language
resolve -> (obj, args, ctx) {
CacheQL::RecordLoader.for(Language).load(obj.language_id)
}
Batch up belongs_to polymorphic: true
calls:
# when obj has a belongs_to :respondable, polymorphic: true
resolve -> (obj, args, ctx) {
CacheQL::PolymorphicKeyLoader.for(Response, :respondable).load(obj.respondable)
}
Batch up entire associations:
# when obj has_many :clozes
resolve -> (obj, args, ctx) {
CacheQL::AssociationLoader.for(obj.class, :clozes).load(obj)
}
Logging
Want to get your GraphQL fields logging locally? In your controller, add:
around_action :log_field_instrumentation
private
def log_field_instrumentation(&block)
CacheQL::FieldInstrumentation.log(&block)
end
This will then spit out timing logs for each field run during each request. For example:
[CacheQL::Tracing] User.displayLanguage took 7.591ms
[CacheQL::Tracing] User.createdAt took 0.117ms
[CacheQL::Tracing] User.intercomHash took 0.095ms
[CacheQL::Tracing] User.id took 0.09ms
[CacheQL::Tracing] User.friendlyTimezone took 0.087ms
[CacheQL::Tracing] User.utmContent took 0.075ms
[GraphQL::Tracing] User.timezone took 0.048ms
[CacheQL::Tracing] User.email took 0.046ms
[CacheQL::Tracing] User.name took 0.042ms
[CacheQL::Tracing] Query.currentUser took 0.041ms
Instrumentation
This gem includes an instrumenter for Scout that will show the timing breakdown of each field in your metrics. Assuming you have Scout setup, add to your schema:
YourSchema = GraphQL::Schema.define do
instrument :field, CacheQL::FieldInstrumentation.new(ScoutApm::Tracer)
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
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.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/chatterbugapp/cacheql.
License
The gem is available as open source under the terms of the MIT License.