bullet 4.14.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +56 -1
  3. data/CHANGELOG.md +38 -2
  4. data/Gemfile.mongoid +0 -2
  5. data/Gemfile.mongoid-2.4 +0 -2
  6. data/Gemfile.mongoid-2.5 +0 -2
  7. data/Gemfile.mongoid-2.6 +0 -2
  8. data/Gemfile.mongoid-2.7 +0 -2
  9. data/Gemfile.mongoid-2.8 +0 -2
  10. data/Gemfile.mongoid-3.0 +0 -2
  11. data/Gemfile.mongoid-3.1 +0 -2
  12. data/Gemfile.mongoid-4.0 +0 -2
  13. data/Gemfile.mongoid-5.0 +17 -0
  14. data/Gemfile.rails-3.1 +0 -2
  15. data/Gemfile.rails-3.2 +0 -2
  16. data/Gemfile.rails-4.0 +0 -2
  17. data/Gemfile.rails-4.1 +0 -2
  18. data/Gemfile.rails-4.2 +0 -2
  19. data/Gemfile.rails-5.0 +17 -0
  20. data/README.md +29 -1
  21. data/bullet.gemspec +1 -1
  22. data/lib/bullet/active_record3.rb +65 -35
  23. data/lib/bullet/active_record3x.rb +54 -32
  24. data/lib/bullet/active_record4.rb +61 -31
  25. data/lib/bullet/active_record41.rb +63 -32
  26. data/lib/bullet/active_record42.rb +94 -49
  27. data/lib/bullet/active_record5.rb +217 -0
  28. data/lib/bullet/dependency.rb +16 -0
  29. data/lib/bullet/detector/association.rb +16 -16
  30. data/lib/bullet/detector/counter_cache.rb +13 -13
  31. data/lib/bullet/detector/n_plus_one_query.rb +33 -24
  32. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  33. data/lib/bullet/ext/object.rb +3 -1
  34. data/lib/bullet/mongoid5x.rb +56 -0
  35. data/lib/bullet/notification/n_plus_one_query.rb +6 -0
  36. data/lib/bullet/rack.rb +4 -6
  37. data/lib/bullet/version.rb +1 -1
  38. data/lib/bullet.rb +28 -11
  39. data/spec/bullet/detector/counter_cache_spec.rb +8 -8
  40. data/spec/bullet/detector/n_plus_one_query_spec.rb +42 -27
  41. data/spec/bullet/ext/object_spec.rb +6 -0
  42. data/spec/bullet/notification/base_spec.rb +2 -2
  43. data/spec/bullet/rack_spec.rb +2 -2
  44. data/spec/bullet_spec.rb +47 -0
  45. data/spec/integration/active_record3/association_spec.rb +11 -2
  46. data/spec/integration/active_record4/association_spec.rb +32 -2
  47. data/spec/integration/active_record5/association_spec.rb +715 -0
  48. data/spec/integration/counter_cache_spec.rb +17 -1
  49. data/spec/spec_helper.rb +1 -0
  50. data/spec/support/mongo_seed.rb +13 -0
  51. data/test.sh +1 -0
  52. data/update.sh +1 -0
  53. metadata +13 -7
@@ -7,31 +7,45 @@ module Bullet
7
7
  alias_method :origin_find, :find
8
8
  def find(*args)
9
9
  result = origin_find(*args)
10
- if result.is_a? Array
11
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
12
- Bullet::Detector::CounterCache.add_possible_objects(result)
13
- elsif result.is_a? ::ActiveRecord::Base
14
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
15
- Bullet::Detector::CounterCache.add_impossible_object(result)
10
+ if Bullet.start?
11
+ if result.is_a? Array
12
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
13
+ Bullet::Detector::CounterCache.add_possible_objects(result)
14
+ elsif result.is_a? ::ActiveRecord::Base
15
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
16
+ Bullet::Detector::CounterCache.add_impossible_object(result)
17
+ end
16
18
  end
17
19
  result
18
20
  end
19
21
  end
20
22
  end
21
23
 
24
+ ::ActiveRecord::Persistence.class_eval do
25
+ def save_with_bullet(*args, &proc)
26
+ was_new_record = new_record?
27
+ save_without_bullet(*args, &proc).tap do |result|
28
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
29
+ end
30
+ end
31
+ alias_method_chain :save, :bullet
32
+ end
33
+
22
34
  ::ActiveRecord::Relation.class_eval do
