Class: HQ::GraphQL::PaginatedAssociationLoader

Inherits:
GraphQL::Batch::Loader
  • Object
show all
Defined in:
lib/hq/graphql/paginated_association_loader.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, association_name, internal_association: false, limit: nil, offset: nil, scope: nil, sort_by: nil, sort_order: nil) ⇒ PaginatedAssociationLoader

Returns a new instance of PaginatedAssociationLoader.



19
20
21
22
23
24
25
26
27
28
29
30
# 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)
  @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 || :updated_at
  @sort_order           = normalize_sort_order(sort_order)

  validate!
end

Class Method Details

.for(*args, scope: nil, **kwargs) ⇒ Object



8
9
10
11
12
13
14
15
16
17
# 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

Instance Method Details

#cache_key(record) ⇒ Object



37
38
39
# File 'lib/hq/graphql/paginated_association_loader.rb', line 37

def cache_key(record)
  record.send(primary_key)
end

#load(record) ⇒ Object

Raises:

  • (TypeError)


32
33
34
35
# File 'lib/hq/graphql/paginated_association_loader.rb', line 32

def load(record)
  raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
  super
end

#perform(records) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/hq/graphql/paginated_association_loader.rb', line 41

def perform(records)
  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
      association_table = inner_table.alias("outer")

      inside_scope = default_scope.
        select(inner_table[::Arel.star]).
        from(inner_table).
        where(inner_table[association_key].eq(association_table[association_key])).
        reorder(arel_order(inner_table)).
        limit(@limit).
        offset(@offset)

      outside_table = ::Arel::Table.new("top")
      association_class.
        select(outside_table[::Arel.star]).distinct.
        from(association_table).
        joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{outside_table.name} ON TRUE").
        where(association_table[association_key].in(records.map { |r| join_value(r) })).
        reorder(arel_order(outside_table))
    else
      default_scope.
        reorder(arel_order(association_class.arel_table)).
        where(association_key => records.map { |r| join_value(r) })
    end

  results = scope.to_a
  records.each do |record|
    fulfill(record, association_value(record, results)) unless fulfilled?(record)
  end
end