bullet 4.13.1 → 5.5.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -11
  4. data/CHANGELOG.md +62 -3
  5. data/Gemfile.mongoid +0 -2
  6. data/Gemfile.mongoid-4.0 +1 -5
  7. data/{Gemfile.mongoid-2.8 → Gemfile.mongoid-5.0} +2 -6
  8. data/{Gemfile.mongoid-2.5 → Gemfile.mongoid-6.0} +2 -6
  9. data/Gemfile.rails-4.0 +2 -5
  10. data/Gemfile.rails-4.1 +2 -5
  11. data/{Gemfile.rails-3.0 → Gemfile.rails-4.2} +3 -6
  12. data/{Gemfile.rails-3.1 → Gemfile.rails-5.0} +2 -6
  13. data/Hacking.md +1 -0
  14. data/README.md +39 -7
  15. data/bullet.gemspec +1 -1
  16. data/lib/bullet/active_record4.rb +109 -33
  17. data/lib/bullet/active_record41.rb +99 -37
  18. data/lib/bullet/active_record42.rb +251 -0
  19. data/lib/bullet/active_record5.rb +243 -0
  20. data/lib/bullet/dependency.rb +34 -28
  21. data/lib/bullet/detector/association.rb +16 -16
  22. data/lib/bullet/detector/counter_cache.rb +13 -13
  23. data/lib/bullet/detector/n_plus_one_query.rb +30 -35
  24. data/lib/bullet/detector/unused_eager_loading.rb +12 -11
  25. data/lib/bullet/ext/object.rb +4 -2
  26. data/lib/bullet/{mongoid3x.rb → mongoid5x.rb} +3 -3
  27. data/lib/bullet/{mongoid2x.rb → mongoid6x.rb} +12 -12
  28. data/lib/bullet/notification/base.rb +9 -13
  29. data/lib/bullet/notification/n_plus_one_query.rb +8 -6
  30. data/lib/bullet/notification/unused_eager_loading.rb +18 -1
  31. data/lib/bullet/rack.rb +28 -16
  32. data/lib/bullet/stack_trace_filter.rb +34 -0
  33. data/lib/bullet/version.rb +1 -1
  34. data/lib/bullet.rb +48 -14
  35. data/spec/bullet/detector/counter_cache_spec.rb +8 -8
  36. data/spec/bullet/detector/n_plus_one_query_spec.rb +46 -31
  37. data/spec/bullet/detector/unused_eager_loading_spec.rb +17 -3
  38. data/spec/bullet/ext/object_spec.rb +6 -0
  39. data/spec/bullet/notification/base_spec.rb +4 -29
  40. data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -4
  41. data/spec/bullet/notification/unused_eager_loading_spec.rb +3 -3
  42. data/spec/bullet/rack_spec.rb +38 -7
  43. data/spec/bullet_spec.rb +93 -0
  44. data/spec/integration/active_record4/association_spec.rb +112 -4
  45. data/spec/integration/{active_record3 → active_record5}/association_spec.rb +121 -15
  46. data/spec/integration/counter_cache_spec.rb +24 -1
  47. data/spec/models/category.rb +4 -1
  48. data/spec/models/comment.rb +2 -0
  49. data/spec/models/post.rb +7 -2
  50. data/spec/models/reply.rb +3 -0
  51. data/spec/models/submission.rb +1 -1
  52. data/spec/spec_helper.rb +3 -5
  53. data/spec/support/mongo_seed.rb +13 -0
  54. data/spec/support/sqlite_seed.rb +15 -6
  55. data/test.sh +4 -10
  56. data/update.sh +7 -0
  57. metadata +21 -23
  58. data/Gemfile.mongoid-2.4 +0 -19
  59. data/Gemfile.mongoid-2.6 +0 -19
  60. data/Gemfile.mongoid-2.7 +0 -19
  61. data/Gemfile.mongoid-3.0 +0 -19
  62. data/Gemfile.mongoid-3.1 +0 -19
  63. data/Gemfile.rails-3.2 +0 -19
  64. data/lib/bullet/active_record3.rb +0 -154
  65. data/lib/bullet/active_record3x.rb +0 -134
