class GraphQL::FancyLoader::QueryGenerator

Attributes

context[R]

Public Class Methods

new( model:, find_by:, sort:, keys:, before: nil, after: 0, first: nil, last: nil, where: nil, context: {}, modify_query: nil ) click to toggle source

@param model [ActiveRecord::Model] the model to load from @param find_by [Symbol, String, Array<Symbol, String>] the key or keys to find by @param sort [Array<{:column, :transform, :direction => Object}>] The sorts to apply @param keys [Array] an array of values to find by @param before [Integer] Filter by rows less than this (one-indexed) @param after [Integer] Filter by rows greater than this (one-indexed) @param first [Integer] Filter for first N rows @param last [Integer] Filter for last N rows @param where [Hash] a filter to use when querying @param context [Context] The context of the graphql query. Can be used inside of modify_query. @param modify_query [Lambda] An escape hatch to FancyLoader to allow modifying

the base_query before it generates the rest of the query
# File lib/graphql/fancy_loader/query_generator.rb, line 17
def initialize(
  model:, find_by:, sort:, keys:,
  before: nil, after: 0, first: nil, last: nil,
  where: nil, context: {}, modify_query: nil
)
  @model = model
  @find_by = find_by
  @sort = sort
  @keys = keys
  @before = before
  @after = after
  @first = first
  @last = last
  @where = where
  @context = context
  @modify_query = modify_query
end

Public Instance Methods

query() click to toggle source
# File lib/graphql/fancy_loader/query_generator.rb, line 35
def query
  # Finally, go *back* to the ActiveRecord model, and do the final select
  @model.unscoped
        .select(Arel.star)
        .from(subquery)
        .where(pagination_filter(subquery))
        .order(subquery[:row_number].asc)
end

Private Instance Methods

base_query() click to toggle source

The “base” query. This is the query that would load everything without pagination or sorting, just auth scoping.

# File lib/graphql/fancy_loader/query_generator.rb, line 94
def base_query
  query = @model.where(@find_by => @keys)
  query = query.where(@where) unless @where.nil?
  query = middleware(query: query)
  query.arel
end
count() click to toggle source

A count window function. Omits sort from the partition to get the total count.

COUNT(*) OVER (#{partition})
# File lib/graphql/fancy_loader/query_generator.rb, line 77
def count
  count_partition = Arel::Nodes::Window.new.partition(table[@find_by])
  Arel::Nodes::NamedFunction.new('COUNT', [Arel.star]).over(count_partition).as('total_count')
end
middleware(query:) click to toggle source
# File lib/graphql/fancy_loader/query_generator.rb, line 114
def middleware(query:)
  return query if GraphQL::FancyLoader.middleware.blank?

  GraphQL::FancyLoader.middleware.each do |klass|
    query = klass.call(model: @model, query: query, context: context)
  end

  query
end
pagination_filter(query) click to toggle source
# File lib/graphql/fancy_loader/query_generator.rb, line 82
def pagination_filter(query)
  @pagination_filter ||= GraphQL::FancyLoader::PaginationFilter.new(
    query,
    before: @before,
    after: @after,
    first: @first,
    last: @last
  ).arel
end
partition() click to toggle source

A window function partition clause to apply the sort within each window

PARTITION BY #{find_by} ORDER BY #{orders}
# File lib/graphql/fancy_loader/query_generator.rb, line 56
def partition
  @partition ||= begin
    # Every sort has a column and a direction, apply them
    orders = @sort.map do |sort|
      sort[:column].call.public_send(sort[:direction])
    end

    Arel::Nodes::Window.new.partition(table[@find_by]).order(orders)
  end
end
row_number() click to toggle source

Our actual window function.

ROW_NUMBER() OVER (#{partition})
# File lib/graphql/fancy_loader/query_generator.rb, line 70
def row_number
  Arel::Nodes::NamedFunction.new('ROW_NUMBER', []).over(partition).as('row_number')
end
subquery() click to toggle source
# File lib/graphql/fancy_loader/query_generator.rb, line 101
def subquery
  @subquery ||= begin
    # Apply the sort transforms and add the window function to our projection
    subquery = @sort.inject(base_query) do |arel, sort|
      sort[:transform] ? sort[:transform].call(arel, context) : arel
    end

    subquery = subquery.project(row_number).project(count)
    subquery = instance_exec(subquery, &@modify_query) unless @modify_query.nil?
    subquery.as('subquery')
  end
end
table() click to toggle source

The underlying Arel table for the model

# File lib/graphql/fancy_loader/query_generator.rb, line 49
def table
  @table ||= @model.arel_table
end