Class: HQ::GraphQL::PaginatedAssociationLoader

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

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of PaginatedAssociationLoader.



6
7
8
9
10
11
12
13
14
15
# File 'lib/hq/graphql/paginated_association_loader.rb', line 6

def initialize(model, association_name, limit: nil, offset: nil, sort_by: nil, sort_order: nil)
  @model            = model
  @association_name = association_name
  @limit            = [0, limit].max if limit
  @offset           = [0, offset].max if offset
  @sort_by          = sort_by || :updated_at
  @sort_order       = normalize_sort_order(sort_order)

  validate!
end

Instance Method Details

#cache_key(record) ⇒ Object



22
23
24
# File 'lib/hq/graphql/paginated_association_loader.rb', line 22

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

#load(record) ⇒ Object

Raises:

  • (TypeError)


17
18
19
20
# File 'lib/hq/graphql/paginated_association_loader.rb', line 17

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

#perform(records) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
# File 'lib/hq/graphql/paginated_association_loader.rb', line 26

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