Skip to content

Instantly share code, notes, and snippets.

@rmosolgo
Created March 15, 2024 20:35
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/c74fd1fdb0f4e2f847c64d1331d21746 to your computer and use it in GitHub Desktop.
Save rmosolgo/c74fd1fdb0f4e2f847c64d1331d21746 to your computer and use it in GitHub Desktop.
Removing items from `nodes` after it has been paginated
# In this approach `nodes` are removed from a connection _after_ pagination has been applied.
#
# This approach has some downsides:
# - It may leak data about unauthorized nodes. hasNextPage and hasPrevPage will
# indicate that items were removed from the list; depending on sort and filter options,
# a client could learn about the attributes of the removed nodes.
# - pagination metadata may be wrong, since the list of nodes was modified after it was calculated.
# (some pagination info may be calculated _while_ the list of nodes is prepared.)
#
# The best alternative approach is to use `scope_items` instead, but I share this
# in case there are situations where that isn't feasible.
#
require "bundler/inline"
gemfile do
gem "graphql", "2.2.12"
gem "sqlite3"
gem "rails"
end
require "active_record"
# Set up the database and add some records:
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
self.verbose = false
create_table :things, force: true do |t|
t.column :name, :string
t.column :authorized, :boolean, default: true
end
end
class Thing < ActiveRecord::Base; end
Thing.create!(name: "Alfalfa")
Thing.create!(name: "Barley")
Thing.create!(name: "Corn", authorized: false)
Thing.create!(name: "Durum")
class MySchema < GraphQL::Schema
# Make a custom Connection object which will apply authorization to its nodes
class FilteredThingsConnection < GraphQL::Pagination::ActiveRecordRelationConnection
# Before returning nodes, remove unauthorized ones. (Pagination metadata will be unaffected.)
def nodes
@loaded_and_authorized_nodes ||= begin
paginated_nodes = super
res = Thing.remove_unauthorized_nodes(paginated_nodes)
pp res
res
end
end
end
class Thing < GraphQL::Schema::Object
field :name, String
def self.remove_unauthorized_nodes(all_nodes)
all_nodes.select(&:authorized?)
end
end
class Query < GraphQL::Schema::Object
field :things, Thing.connection_type
def things
things = ::Thing.all
# Wrap the ActiveRecord::Relation in a custom connection object:
FilteredThingsConnection.new(things)
end
end
query(Query)
end
pp MySchema.execute("{ things { nodes { name } } }").to_h
# {"data"=>{"things"=>{"nodes"=>[{"name"=>"Alfalfa"}, {"name"=>"Barley"}, {"name"=>"Durum"}]}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment