Class: GraphqlDeclarative::Preloader
- Inherits:
-
Object
- Object
- GraphqlDeclarative::Preloader
- Defined in:
- lib/graphql_declarative/preloader.rb
Overview
Builds the preload list from the query's actual selection set, so adding a
field to a query never reintroduces an N+1 and no .includes list has to be
kept in sync by hand.
Walk lookahead for selections whose field maps to an association on the
model, recursing to build a nested preload hash:
{author: [:profile], enrollments: []}
Ignore selections that are plain columns, and connection wrapper fields (edges/node) must be unwrapped before matching.
Constant Summary collapse
- CONNECTION_FIELDS =
Connection plumbing. These are structural, not associations: the entity selections of
courses { edges { node { author { ... } } } }live two levels belowcourses. Unwrap them BEFORE matching names against associations, otherwise every connection field looks like a leaf column and nothing is ever preloaded.An association is checked for first, so a model that genuinely has an association called
nodesstill wins over the unwrap rule. %i[edges node nodes].freeze
- DEFAULT_MAX_DEPTH =
A deeply nested query would otherwise let a client dictate the size of the preload tree:
a { b { c { d { ... } } } }is cheap to write and expensive to serve. Three levels of associations covers the real cases; anything below that is simply not preloaded (it still resolves, just lazily). 3
Class Method Summary collapse
-
.from_lookahead(lookahead, model, max_depth: DEFAULT_MAX_DEPTH, static: nil) ⇒ Hash
Suitable for
.preload, e.g. -
.merge(static, derived) ⇒ Hash
Merge declared (
preload author: :profile) preloads with derived ones.
Class Method Details
.from_lookahead(lookahead, model, max_depth: DEFAULT_MAX_DEPTH, static: nil) ⇒ Hash
Returns suitable for .preload, e.g. [:profile].
38 39 40 41 42 43 44 45 46 |
# File 'lib/graphql_declarative/preloader.rb', line 38 def from_lookahead(lookahead, model, max_depth: DEFAULT_MAX_DEPTH, static: nil) derived = if lookahead.respond_to?(:selections) && model.respond_to?(:reflect_on_association) walk(lookahead, model, Integer(max_depth)) else {} end denormalize(deep_merge(normalize(static), derived)) end |
.merge(static, derived) ⇒ Hash
Merge declared (preload author: :profile) preloads with derived ones.
Both sides are normalized to a canonical tree and unioned, so a key present in both keeps everything the static declaration asked for and gains whatever the query additionally selected underneath it. Static is authoritative in the sense that nothing it declares can be dropped or flattened by the derived tree -- which is the only "conflict" that can arise between two preload trees. Preloading a little extra is harmless; preloading too little is an N+1.
59 60 61 |
# File 'lib/graphql_declarative/preloader.rb', line 59 def merge(static, derived) denormalize(deep_merge(normalize(static), normalize(derived))) end |