# File lib/hq/graphql/paginated_association_loader.rb, line 172 def primary_key @model.primary_key end
class HQ::GraphQL::PaginatedAssociationLoader
Public Class Methods
for(*args, scope: nil, **kwargs)
click to toggle source
Calls superclass method
# File lib/hq/graphql/paginated_association_loader.rb, line 8 def self.for(*args, scope: nil, **kwargs) if scope raise TypeError, "scope must be an ActiveRecord::Relation" unless scope.is_a?(::ActiveRecord::Relation) executor = ::GraphQL::Batch::Executor.current loader_key = loader_key_for(*args, **kwargs, scope: scope.to_sql) executor.loader(loader_key) { new(*args, **kwargs, scope: scope) } else super end end
new(model, association_name, internal_association: false, limit: nil, offset: nil, scope: nil, sort_by: nil, sort_order: nil)
click to toggle source
Calls superclass method
# File lib/hq/graphql/paginated_association_loader.rb, line 19 def initialize(model, association_name, internal_association: false, limit: nil, offset: nil, scope: nil, sort_by: nil, sort_order: nil) super() @model = model @association_name = association_name @internal_association = internal_association @limit = [0, limit].max if limit @offset = [0, offset].max if offset @scope = scope @sort_by = sort_by || :created_at @sort_order = normalize_sort_order(sort_order) validate! end
Public Instance Methods
cache_key(record)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 38 def cache_key(record) record.send(primary_key) end
load(record)
click to toggle source
Calls superclass method
# File lib/hq/graphql/paginated_association_loader.rb, line 33 def load(record) raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model) super end
perform(records)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 42 def perform(records) values = records.map { |r| source_value(r) } scope = if @limit || @offset # If a limit or offset is added, then we need to transform the query # into a lateral join so that we can limit on groups of data. # # > SELECT * FROM addresses WHERE addresses.user_id IN ($1, $2, ..., $N) ORDER BY addresses.created_at DESC; # ...becomes # > SELECT DISTINCT a_top.* # > FROM addresses # > INNER JOIN LATERAL ( # > SELECT inner.* # > FROM addresses inner # > WHERE inner.user_id = addresses.user_id # > ORDER BY inner.created_at DESC # > LIMIT 1 # > ) a_top ON TRUE # > WHERE addresses.user_id IN ($1, $2, ..., $N) # > ORDER BY a_top.created_at DESC inner_table = association_class.arel_table lateral_join_table = through_reflection? ? through_association.klass.arel_table : inner_table from_table = lateral_join_table.alias("outer") inside_scope = default_scope. select(inner_table[::Arel.star]). where(lateral_join_table[target_join_key].eq(from_table[target_join_key])). reorder(arel_order(inner_table)). limit(@limit). offset(@offset) if through_reflection? # expose the through_reflection key inside_scope = inside_scope.select(lateral_join_table[target_join_key]) end lateral_table = ::Arel::Table.new("top") association_class. select(lateral_table[::Arel.star]).distinct. from(from_table). where(from_table[target_join_key].in(values)). joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{lateral_table.name} ON TRUE"). reorder(arel_order(lateral_table)) else scope = default_scope.reorder(arel_order(association_class.arel_table)) if through_reflection? scope.where(through_association.name => { target_join_key => values }). # expose the through_reflection key select(association_class.arel_table[::Arel.star], through_association.klass.arel_table[target_join_key]) else scope.where(target_join_key => values) end end results = scope.to_a records.each do |record| fulfill(record, target_value(record, results)) unless fulfilled?(record) end end
Private Instance Methods
arel_order(table)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 176 def arel_order(table) table[@sort_by].send(@sort_order) end
association()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 160 def association if @internal_association Types[@model].reflect_on_association(@association_name) else @model.reflect_on_association(@association_name) end end
association_class()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 168 def association_class association.klass end
belongs_to?()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 144 def belongs_to? association.macro == :belongs_to end
default_scope()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 128 def default_scope scope = association_class scope = association.scopes.reduce(scope, &:merge) scope = association_class.default_scopes.reduce(scope, &:merge) scope = scope.merge(@scope) if @scope if through_reflection? source = association_class.arel_table target = through_association.klass.arel_table join = source.join(target).on(target[association.foreign_key].eq(source[source_join_key])) scope = scope.joins(join.join_sources) end scope end
has_many?()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 148 def has_many? association.macro == :has_many end
normalize_sort_order(input)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 180 def normalize_sort_order(input) if input.to_s.casecmp("asc").zero? :asc else :desc end end
primary_key()
click to toggle source
source_join_key()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 105 def source_join_key belongs_to? ? association.foreign_key : association.association_primary_key end
source_value(record)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 109 def source_value(record) record.send(source_join_key) end
target_join_key()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 113 def target_join_key if through_reflection? through_association.foreign_key elsif belongs_to? association.association_primary_key else association.foreign_key end end
target_value(record, results)
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 123 def target_value(record, results) enumerator = has_many? ? :select : :detect results.send(enumerator) { |r| r.send(target_join_key) == source_value(record) } end
through_association()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 152 def through_association association.through_reflection end
through_reflection?()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 156 def through_reflection? association.through_reflection? end
validate!()
click to toggle source
# File lib/hq/graphql/paginated_association_loader.rb, line 188 def validate! raise ArgumentError, "No association #{@association_name} on #{@model}" unless association end