23
35
  alias_method :origin_to_a, :to_a
24
36
  # if select a collection of objects, then these objects have possible to cause N+1 query.
25
37
  # if select only one object, then the only one object has impossible to cause N+1 query.
26
38
  def to_a
27
39
  records = origin_to_a
28
- if records.first.class.name !~ /^HABTM_/
29
- if records.size > 1
30
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
31
- Bullet::Detector::CounterCache.add_possible_objects(records)
32
- elsif records.size == 1
33
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
34
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
40
+ if Bullet.start?
41
+ if records.first.class.name !~ /^HABTM_/
42
+ if records.size > 1
43
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
44
+ Bullet::Detector::CounterCache.add_possible_objects(records)
45
+ elsif records.size == 1
46
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
47
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
48
+ end
35
49
  end
36
50
  end
37
51
  records
@@ -42,12 +56,14 @@ module Bullet
42
56
  alias_method :origin_preloaders_on, :preloaders_on
43
57
 
44
58
  def preloaders_on(association, records, scope)
45
- records.compact!
46
- if records.first.class.name !~ /^HABTM_/
47
- records.each do |record|
48
- Bullet::Detector::Association.add_object_associations(record, association)
59
+ if Bullet.start?
60
+ records.compact!
61
+ if records.first.class.name !~ /^HABTM_/
62
+ records.each do |record|
63
+ Bullet::Detector::Association.add_object_associations(record, association)
64
+ end
65
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
49
66
  end
50
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
51
67
  end
52
68
  origin_preloaders_on(association, records, scope)
53
69
  end
@@ -57,12 +73,15 @@ module Bullet
57
73
  # add includes in scope
58
74
  alias_method :origin_find_with_associations, :find_with_associations
59
75
  def find_with_associations
76
+ return origin_find_with_associations { |r| yield r } if block_given?
60
77
  records = origin_find_with_associations
61
- associations = (eager_load_values + includes_values).uniq
62
- records.each do |record|
63
- Bullet::Detector::Association.add_object_associations(record, associations)
78
+ if Bullet.start?
79
+ associations = (eager_load_values + includes_values).uniq
80
+ records.each do |record|
81
+ Bullet::Detector::Association.add_object_associations(record, associations)
82
+ end
83
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
64
84
  end
65
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
66
85
  records
67
86
  end
68
87
  end
@@ -75,9 +94,11 @@ module Bullet
75
94
  @bullet_eager_loadings = {}
76
95
  records = origin_instantiate(result_set, aliases)
77
96
 
78
- @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
79
- objects = eager_loadings_hash.keys
80
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
97
+ if Bullet.start?
98
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
99
+ objects = eager_loadings_hash.keys
100
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
101
+ end
81
102
  end
82
103
  records
83
104
  end
@@ -86,12 +107,14 @@ module Bullet
86
107
  def construct_model(record, node, row, model_cache, id, aliases)
87
108
  result = origin_construct_model(record, node, row, model_cache, id, aliases)
88
109
 
89
- associations = node.reflection.name
90
- Bullet::Detector::Association.add_object_associations(record, associations)
91
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
92
- @bullet_eager_loadings[record.class] ||= {}
93
- @bullet_eager_loadings[record.class][record] ||= Set.new
94
- @bullet_eager_loadings[record.class][record] << associations
110
+ if Bullet.start?
111
+ associations = node.reflection.name
112
+ Bullet::Detector::Association.add_object_associations(record, associations)
113
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
114
+ @bullet_eager_loadings[record.class] ||= {}
115
+ @bullet_eager_loadings[record.class][record] ||= Set.new
116
+ @bullet_eager_loadings[record.class][record] << associations
117
+ end
95
118
 
96
119
  result
97
120
  end
@@ -101,16 +124,18 @@ module Bullet
101
124
  # call one to many associations
102
125
  alias_method :origin_load_target, :load_target
103
126
  def load_target
104
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
105
127
  records = origin_load_target
106
128
 
