Module: Bullet::ActiveRecord

Defined in:
lib/bullet/active_record4.rb,
lib/bullet/active_record5.rb,
lib/bullet/active_record41.rb,
lib/bullet/active_record42.rb,
lib/bullet/active_record52.rb,
lib/bullet/active_record60.rb,
lib/bullet/active_record61.rb,
lib/bullet/active_record70.rb,
lib/bullet/active_record71.rb

Class Method Summary collapse

Class Method Details

.enableObject



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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/bullet/active_record4.rb', line 5

def self.enable
  require 'active_record'
  ::ActiveRecord::Base.class_eval do
    class << self
      alias_method :origin_find_by_sql, :find_by_sql
      def find_by_sql(sql, binds = [])
        result = origin_find_by_sql(sql, binds)
        if Bullet.start?
          if result.is_a? Array
            if result.size > 1
              Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
              Bullet::Detector::CounterCache.add_possible_objects(result)
            elsif result.size == 1
              Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
              Bullet::Detector::CounterCache.add_impossible_object(result.first)
            end
          elsif result.is_a? ::ActiveRecord::Base
            Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
            Bullet::Detector::CounterCache.add_impossible_object(result)
          end
        end
        result
      end
    end
  end

  ::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::Persistence.class_eval do
    def _create_record_with_bullet(*args)
      _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
    end
    alias_method_chain :_create_record, :bullet
  end

  ::ActiveRecord::Associations::Preloader.class_eval do
    # include query for one to many associations.
    # keep this eager loadings.
    alias_method :origin_initialize, :initialize
    def initialize(records, associations, preload_scope = nil)
      origin_initialize(records, associations, preload_scope)

      if Bullet.start?
        records = [records].flatten.compact.uniq
        return if records.empty?

        records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
    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 { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
        Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
      end
      records
    end
  end

  ::ActiveRecord::Associations::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]
        if join.reflection.nested?
          associations << join.reflection.through_reflection.name
        end
        associations.each do |association|
          Bullet::Detector::Association.add_object_associations(record, association)
          Bullet::Detector::NPlusOneQuery.call_association(record, association)
          @bullet_eager_loadings[record.class] ||= {}
          @bullet_eager_loadings[record.class][record] ||= Set.new
          @bullet_eager_loadings[record.class][record] << association
        end
      end

      result
    end
  end

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

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

  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
    alias_method :origin_empty?, :empty?
    def empty?
      if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)
        Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
      end
      origin_empty?
    end
  end

  ::ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
    alias_method :origin_empty?, :empty?
    def empty?
      Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !loaded?
      origin_empty?
    end
  end

  ::ActiveRecord::Associations::SingularAssociation.class_eval do
    # call has_one and belongs_to associations
    alias_method :origin_reader, :reader
    def reader(force_reload = false)
      result = origin_reader(force_reload)
      if Bullet.start?
        unless @inversed
          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
          Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
        end
      end
      result
    end
  end

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

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

  ::ActiveRecord::Associations::CollectionProxy.class_eval do
    def count(column_name = nil, options = {})
      if Bullet.start?
        Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)
        Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)
      end
      super(column_name, options)
    end
  end
end