Class: EagerEye::SerializerUsageParser

Inherits:
Object
  • Object
show all
Defined in:
lib/eager_eye/serializer_usage_parser.rb

Overview

Serializers are the worst false-positive source because the detector sees the serializer class in isolation: it cannot tell whether an association it flags is actually eager-loaded by the controller that renders it, nor whether the serializer is only ever handed a single record (no collection => no N+1).

This parser scans the whole app for render sites — ‘XxxBlueprint.render*(arg, view: :v)`, AMS `render json: arg, (each_)serializer: XxxSerializer` — and, per (serializer, view), records:

* preloaded_per_site : the associations eager-loaded on `arg` at each site
* any_collection      : was `arg` ever a collection (vs a single record)?

An association preloaded at EVERY site, or a serializer only ever fed single records, cannot cause an N+1 — letting the detector stay silent there.

Constant Summary collapse

PRELOAD_METHODS =
%i[includes preload eager_load].freeze
RELATION_WRAPPERS =
%i[pagy paginate page kaminari with_pagy].freeze
SINGLE_RECORD_METHODS =
%i[find find_by find_by! first first! last last! take take! sole find_sole_by
new build current_user current_account].freeze
RENDER_METHODS =
%i[render render_as_hash render_as_json render_as_json! serialize].freeze
SERIALIZER_SUFFIXES =
%w[Blueprint Serializer Resource].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSerializerUsageParser

Returns a new instance of SerializerUsageParser.



29
30
31
# File 'lib/eager_eye/serializer_usage_parser.rb', line 29

def initialize
  @usages = Hash.new { |h, k| h[k] = [] }
end

Instance Attribute Details

#usagesObject (readonly)

serializer_basename => [ { view: sym_or_nil, preloaded: Set, collection: bool }, … ]



27
28
29
# File 'lib/eager_eye/serializer_usage_parser.rb', line 27

def usages
  @usages
end

Instance Method Details

#known_serializer?(serializer) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/eager_eye/serializer_usage_parser.rb', line 56

def known_serializer?(serializer)
  @usages.key?(serializer)
end

#parse_file(ast) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/eager_eye/serializer_usage_parser.rb', line 33

def parse_file(ast)
  return unless ast

  each_scope(ast) do |body|
    var_values = collect_assignments(body)
    find_render_sites(body, var_values)
  end
end

#safe_access?(serializer, view, association) ⇒ Boolean

Whether an association read in ‘view` of `serializer` can be proven safe. `view` is the Blueprinter view the field lives in (nil = a base/default field, rendered by every site). The field is safe when, at every render site that renders it, the association is eager-loaded OR the site passes a single record. To avoid hiding a genuine N+1 we never conclude “safe” when we cannot see the render sites for a named view (it may be rendered dynamically) — only an EXISTING, uniformly-safe set of sites suppresses.

Returns:

  • (Boolean)


49
50
51
52
53
54
# File 'lib/eager_eye/serializer_usage_parser.rb', line 49

def safe_access?(serializer, view, association)
  sites = sites_rendering(serializer, view)
  return false if sites.empty?

  sites.all? { |s| !s[:collection] || s[:preloaded].include?(association) }
end