107
- if records.first.class.name !~ /^HABTM_/
108
- if records.size > 1
109
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
110
- Bullet::Detector::CounterCache.add_possible_objects(records)
111
- elsif records.size == 1
112
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
113
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
129
+ if Bullet.start?
130
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
131
+ if records.first.class.name !~ /^HABTM_/
132
+ if records.size > 1
133
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
134
+ Bullet::Detector::CounterCache.add_possible_objects(records)
135
+ elsif records.size == 1
136
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
137
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
138
+ end
114
139
  end
115
140
  end
116
141
  records
@@ -118,13 +143,17 @@ module Bullet
118
143
 
119
144
  alias_method :origin_empty?, :empty?
120
145
  def empty?
121
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
146
+ if Bullet.start?
147
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
148
+ end
122
149
  origin_empty?
123
150
  end
124
151
 
125
152
  alias_method :origin_include?, :include?
126
153
  def include?(object)
127
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
154
+ if Bullet.start?
155
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
156
+ end
128
157
  origin_include?(object)
129
158
  end
130
159
  end
@@ -134,12 +163,15 @@ module Bullet
134
163
  alias_method :origin_reader, :reader
135
164
  def reader(force_reload = false)
136
165
  result = origin_reader(force_reload)
137
- if @owner.class.name !~ /^HABTM_/ && !@inversed
138
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
139
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
140
- end
141
- if ::ActiveRecord::Reflection::HasOneReflection === @reflection && result
142
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
166
+ if Bullet.start?
167
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
168
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
169
+ if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
170
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
171
+ else
172
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
173
+ end
174
+ end
143
175
  end
144
176
  result
145
177
  end
@@ -151,14 +183,27 @@ module Bullet
151
183
  Thread.current[:bullet_collection_empty] = true
152
184
  result = origin_many_empty?
153
185
  Thread.current[:bullet_collection_empty] = nil
154
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
186
+ if Bullet.start?
187
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
188
+ end
155
189
  result
156
190
  end
157
191
 
158
192
  alias_method :origin_has_cached_counter?, :has_cached_counter?
159
193
  def has_cached_counter?(reflection = reflection())
160
194
  result = origin_has_cached_counter?(reflection)
161
- if !result && !Thread.current[:bullet_collection_empty]
195
+ if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
196
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
197
+ end
198
+ result
199
+ end
200
+ end
201
+
202
+ ::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
203
+ alias_method :origin_hmt_has_cached_counter?, :has_cached_counter?
204
+ def has_cached_counter?(reflection = reflection())
205
+ result = origin_hmt_has_cached_counter?(reflection)
206
+ if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
162
207
  Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
163
208
  end
164
209
  result