@@ -0,0 +1,243 @@
1
+ module Bullet
2
+ module SaveWithBulletSupport
3
+ def save(*args)
4
+ was_new_record = new_record?
5
+ super(*args).tap do |result|
6
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
7
+ end
8
+ end
9
+
10
+ def save!(*args)
11
+ was_new_record = new_record?
12
+ super(*args).tap do |result|
13
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
14
+ end
15
+ end
16
+ end
17
+
18
+ module ActiveRecord
19
+ def self.enable
20
+ require 'active_record'
21
+ ::ActiveRecord::Base.class_eval do
22
+ class <<self
23
+ alias_method :origin_find_by_sql, :find_by_sql
24
+ def find_by_sql(sql, binds = [], preparable: nil)
25
+ result = origin_find_by_sql(sql, binds, preparable: nil)
26
+ if Bullet.start?
27
+ if result.is_a? Array
28
+ if result.size > 1
29
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
30
+ Bullet::Detector::CounterCache.add_possible_objects(result)
31
+ elsif result.size == 1
32
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
33
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
34
+ end
35
+ elsif result.is_a? ::ActiveRecord::Base
36
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
37
+ Bullet::Detector::CounterCache.add_impossible_object(result)
38
+ end
39
+ end
40
+ result
41
+ end
42
+ end
43
+ end
44
+
45
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
46
+
47
+ ::ActiveRecord::Relation.class_eval do
48
+ alias_method :origin_records, :records
49
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
50
+ # if select only one object, then the only one object has impossible to cause N+1 query.
51
+ def records
52
+ result = origin_records
53
+ if Bullet.start?
54
+ if result.first.class.name !~ /^HABTM_/
55
+ if result.size > 1
56
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
57
+ Bullet::Detector::CounterCache.add_possible_objects(result)
58
+ elsif result.size == 1
59
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
60
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
61
+ end
62
+ end
63
+ end
64
+ result
65
+ end
66
+ end
67
+
68
+ ::ActiveRecord::Associations::Preloader.class_eval do
69
+ alias_method :origin_preloaders_for_one, :preloaders_for_one
70
+
71
+ def preloaders_for_one(association, records, scope)
72
+ if Bullet.start?
73
+ records.compact!
74
+ if records.first.class.name !~ /^HABTM_/
75
+ records.each do |record|
76
+ Bullet::Detector::Association.add_object_associations(record, association)
77
+ end
78
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
79
+ end
80
+ end
81
+ origin_preloaders_for_one(association, records, scope)
82
+ end
83
+ end
84
+
85
+ ::ActiveRecord::FinderMethods.class_eval do
86
+ # add includes in scope
87
+ alias_method :origin_find_with_associations, :find_with_associations
88
+ def find_with_associations
89
+ return origin_find_with_associations { |r| yield r } if block_given?
90
+ records = origin_find_with_associations
91
+ if Bullet.start?
92
+ associations = (eager_load_values + includes_values).uniq
93
+ records.each do |record|
94
+ Bullet::Detector::Association.add_object_associations(record, associations)
95
+ end
96
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
97
+ end
98
+ records
99
+ end
100
+ end
101
+
102
+ ::ActiveRecord::Associations::JoinDependency.class_eval do
103
+ alias_method :origin_instantiate, :instantiate
104
+ alias_method :origin_construct, :construct
105
+ alias_method :origin_construct_model, :construct_model
106
+
107
+ def instantiate(result_set, aliases)
108
+ @bullet_eager_loadings = {}
109
+ records = origin_instantiate(result_set, aliases)
110
+
111
+ if Bullet.start?
112
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
113
+ objects = eager_loadings_hash.keys
114
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
115
+ end
116
+ end
117
+ records
118
+ end
119
+
120
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
121
+ if Bullet.start?
122
+ unless ar_parent.nil?
123
+ parent.children.each do |node|
124
+ key = aliases.column_alias(node, node.primary_key)
125
+ id = row[key]
126
+ if id.nil?
127
+ associations = node.reflection.name
128
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
129
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
130
+ @bullet_eager_loadings[ar_parent.class] ||= {}
131
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
132
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
139
+ end
140
+
141
+ # call join associations
142
+ def construct_model(record, node, row, model_cache, id, aliases)
143
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
144
+
145
+ if Bullet.start?
146
+ associations = node.reflection.name
147
+ Bullet::Detector::Association.add_object_associations(record, associations)
148
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
149
+ @bullet_eager_loadings[record.class] ||= {}
150
+ @bullet_eager_loadings[record.class][record] ||= Set.new
151
+ @bullet_eager_loadings[record.class][record] << associations
152
+ end
153
+
154
+ result
155
+ end
156
+ end
157
+
158
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
159
+ # call one to many associations
160
+ alias_method :origin_load_target, :load_target
161
+ def load_target
162
+ records = origin_load_target
163
+
164
+ if Bullet.start?
165
+ if self.is_a? ::ActiveRecord::Associations::ThroughAssociation
166
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
167
+ association = self.owner.association self.through_reflection.name
168
+ Array(association.target).each do |through_record|
169
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
170
+ end
171
+ end
172
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
173
+ if records.first.class.name !~ /^HABTM_/
174
+ if records.size > 1
175
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
176
+ Bullet::Detector::CounterCache.add_possible_objects(records)
177
+ elsif records.size == 1
178
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
179
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
180
+ end
181
+ end
182
+ end
183
+ records
184
+ end
185
+
186
+ alias_method :origin_empty?, :empty?
187
+ def empty?
188
+ if Bullet.start? && !reflection.has_cached_counter?
189
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
190
+ end
191
+ origin_empty?
192
+ end
193
+
194
+ alias_method :origin_include?, :include?
195
+ def include?(object)
196
+ if Bullet.start?
197
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
198
+ end
199
+ origin_include?(object)
200
+ end
201
+ end
202
+
203
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
204
+ # call has_one and belongs_to associations
205
+ alias_method :origin_reader, :reader
206
+ def reader(force_reload = false)
207
+ result = origin_reader(force_reload)
208
+ if Bullet.start?
209
+ if owner.class.name !~ /^HABTM_/ && !@inversed
210
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
211
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
212
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
213
+ else
214
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
215
+ end
216
+ end
217
+ end
218
+ result
219
+ end
220
+ end
221
+
222
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
223
+ alias_method :origin_many_empty?, :empty?
224
+ def empty?
225
+ result = origin_many_empty?
226
+ if Bullet.start? && !reflection.has_cached_counter?
227
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
228
+ end
229
+ result
230
+ end
231
+
232
+ alias_method :origin_count_records, :count_records
233
+ def count_records
234
+ result = reflection.has_cached_counter?
235
+ if Bullet.start? && !result && !self.is_a?(::ActiveRecord::Associations::ThroughAssociation)
236
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
237
+ end
238
+ origin_count_records
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -14,48 +14,42 @@ module Bullet
14
14
 
