Module: JitPreloadExtension

Defined in:
lib/jit_preloader/active_record/base.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#jit_n_plus_one_trackingObject

Returns the value of attribute jit_n_plus_one_tracking.



3
4
5
# File 'lib/jit_preloader/active_record/base.rb', line 3

def jit_n_plus_one_tracking
  @jit_n_plus_one_tracking
end

#jit_preload_aggregatesObject

Returns the value of attribute jit_preload_aggregates.



4
5
6
# File 'lib/jit_preloader/active_record/base.rb', line 4

def jit_preload_aggregates
  @jit_preload_aggregates
end

#jit_preload_scoped_relationsObject

Returns the value of attribute jit_preload_scoped_relations.



5
6
7
# File 'lib/jit_preloader/active_record/base.rb', line 5

def jit_preload_scoped_relations
  @jit_preload_scoped_relations
end

#jit_preloaderObject

Returns the value of attribute jit_preloader.



2
3
4
# File 'lib/jit_preloader/active_record/base.rb', line 2

def jit_preloader
  @jit_preloader
end

Class Method Details

.prepended(base) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
114
115
116
# File 'lib/jit_preloader/active_record/base.rb', line 54

def self.prepended(base)
  class << base
    delegate :jit_preload, to: :all

    def has_many_aggregate(assoc, name, aggregate, field, table_alias_name: nil, default: 0)
      method_name = "#{assoc}_#{name}"

      define_method(method_name) do |conditions={}|
        self.jit_preload_aggregates ||= {}

        key = "#{method_name}|#{conditions.sort.hash}"
        return jit_preload_aggregates[key] if jit_preload_aggregates.key?(key)
        if jit_preloader
          reflection = association(assoc).reflection
          primary_ids = jit_preloader.records.collect{|r| r[reflection.active_record_primary_key] }
          klass = reflection.klass

          aggregate_association = reflection
          while aggregate_association.through_reflection
            aggregate_association = aggregate_association.through_reflection
          end

          association_scope = klass.all.merge(association(assoc).scope).unscope(where: aggregate_association.foreign_key)
          association_scope = association_scope.instance_exec(&reflection.scope).reorder(nil) if reflection.scope

          # If the query uses an alias for the association, use that instead of the table name
          table_reference = table_alias_name
          table_reference ||= association_scope.references_values.first || aggregate_association.table_name

          conditions[table_reference] = { aggregate_association.foreign_key => primary_ids }

          # If the association is a STI child model, specify its type in the condition so that it
          # doesn't include results from other child models
          parent_is_base_class = aggregate_association.klass.superclass.abstract_class? || aggregate_association.klass.superclass == ActiveRecord::Base
          has_type_column = aggregate_association.klass.column_names.include?(aggregate_association.klass.inheritance_column)
          is_child_sti_model = !parent_is_base_class && has_type_column
          if is_child_sti_model
            conditions[table_reference].merge!({ aggregate_association.klass.inheritance_column => aggregate_association.klass.sti_name })
          end

          if reflection.type.present?
            conditions[reflection.type] = self.class.name
          end
          group_by = "#{table_reference}.#{aggregate_association.foreign_key}"

          preloaded_data = Hash[association_scope
            .where(conditions)
            .group(group_by)
            .send(aggregate, field)
          ]

          jit_preloader.records.each do |record|
            record.jit_preload_aggregates ||= {}
            record.jit_preload_aggregates[key] = preloaded_data[record.id] || default
          end
        else
          self.jit_preload_aggregates[key] = send(assoc).where(conditions).send(aggregate, field) || default
        end
        jit_preload_aggregates[key]
      end
    end
  end
end

Instance Method Details

#clear_jit_preloader!Object



12
13
14
15
16
17
18
19
# File 'lib/jit_preloader/active_record/base.rb', line 12

def clear_jit_preloader!
  self.jit_preload_aggregates = {}
  self.jit_preload_scoped_relations = {}
  if jit_preloader
    jit_preloader.records.delete(self)
    self.jit_preloader = nil
  end
end

#preload_scoped_relation(name:, base_association:, preload_scope: nil) ⇒ Object



21
22
23
24
25
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
# File 'lib/jit_preloader/active_record/base.rb', line 21

def preload_scoped_relation(name:, base_association:, preload_scope: nil)
  return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)

  records = jit_preloader&.records || [self]
  previous_association_values = {}

  records.each do |record|
    association = record.association(base_association)
    if association.loaded?
      previous_association_values[record] = association.target
      association.reset
    end
  end

  ActiveRecord::Associations::Preloader.new.preload(
    records,
    base_association,
    preload_scope
  )

  records.each do |record|
    record.jit_preload_scoped_relations ||= {}
    association = record.association(base_association)
    record.jit_preload_scoped_relations[name] = association.target
    association.reset
    if previous_association_values.key?(record)
      association.target = previous_association_values[record]
    end
  end

  jit_preload_scoped_relations[name]
end

#reload(*args) ⇒ Object



7
8
9
10
# File 'lib/jit_preloader/active_record/base.rb', line 7

def reload(*args)
  clear_jit_preloader!
  super
end