@@ -0,0 +1,217 @@
1
+ module Bullet
2
+ module ActiveRecord
3
+ def self.enable
4
+ require 'active_record'
5
+ ::ActiveRecord::Base.class_eval do
6
+ class <<self
7
+ alias_method :origin_find, :find
8
+ def find(*args)
9
+ result = origin_find(*args)
10
+ if Bullet.start?
11
+ if result.is_a? Array
12
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
13
+ Bullet::Detector::CounterCache.add_possible_objects(result)
14
+ elsif result.is_a? ::ActiveRecord::Base
15
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
16
+ Bullet::Detector::CounterCache.add_impossible_object(result)
17
+ end
18
+ end
19
+ result
20
+ end
21
+ end
22
+ end
23
+
24
+ ::ActiveRecord::Persistence.class_eval do
25
+ def save_with_bullet(*args, &proc)
26
+ was_new_record = new_record?
27
+ save_without_bullet(*args, &proc).tap do |result|
28
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
29
+ end
30
+ end
31
+ alias_method_chain :save, :bullet
32
+ end
33
+
34
+ ::ActiveRecord::Relation.class_eval do
35
+ alias_method :origin_to_a, :to_a
36
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
37
+ # if select only one object, then the only one object has impossible to cause N+1 query.
38
+ def to_a
39
+ records = origin_to_a
40
+ if Bullet.start?
41
+ if records.first.class.name !~ /^HABTM_/
42
+ if records.size > 1
43
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
44
+ Bullet::Detector::CounterCache.add_possible_objects(records)
45
+ elsif records.size == 1
46
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
47
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
48
+ end
49
+ end
50
+ end
51
+ records
52
+ end
53
+ end
54
+
55
+ ::ActiveRecord::Associations::Association.class_eval do
56
+ alias_method :origin_initialize, :initialize
57
+ def initialize(owner, reflection)
58
+ origin_initialize(owner, reflection)
59
+ reflection.set_owner owner
60
+ end
61
+ end
62
+
63
+ ::ActiveRecord::Associations::Preloader.class_eval do
64
+ alias_method :origin_preloaders_on, :preloaders_on
65
+
66
+ def preloaders_on(association, records, scope)
67
+ if Bullet.start?
68
+ records.compact!
69
+ if records.first.class.name !~ /^HABTM_/
70
+ records.each do |record|
71
+ Bullet::Detector::Association.add_object_associations(record, association)
72
+ end
73
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
74
+ end
75
+ end
76
+ origin_preloaders_on(association, records, scope)
77
+ end
78
+ end
79
+
80
+ ::ActiveRecord::FinderMethods.class_eval do
81
+ # add includes in scope
82
+ alias_method :origin_find_with_associations, :find_with_associations
83
+ def find_with_associations
84
+ return origin_find_with_associations { |r| yield r } if block_given?
85
+ records = origin_find_with_associations
86
+ if Bullet.start?
87
+ associations = (eager_load_values + includes_values).uniq
88
+ records.each do |record|
89
+ Bullet::Detector::Association.add_object_associations(record, associations)
90
+ end
91
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
92
+ end
93
+ records
94
+ end
95
+ end
96
+
97
+ ::ActiveRecord::Associations::JoinDependency.class_eval do
98
+ alias_method :origin_instantiate, :instantiate
99
+ alias_method :origin_construct_model, :construct_model
100
+
101
+ def instantiate(result_set, aliases)
102
+ @bullet_eager_loadings = {}
103
+ records = origin_instantiate(result_set, aliases)
104
+
105
+ if Bullet.start?
106
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
107
+ objects = eager_loadings_hash.keys
108
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
109
+ end
110
+ end
111
+ records
112
+ end
113
+
114
+ # call join associations
115
+ def construct_model(record, node, row, model_cache, id, aliases)
116
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
117
+
118
+ if Bullet.start?
119
+ associations = node.reflection.name
120
+ Bullet::Detector::Association.add_object_associations(record, associations)
121
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
122
+ @bullet_eager_loadings[record.class] ||= {}
123
+ @bullet_eager_loadings[record.class][record] ||= Set.new
124
+ @bullet_eager_loadings[record.class][record] << associations
125
+ end
126
+
127
+ result
128
+ end
129
+ end
130
+
131
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
132
+ # call one to many associations
133
+ alias_method :origin_load_target, :load_target
134
+ def load_target
135
+ records = origin_load_target
136
+
137
+ if Bullet.start?
138
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
139
+ if records.first.class.name !~ /^HABTM_/
140
+ if records.size > 1
141
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
142
+ Bullet::Detector::CounterCache.add_possible_objects(records)
143
+ elsif records.size == 1
144
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
145
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
146
+ end
147
+ end
148
+ end
149
+ records
150
+ end
151
+
152
+ alias_method :origin_empty?, :empty?
153
+ def empty?
154
+ if Bullet.start?
155
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
156
+ end
157
+ origin_empty?
158
+ end
159
+
160
+ alias_method :origin_include?, :include?
161
+ def include?(object)
162
+ if Bullet.start?
163
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
164
+ end
165
+ origin_include?(object)
166
+ end
167
+ end
168
+
169
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
170
+ # call has_one and belongs_to associations
171
+ alias_method :origin_reader, :reader
172
+ def reader(force_reload = false)
173
+ result = origin_reader(force_reload)
174
+ if Bullet.start?
175
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
176
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
177
+ if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
178
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
179
+ else
180
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
181
+ end
182
+ end
183
+ end
184
+ result
185
+ end
186
+ end
187
+
188
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
189
+ alias_method :origin_many_empty?, :empty?
190
+ def empty?
191
+ Thread.current[:bullet_collection_empty] = true
192
+ result = origin_many_empty?
193
+ Thread.current[:bullet_collection_empty] = nil
194
+ if Bullet.start?
195
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
196
+ end
197
+ result
198
+ end
199
+ end
200
+
201
+ ::ActiveRecord::Reflection::AbstractReflection.class_eval do
202
+ def set_owner(owner)
203
+ @owner = owner
204
+ end
205
+
206
+ alias_method :origin_has_cached_counter?, :has_cached_counter?
207
+ def has_cached_counter?
208
+ result = origin_has_cached_counter?
209
+ if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
210
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @name)
211
+ end
212
+ result
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -24,6 +24,8 @@ module Bullet
24
24
  'active_record41'