15
15
  def active_record_version
16
16
  @active_record_version ||= begin
17
- if active_record30?
18
- 'active_record3'
19
- elsif active_record31? || active_record32?
20
- 'active_record3x'
21
- elsif active_record40?
17
+ if active_record40?
22
18
  'active_record4'
23
19
  elsif active_record41?
24
20
  'active_record41'
21
+ elsif active_record42?
22
+ 'active_record42'
23
+ elsif active_record50?
24
+ 'active_record5'
25
+ elsif active_record51?
26
+ 'active_record5'
27
+ else
28
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION} yet"
25
29
  end
26
30
  end
27
31
  end
28
32
 
29
33
  def mongoid_version
30
34
  @mongoid_version ||= begin
31
- if mongoid2x?
32
- 'mongoid2x'
33
- elsif mongoid3x?
34
- 'mongoid3x'
35
- elsif mongoid4x?
35
+ if mongoid4x?
36
36
  'mongoid4x'
37
+ elsif mongoid5x?
38
+ 'mongoid5x'
39
+ elsif mongoid6x?
40
+ 'mongoid6x'
41
+ else
42
+ raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
37
43
  end
38
44
  end
39
45
  end
40
46
 
41
- def active_record3?
42
- active_record? && ::ActiveRecord::VERSION::MAJOR == 3
43
- end
44
-
45
47
  def active_record4?
46
48
  active_record? && ::ActiveRecord::VERSION::MAJOR == 4
47
49
  end
48
50
 
49
- def active_record30?
50
- active_record3? && ::ActiveRecord::VERSION::MINOR == 0
51
- end
52
-
53
- def active_record31?
54
- active_record3? && ::ActiveRecord::VERSION::MINOR == 1
55
- end
56
-
57
- def active_record32?
58
- active_record3? && ::ActiveRecord::VERSION::MINOR == 2
51
+ def active_record5?
52
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 5
59
53
  end
60
54
 
61
55
  def active_record40?
@@ -66,16 +60,28 @@ module Bullet
66
60
  active_record4? && ::ActiveRecord::VERSION::MINOR == 1
67
61
  end
68
62
 
69
- def mongoid2x?
70
- mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
63
+ def active_record42?
64
+ active_record4? && ::ActiveRecord::VERSION::MINOR == 2
71
65
  end
72
66
 
73
- def mongoid3x?
74
- mongoid? && ::Mongoid::VERSION =~ /\A3/
67
+ def active_record50?
68
+ active_record5? && ::ActiveRecord::VERSION::MINOR == 0
69
+ end
70
+
71
+ def active_record51?
72
+ active_record5? && ::ActiveRecord::VERSION::MINOR == 1
75
73
  end
76
74
 
77
75
  def mongoid4x?
78
76
  mongoid? && ::Mongoid::VERSION =~ /\A4/
79
77
  end
78
+
79
+ def mongoid5x?
80
+ mongoid? && ::Mongoid::VERSION =~ /\A5/
81
+ end
82
+
83
+ def mongoid6x?
84
+ mongoid? && ::Mongoid::VERSION =~ /\A6/
85
+ end
80
86
  end
81
87
  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
@@ -2,6 +2,7 @@ module Bullet
2
2
  module Detector
3
3
  class NPlusOneQuery < Association
4
4
  extend Dependency
5
+ extend StackTraceFilter
5
6
 
6
7
  class <<self
7
8
  # executed when object.assocations is called.
@@ -15,7 +16,7 @@ module Bullet
15
16
  add_call_object_associations(object, associations)
16
17
 
17
18
  Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_key}, associations: #{associations}")
18
- if conditions_met?(object.bullet_key, associations)
19
+ if !excluded_stacktrace_path? && conditions_met?(object, associations)
19
20
  Bullet.debug("detect n + 1 query", "object: #{object.bullet_key}, associations: #{associations}")
20
21
  create_notification caller_in_project, object.class.to_s, associations
21
22
  end
@@ -49,49 +50,43 @@ module Bullet
49
50
  inversed_objects.add object.bullet_key, association
50
51
  end
51
52
 
52
- private
53
- def create_notification(callers, klazz, associations)
54
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
53
+ # decide whether the object.associations is unpreloaded or not.
54
+ def conditions_met?(object, associations)
55
+ possible?(object) && !impossible?(object) && !association?(object, associations)
56
+ end
55
57
 
56
- if notify_associations.present?
57
- notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
58
- Bullet.notification_collector.add(notice)
59
- end
60
- end
58
+ def possible?(object)
59
+ possible_objects.include? object.bullet_key
60
+ end
61
61
 
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
62
+ def impossible?(object)
63
+ impossible_objects.include? object.bullet_key
64
+ end
66
65
 
67
- def caller_in_project
68
- app_root = rails? ? Rails.root.to_s : Dir.pwd
69
- vendor_root = app_root + "/vendor"
70
- caller.select do |c|
71
- c.include?(app_root) && !c.include?(vendor_root) ||
72
- Bullet.stacktrace_includes.any? { |include| c.include?(include) }
66
+ # check if object => associations already exists in object_associations.
67
+ def association?(object, associations)
68
+ value = object_associations[object.bullet_key]
69
+ if value
70
+ value.each do |v|
71
+ # associations == v comparision order is important here because
72
+ # v variable might be a squeel node where :== method is redefined,
73
+ # so it does not compare values at all and return unexpected results
74
+ result = v.is_a?(Hash) ? v.key?(associations) : associations == v
75
+ return true if result
73
76
  end
74
77
  end
75
78
 
76
- def possible?(bullet_key)
77
- possible_objects.include? bullet_key
78
- end
79
+ false
80
+ end
79
81
 
80
- def impossible?(bullet_key)
81
- impossible_objects.include? bullet_key
82
- end
82
+ private
83
+ def create_notification(callers, klazz, associations)
84
+ notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
83
85
 
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
86
+ if notify_associations.present?
87
+ notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
88
+ Bullet.notification_collector.add(notice)
92
89
  end
93
-
94
- return false
95
90
  end
96
91
  end
97
92
  end
@@ -1,6 +1,9 @@
1
1
  module Bullet
2
2
  module Detector
3
3
  class UnusedEagerLoading < Association
4
+ extend Dependency
5
+ extend StackTraceFilter
6
+
4
7
  class <<self
5
8
  # check if there are unused preload associations.
6
9
  # get related_objects from eager_loadings associated with object and associations
@@ -15,7 +18,7 @@ module Bullet
15
18
  next if object_association_diff.empty?
16
19
 
17
20
  Bullet.debug("detect unused preload", "object: #{bullet_key}, associations: #{object_association_diff}")
18
- create_notification bullet_key.bullet_class_name, object_association_diff
21
+ create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
19
22
  end
20
23
  end
21
24
 
@@ -27,39 +30,37 @@ module Bullet
27
30
  Bullet.debug("Detector::UnusedEagerLoading#add_eager_loadings", "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
28
31
  bullet_keys = objects.map(&:bullet_key)
29
32
 
30
- to_add = nil
31
- to_merge, to_delete = [], []
33
+ to_add, to_merge, to_delete = [], [], []
32
34
  eager_loadings.each do |k, v|
33
35
  key_objects_overlap = k & bullet_keys
34
36
 
35
37
  next if key_objects_overlap.empty?
36
38
 
39
+ bullet_keys = bullet_keys - k
37
40
  if key_objects_overlap == k
38
- to_add = [k, associations]
39
- break
41
+ to_add << [k, associations]
40
42
  else
41
43
  to_merge << [key_objects_overlap, ( eager_loadings[k].dup << associations )]
42
44
 
43
- keys_without_objects = k - bullet_keys
45
+ keys_without_objects = k - key_objects_overlap
44
46
  to_merge << [keys_without_objects, eager_loadings[k]]
45
47
  to_delete << k
46
- bullet_keys = bullet_keys - k
47
48
  end
48
49
  end
49
50
 
50
- eager_loadings.add *to_add if to_add
51
- to_merge.each { |k,val| eager_loadings.merge k, val }
51
+ to_add.each { |k, val| eager_loadings.add k, val }
52
+ to_merge.each { |k, val| eager_loadings.merge k, val }
52
53
  to_delete.each { |k| eager_loadings.delete k }
53
54
 
54
55
  eager_loadings.add bullet_keys, associations unless bullet_keys.empty?
55
56
  end
56
57
 
57
58
  private
58
- def create_notification(klazz, associations)
59
+ def create_notification(callers, klazz, associations)
59
60
  notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
60
61
 
61
62
  if notify_associations.present?
62
- notice = Bullet::Notification::UnusedEagerLoading.new(klazz, notify_associations)
63
+ notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
63
64
  Bullet.notification_collector.add(notice)
64
65
  end
65
66
  end
@@ -1,10 +1,12 @@
1
1
  class Object
2
2
  def bullet_key
3
- [self.class, self.primary_key_value].join(':')
3
+ "#{self.class}:#{self.primary_key_value}"
4
4
  end
5
5
 
6
6
  def primary_key_value
7
- if self.class.respond_to?(:primary_key) && self.class.primary_key
7
+ if self.class.respond_to?(:primary_keys) && self.class.primary_keys
8
+ self.class.primary_keys.map { |primary_key| self.send primary_key }.join(',')
9
+ elsif self.class.respond_to?(:primary_key) && self.class.primary_key
8
10
  self.send self.class.primary_key
9
11
  else
10
12
  self.id
@@ -21,7 +21,7 @@ module Bullet
21
21
  end
22
22
 
23
23
  def each(&block)
24
- records = query.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
24
+ records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
25
25
  if records.length > 1
26
26
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
27
27
  elsif records.size == 1
@@ -43,8 +43,8 @@ module Bullet
43
43
  ::Mongoid::Relations::Accessors.class_eval do
44
44
  alias_method :origin_get_relation, :get_relation
45
45
 
46
- def get_relation(name, metadata, reload = false)
47
- result = origin_get_relation(name, metadata, reload)
46
+ def get_relation(name, metadata, object, reload = false)
47
+ result = origin_get_relation(name, metadata, object, reload)
48
48
  if metadata.macro !~ /embed/
49
49
  Bullet::Detector::NPlusOneQuery.call_association(self, name)
50
50
  end