Class: DeepPreloader::PreloadWorker

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

Instance Method Summary collapse

Constructor Details

#initialize(lock:) ⇒ PreloadWorker

Returns a new instance of PreloadWorker.



22
23
24
25
# File 'lib/deep_preloader.rb', line 22

def initialize(lock:)
  @lock     = lock
  @worklist = {}
end

Instance Method Details

#add_associations_from_spec(models, spec) ⇒ Object



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
# File 'lib/deep_preloader.rb', line 27

def add_associations_from_spec(models, spec)
  models = Array.wrap(models)

  # A polymorphic spec expects models of several types, and defines which
  # associations to preload for each of them.
  if spec.polymorphic?
    # We expect models to be of different types, and to have different subspecs for each.
    models_by_class = models.group_by(&:class)

    models_by_class.each do |model_class, class_models|
      model_spec = spec.for_type(model_class)
      next unless model_spec

      add_associations_from_spec(class_models, model_spec)
    end
  else
    # A non-polymorphic spec implies that the models are all of the same type
    model_class = models.first.class

    unless models.all? { |m| m.is_a?(model_class) }
      raise ArgumentError.new('Provided multiple model types to a non-polymorphic preload spec')
    end

    spec.association_specs.each do |association_name, child_spec|
      association_reflection = model_class.reflect_on_association(association_name)
      if association_reflection.nil?
        raise ArgumentError.new("Preloading error: couldn't find association #{association_name} on model class #{model_class.name}")
      end

      # A polymorphic association links to many different model types
      # (discriminated in the referrer), so must be loaded separately per
      # target model type.
      if association_reflection.polymorphic?
        add_polymorphic_association(models, association_reflection, child_spec)
      else
        add_association(models, association_reflection, child_spec)
      end
    end
  end
end

#run!Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/deep_preloader.rb', line 68

def run!
  while @worklist.present?
    context, entries = @worklist.shift
    ActiveRecord::Base.logger&.debug("Preloading children in context #{context}") if DEBUG

    loaded_entries, unloaded_entries = entries.partition(&:loaded?)

    unloaded_keys = unloaded_entries.map(&:key).to_set.delete(nil)

    ActiveRecord::Base.logger&.debug("Need to load children for following keys: #{unloaded_keys.to_a}") if DEBUG

    found_children = {}

    if unloaded_keys.present?
      # When :belongs_to, children could be shared with already loaded
      # entries - use what we already have.
      loaded_entries.each do |entry|
        next unless entry.belongs_to?

        if entry.belongs_to? && unloaded_keys.delete?(entry.key)
          found_children[entry.key] = entry.children
        end
      end
      ActiveRecord::Base.logger&.debug("found loaded children for keys #{found_children.keys}") if DEBUG
    end

    if unloaded_keys.present?
      fetched_children = context.load_children(unloaded_keys.to_a, lock: @lock)
      ActiveRecord::Base.logger&.debug("fetched children for keys #{fetched_children.keys}") if DEBUG
      found_children.merge!(fetched_children)
    end

    unloaded_entries.each do |entry|
      children = found_children.fetch(entry.key, [])
      entry.children = children
    end

    entries.each do |entry|
      children = entry.children
      child_spec = entry.child_spec
      next unless child_spec && children.present?

      add_associations_from_spec(children, child_spec)
    end
  end
end