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



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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/jit_preloader/active_record/base.rb', line 89

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, max_ids_per_query: nil)
      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] }
          max_ids_per_query = max_ids_per_query || JitPreloader.max_ids_per_query
          if max_ids_per_query
            slices = primary_ids.each_slice(max_ids_per_query)
          else
            slices = [primary_ids]
          end

          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

          # 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] = { 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 = {}
          slices.each do |slice|
            data = Hash[association_scope
                          .where(conditions.deep_merge(table_reference => { aggregate_association.foreign_key => slice }))
                          .group(group_by)
                          .send(aggregate, field)
            ]
            preloaded_data.merge!(data)
          end

          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



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

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

  preloader_association = ActiveRecord::Associations::Preloader.new(
    records: records,
    associations: base_association,
    scope: preload_scope
  ).call.first

  records.each do |record|
    record.jit_preload_scoped_relations ||= {}
    association = record.association(base_association)
    record.jit_preload_scoped_relations[name] = preloader_association.records_by_owner[record] || []
    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