exAspArk / batch-loader

:zap: Powerful tool for avoiding N+1 DB or HTTP queries
https://engineering.universe.com/batching-a-powerful-way-to-solve-n-1-queries-every-rubyist-should-know-24e20c6e7b94
MIT License
1.04k stars 52 forks source link

Using with GraphQL Pro authorization #21

Closed benkimball closed 6 years ago

benkimball commented 6 years ago

I'm running into a bit of difficulty using GraphQL Pro's Pundit authorization with batch-loader. We've set up a base resolver class like so:

class BaseResolver < GraphQL::Function
  def batch(obj, args, model)
    object_id = "#{obj.class.name.downcase}_id"
    BatchLoader.for(obj.id).batch(default_value: [], key: args.to_h) do |obj_ids, loader|
      scopes(model.in(object_id => obj_ids), args).each do |record|
        loader.call(record[object_id]) { |memo| memo << record }
      end
    end
  end

  def self.extend_type(base_type)
    base_type.define_connection do
      field :totalCount do
        type types.Int
        resolve ->(obj, _args, _ctx) { obj.nodes.size }
      end
    end
  end
end

and then a specific resolver might look like this:

class FindUnicorns < BaseResolver
  type extend_type(Types::UnicornType)

  def call(obj, args, _ctx)
    batch(obj, args, Unicorn)
  end

  private

  def scopes(query, _args)
    query
  end
end

and we have a policy like this:

class UnicornPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      if user.admin?
        scope.all
      else
        # only unicorns user rides
        scope.where(rider_id: user.id)
      end
    end
  end
end

The trouble is that GraphQL Pro will only invoke the UnicornPolicy::Scope#resolve method if the resolver returns an ActiveRecord::Relation or Mongoid::Criteria, and with batch-loader, we're actually returning a BatchLoader. If I replace our FindUnicorns resolver with this:

class FindUnicornsNoBatch < BaseResolver
  type Types::UnicornType

  def call(_obj, _args, ctx)
    Unicorn.all
  end
end

...then the Scope#resolve method is called as I desire.

My question is: is there a way I can use these two libraries together? Perhaps I should batch after applying the policy? Or maybe patch GraphQL Pro to also invoke the policy scope if the object type is BatchLoader? My goal is to achieve the efficiency of batch loading and caching Unicorn results, while also ensuring that no user receives Unicorns which they do not deserve.

exAspArk commented 6 years ago

Hey @benkimball!

Unfortunately, I can't debug issues with tools I don't have access to (GraphQL Pro). I would recommend either: