Class: LcpRuby::Presenter::IncludesResolver::StrategyResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb

Overview

Maps a collection of AssociationDependency objects into a LoadingStrategy.

Strategy matrix:

display only          query (or both)

belongs_to/has_one includes eager_load has_many includes joins + includes

Rationale: eager_load on has_many causes cartesian products that break Kaminari pagination. Using joins (for query) + includes (for preload) avoids this.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dependencies, model_def) ⇒ StrategyResolver

Returns a new instance of StrategyResolver.



22
23
24
25
# File 'lib/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb', line 22

def initialize(dependencies, model_def)
  @dependencies = dependencies
  @model_def = model_def
end

Class Method Details

.resolve(dependencies, model_def) ⇒ LoadingStrategy

Parameters:

Returns:



18
19
20
# File 'lib/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb', line 18

def self.resolve(dependencies, model_def)
  new(dependencies, model_def).resolve
end

Instance Method Details

#resolveObject



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/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb', line 27

def resolve
  includes_list = []
  eager_load_list = []
  joins_list = []
  api_preloads_list = []

  # Group dependencies by association name to determine combined reason
  grouped = @dependencies.group_by(&:association_name)

  grouped.each do |assoc_name, deps|
    assoc = @model_def.associations.find { |a| a.name == assoc_name.to_s }
    assoc_type = resolve_assoc_type(assoc, assoc_name)
    next unless assoc_type

    # Check if this association targets an API model (cross-source)
    if assoc && api_target?(assoc)
      api_preloads_list << { name: assoc_name.to_s, association: assoc }
      next
    end

    has_query = deps.any?(&:query?)
    # Use the most nested path among the deps for this association
    path = select_path(deps)

    if has_query
      # Query reason: need JOIN for WHERE/ORDER
      if assoc_type == "has_many"
        # has_many + query: joins for query, includes for preload
        joins_list << path
        includes_list << path
      else
        # belongs_to/has_one + query: eager_load (LEFT JOIN)
        eager_load_list << path
      end
    else
      # Display only: includes (separate query or LEFT JOIN, AR decides)
      includes_list << path
    end
  end

  LoadingStrategy.new(
    includes: includes_list.uniq,
    eager_load: eager_load_list.uniq,
    joins: joins_list.uniq,
    api_preloads: api_preloads_list
  )
end