25
25
  elsif active_record42?
26
26
  'active_record42'
27
+ elsif active_record50?
28
+ 'active_record5'
27
29
  end
28
30
  end
29
31
  end
@@ -36,6 +38,8 @@ module Bullet
36
38
  'mongoid3x'
37
39
  elsif mongoid4x?
38
40
  'mongoid4x'
41
+ elsif mongoid5x?
42
+ 'mongoid5x'
39
43
  end
40
44
  end
41
45
  end
@@ -48,6 +52,10 @@ module Bullet
48
52
  active_record? && ::ActiveRecord::VERSION::MAJOR == 4
49
53
  end
50
54
 
55
+ def active_record5?
56
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 5
57
+ end
58
+
51
59
  def active_record30?
52
60
  active_record3? && ::ActiveRecord::VERSION::MINOR == 0
53
61
  end
@@ -72,6 +80,10 @@ module Bullet
72
80
  active_record4? && ::ActiveRecord::VERSION::MINOR == 2
73
81
  end
74
82
 
83
+ def active_record50?
84
+ active_record5? && ::ActiveRecord::VERSION::MINOR == 0
85
+ end
86
+
75
87
  def mongoid2x?
76
88
  mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
77
89
  end
@@ -83,5 +95,9 @@ module Bullet
83
95
  def mongoid4x?
84
96
  mongoid? && ::Mongoid::VERSION =~ /\A4/
85
97
  end
98
+
99
+ def mongoid5x?
100
+ mongoid? && ::Mongoid::VERSION =~ /\A5/
101
+ end
86
102
  end
87
103
  end
@@ -20,6 +20,22 @@ module Bullet
20
20
  call_object_associations.add(object.bullet_key, associations)
21
21
  end
22
22
 
23
+ # possible_objects keep the class to object relationships
24
+ # that the objects may cause N+1 query.
25
+ # e.g. { Post => ["Post:1", "Post:2"] }
26
+ def possible_objects
27
+ Thread.current[:bullet_possible_objects]
28
+ end
29
+
30
+ # impossible_objects keep the class to objects relationships
31
+ # that the objects may not cause N+1 query.
32
+ # e.g. { Post => ["Post:1", "Post:2"] }
33
+ # if find collection returns only one object, then the object is impossible object,
34
+ # impossible_objects are used to avoid treating 1+1 query to N+1 query.
35
+ def impossible_objects
36
+ Thread.current[:bullet_impossible_objects]
37
+ end
38
+
23
39
  private
24
40
  # object_associations keep the object relationships
25
41
  # that the object has many associations.
@@ -38,22 +54,6 @@ module Bullet
38
54
  Thread.current[:bullet_call_object_associations]
39
55
  end
40
56
 
41
- # possible_objects keep the class to object relationships
42
- # that the objects may cause N+1 query.
43
- # e.g. { Post => ["Post:1", "Post:2"] }
44
- def possible_objects
45
- Thread.current[:bullet_possible_objects]
46
- end
47
-
48
- # impossible_objects keep the class to objects relationships
49
- # that the objects may not cause N+1 query.
50
- # e.g. { Post => ["Post:1", "Post:2"] }
51
- # if find collection returns only one object, then the object is impossible object,
52
- # impossible_objects are used to avoid treating 1+1 query to N+1 query.
53
- def impossible_objects
54
- Thread.current[:bullet_impossible_objects]
55
- end
56
-
57
57
  # inversed_objects keeps object relationships
58
58
  # that association is inversed.
59
59
  # e.g. { "Comment:1" => ["post"] }
@@ -8,7 +8,7 @@ module Bullet
8
8
  return unless object.primary_key_value
9
9
 
10
10
  Bullet.debug("Detector::CounterCache#add_counter_cache", "object: #{object.bullet_key}, associations: #{associations}")
11
- if conditions_met?(object.bullet_key, associations)
11
+ if conditions_met?(object, associations)
12
12
  create_notification object.class.to_s, associations
