Skip to content

Instantly share code, notes, and snippets.

@rmosolgo
Created October 19, 2023 18:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmosolgo/4c81f55ad388022c6f0f51922bb0812f to your computer and use it in GitHub Desktop.
Save rmosolgo/4c81f55ad388022c6f0f51922bb0812f to your computer and use it in GitHub Desktop.
Multi-tenant querying with GraphQL::Dataloader
require "bundler/inline"
gemfile do
gem "graphql"
end
# Here's a pretend DB with three tenants:
DATA = {
"food_lion" => [
{ id: 1, name: "Mayo" },
{ id: 2, name: "Bread" },
{ id: 3, name: "Apple" },
],
"books_a_million" => [
{ id: 1, name: "Hannah Coulter" },
{ id: 2, name: "Ruby Under a Microscope" },
{ id: 3, name: "American Farmstead Cheese" },
],
"crozet_hardware" => [
{ id: 1, name: "Paint brush" },
{ id: 2, name: "Flashlight" },
{ id: 3, name: "Gate hinge" },
]
}
# Here's a dataloader source which selects by tenant:
class PerTenantRecordSource < GraphQL::Dataloader::Source
def initialize(tenant)
@tenant = tenant
end
def fetch(ids)
puts "Selecting from #{@tenant.inspect}: #{ids}"
# IRL you'd query ActiveRecord, setting the tenant here
tenant_data = DATA[@tenant]
ids.map { |id| tenant_data.find { |d| d[:id] == id } }
end
end
class ExampleSchema < GraphQL::Schema
class Product < GraphQL::Schema::Object
field :name, String
end
class Query < GraphQL::Schema::Object
field :product, Product do
argument :id, Integer
end
def product(id:)
# This field passes the tenant to dataloader:
dataloader.with(PerTenantRecordSource, context[:tenant]).load(id)
end
end
use GraphQL::Dataloader
query(Query)
end
result = ExampleSchema.multiplex([
{ query: "{ product(id: 1) { name } }", context: { tenant: "food_lion" } },
{ query: "{ product(id: 1) { name } }", context: { tenant: "books_a_million" } },
{ query: "{ p1: product(id: 1) { name } p2: product(id: 2) { name } }", context: { tenant: "crozet_hardware" } },
{ query: "{ p1: product(id: 1) { name } p2: product(id: 2) { name } p5: product(id: 5) { name } }", context: { tenant: "food_lion" } },
])
# Under the hood, dataloader runs these separately:
#
# Selecting from "food_lion": [1, 2, 3]
# Selecting from "books_a_million": [1]
# Selecting from "crozet_hardware": [1, 2]
pp result.map(&:to_h)
# [
# {"data"=>{"product"=>{"name"=>"Mayo"}}},
# {"data"=>{"product"=>{"name"=>"Hannah Coulter"}}},
# {"data"=>{"p1"=>{"name"=>"Paint brush"}, "p2"=>{"name"=>"Flashlight"}}},
# {"data"=>{"p1"=>{"name"=>"Mayo"}, "p2"=>{"name"=>"Bread"}, "p5"=>nil}}
# ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment