Module: Bullet::ActiveRecord

Defined in:
lib/bullet/active_record3.rb,
lib/bullet/active_record4.rb,
lib/bullet/active_record3x.rb,
lib/bullet/active_record41.rb,
lib/bullet/active_record42.rb

Class Method Summary collapse

Class Method Details

.enableObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
53
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
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/bullet/active_record3.rb', line 3

def self.enable
  require 'active_record'
  ::ActiveRecord::Relation.class_eval do
    alias_method :origin_to_a, :to_a
    # if select a collection of objects, then these objects have possible to cause N+1 query.
    # if select only one object, then the only one object has impossible to cause N+1 query.
    def to_a
      records = origin_to_a
      if Bullet.start?
        if records.size > 1
          Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
          Bullet::Detector::CounterCache.add_possible_objects(records)
        elsif records.size == 1
          Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
          Bullet::Detector::CounterCache.add_impossible_object(records.first)
        end
      end
      records
    end
  end

  ::ActiveRecord::AssociationPreload::ClassMethods.class_eval do
    alias_method :origin_preload_associations, :preload_associations
    # include query for one to many associations.
    # keep this eager loadings.
    def preload_associations(records, associations, preload_options={})
      if Bullet.start?
        records = [records].flatten.compact.uniq
        return if records.empty?
        records.each do |record|
          Bullet::Detector::Association.add_object_associations(record, associations)
        end
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
      origin_preload_associations(records, associations, preload_options={})
    end
  end

  ::ActiveRecord::FinderMethods.class_eval do
    # add includes in scope
    alias_method :origin_find_with_associations, :find_with_associations
    def find_with_associations
      records = origin_find_with_associations
      if Bullet.start?
        associations = (@eager_load_values + @includes_values).uniq
        records.each do |record|
          Bullet::Detector::Association.add_object_associations(record, associations)
        end
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
      records
    end
  end

  ::ActiveRecord::Associations::ClassMethods::JoinDependency.class_eval do
    alias_method :origin_instantiate, :instantiate
    alias_method :origin_construct_association, :construct_association

    def instantiate(rows)
      @bullet_eager_loadings = {}
      records = origin_instantiate(rows)

      if Bullet.start?
        @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
          objects = eager_loadings_hash.keys
          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
        end
      end
      records
    end

    # call join associations
    def construct_association(record, join, row)
      result = origin_construct_association(record, join, row)

      if Bullet.start?
        associations = join.reflection.name
        Bullet::Detector::Association.add_object_associations(record, associations)
        Bullet::Detector::NPlusOneQuery.call_association(record, associations)
        @bullet_eager_loadings[record.class] ||= {}
        @bullet_eager_loadings[record.class][record] ||= Set.new
        @bullet_eager_loadings[record.class][record] << associations
      end

      result
    end
  end

::ActiveRecord::Associations::AssociationCollection.class_eval do
    # call one to many associations
    alias_method :origin_load_target, :load_target
    def load_target
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_load_target
    end

    alias_method :origin_first, :first
    def first(*args)
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_first(*args)
    end

    alias_method :origin_last, :last
    def last(*args)
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_last(*args)
    end

    alias_method :origin_empty?, :empty?
    def empty?
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_empty?
    end

    alias_method :origin_include?, :include?
    def include?(object)
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_include?(object)
    end
  end

  ::ActiveRecord::Associations::AssociationProxy.class_eval do
    # call has_one and belong_to association
    alias_method :origin_load_target, :load_target
    def load_target
      # avoid stack level too deep
      result = origin_load_target
      if Bullet.start?
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? { |c| c.include?("load_target") }
        Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
      end
      result
    end

    alias_method :origin_set_inverse_instance, :set_inverse_instance
    def set_inverse_instance(record, instance)
      if Bullet.start?
        if record && we_can_set_the_inverse_on_this?(record)
          Bullet::Detector::NPlusOneQuery.add_inversed_object(record, @reflection.inverse_of.name)
        end
      end
      origin_set_inverse_instance(record, instance)
    end
  end

  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
    alias_method :origin_has_cached_counter?, :has_cached_counter?

    def has_cached_counter?
      result = origin_has_cached_counter?
      if Bullet.start?
        Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
      end
      result
    end
  end

  ::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
    alias_method :origin_has_cached_counter?, :has_cached_counter?
    def has_cached_counter?
      result = origin_has_cached_counter?
      if Bullet.start?
        Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
      end
      result
    end
  end
end