13
13
  end
14
14
  end
@@ -32,6 +32,18 @@ module Bullet
32
32
  impossible_objects.add object.bullet_key
33
33
  end
34
34
 
35
+ def conditions_met?(object, associations)
36
+ possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
37
+ end
38
+
39
+ def possible_objects
40
+ Thread.current[:bullet_counter_possible_objects]
41
+ end
42
+
43
+ def impossible_objects
44
+ Thread.current[:bullet_counter_impossible_objects]
45
+ end
46
+
35
47
  private
36
48
  def create_notification(klazz, associations)
37
49
  notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
@@ -41,18 +53,6 @@ module Bullet
41
53
  Bullet.notification_collector.add notice
42
54
  end
43
55
  end
44
-
45
- def possible_objects
46
- Thread.current[:bullet_counter_possible_objects]
47
- end
48
-
49
- def impossible_objects
50
- Thread.current[:bullet_counter_impossible_objects]
51
- end
52
-
53
- def conditions_met?(bullet_key, associations)
54
- possible_objects.include?(bullet_key) && !impossible_objects.include?(bullet_key)
55
- end
56
56
  end
57
57
  end
58
58
  end
@@ -15,7 +15,7 @@ module Bullet
15
15
  add_call_object_associations(object, associations)
16
16
 
17
17
  Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_key}, associations: #{associations}")
18
- if conditions_met?(object.bullet_key, associations)
18
+ if !excluded_stacktrace_path? && conditions_met?(object, associations)
19
19
  Bullet.debug("detect n + 1 query", "object: #{object.bullet_key}, associations: #{associations}")
20
20
  create_notification caller_in_project, object.class.to_s, associations
21
21
  end
@@ -49,6 +49,35 @@ module Bullet
49
49
  inversed_objects.add object.bullet_key, association
50
50
  end
51
51
 
52
+ # decide whether the object.associations is unpreloaded or not.
53
+ def conditions_met?(object, associations)
54
+ possible?(object) && !impossible?(object) && !association?(object, associations)
55
+ end
56
+
57
+ def possible?(object)
58
+ possible_objects.include? object.bullet_key
59
+ end
60
+
61
+ def impossible?(object)
62
+ impossible_objects.include? object.bullet_key
63
+ end
64
+
65
+ # check if object => associations already exists in object_associations.
66
+ def association?(object, associations)
67
+ value = object_associations[object.bullet_key]
68
+ if value
69
+ value.each do |v|
70
+ # associations == v comparision order is important here because
71
+ # v variable might be a squeel node where :== method is redefined,
72
+ # so it does not compare values at all and return unexpected results
73
+ result = v.is_a?(Hash) ? v.key?(associations) : associations == v
74
+ return true if result
75
+ end
76
+ end
77
+
78
+ false
79
+ end
80
+
52
81
  private
53
82
  def create_notification(callers, klazz, associations)
54
83
  notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
@@ -59,11 +88,6 @@ module Bullet
59
88
  end
60
89
  end
61
90
 
62
- # decide whether the object.associations is unpreloaded or not.
63
- def conditions_met?(bullet_key, associations)
64
- possible?(bullet_key) && !impossible?(bullet_key) && !association?(bullet_key, associations)
65
- end
66
-
67
91
  def caller_in_project
68
92
  app_root = rails? ? Rails.root.to_s : Dir.pwd
69
93
  vendor_root = app_root + "/vendor"
@@ -73,25 +97,10 @@ module Bullet
73
97
  end
74
98
  end
75
99
 
76
- def possible?(bullet_key)
77
- possible_objects.include? bullet_key
78
- end
79
-
80
- def impossible?(bullet_key)
81
- impossible_objects.include? bullet_key
82
- end
83
-
84
- # check if object => associations already exists in object_associations.
85
- def association?(bullet_key, associations)
86
- value = object_associations[bullet_key]
87
- if value
88
- value.each do |v|
89
- result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
90
- return true if result
91
- end
100
+ def excluded_stacktrace_path?
101
+ Bullet.stacktrace_excludes.any? do |excluded_path|
102
+ caller_in_project.any? { |c| c.include?(excluded_path) }
92
103
  end
93
-
94
- return false
95
104
  end
96
105
  end
97
106
  end