bullet_instructure 4.0.5 → 4.14.7

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +27 -1
  3. data/CHANGELOG.md +44 -2
  4. data/Gemfile.mongoid +0 -2
  5. data/Gemfile.mongoid-2.4 +2 -4
  6. data/Gemfile.mongoid-2.5 +2 -4
  7. data/Gemfile.mongoid-2.6 +1 -3
  8. data/Gemfile.mongoid-2.7 +2 -4
  9. data/Gemfile.mongoid-2.8 +2 -4
  10. data/Gemfile.mongoid-3.0 +2 -4
  11. data/Gemfile.mongoid-3.1 +2 -4
  12. data/Gemfile.mongoid-4.0 +2 -4
  13. data/Gemfile.rails-3.0 +1 -3
  14. data/Gemfile.rails-3.1 +1 -3
  15. data/Gemfile.rails-3.2 +1 -3
  16. data/Gemfile.rails-4.0 +1 -3
  17. data/Gemfile.rails-4.1 +1 -3
  18. data/Gemfile.rails-4.2 +17 -0
  19. data/README.md +55 -43
  20. data/bullet_instructure.gemspec +2 -1
  21. data/lib/bullet/active_record3.rb +68 -34
  22. data/lib/bullet/active_record3x.rb +60 -32
  23. data/lib/bullet/active_record4.rb +57 -39
  24. data/lib/bullet/active_record41.rb +83 -42
  25. data/lib/bullet/active_record42.rb +195 -0
  26. data/lib/bullet/dependency.rb +6 -0
  27. data/lib/bullet/detector/association.rb +23 -17
  28. data/lib/bullet/detector/counter_cache.rb +16 -16
  29. data/lib/bullet/detector/n_plus_one_query.rb +43 -30
  30. data/lib/bullet/detector/unused_eager_loading.rb +2 -2
  31. data/lib/bullet/ext/object.rb +6 -2
  32. data/lib/bullet/notification/base.rb +17 -18
  33. data/lib/bullet/notification/n_plus_one_query.rb +6 -4
  34. data/lib/bullet/notification/unused_eager_loading.rb +1 -1
  35. data/lib/bullet/rack.rb +21 -14
  36. data/lib/bullet/version.rb +2 -2
  37. data/lib/bullet.rb +18 -10
  38. data/spec/bullet/detector/counter_cache_spec.rb +8 -8
  39. data/spec/bullet/detector/n_plus_one_query_spec.rb +27 -27
  40. data/spec/bullet/ext/object_spec.rb +14 -0
  41. data/spec/bullet/notification/base_spec.rb +30 -18
  42. data/spec/bullet/notification/n_plus_one_query_spec.rb +3 -3
  43. data/spec/bullet/notification/unused_eager_loading_spec.rb +1 -1
  44. data/spec/bullet/rack_spec.rb +3 -3
  45. data/spec/bullet_spec.rb +2 -2
  46. data/spec/integration/active_record3/association_spec.rb +22 -2
  47. data/spec/integration/active_record4/association_spec.rb +47 -2
  48. data/spec/models/category.rb +5 -2
  49. data/spec/models/comment.rb +2 -0
  50. data/spec/models/post.rb +8 -3
  51. data/spec/models/reply.rb +3 -0
  52. data/spec/models/submission.rb +1 -1
  53. data/spec/spec_helper.rb +4 -4
  54. data/spec/support/sqlite_seed.rb +14 -6
  55. data/test.sh +1 -0
  56. data/update.sh +14 -0
  57. metadata +26 -9
  58. data/.ruby-version +0 -1
@@ -8,29 +8,35 @@ module Bullet
8
8
  # if select only one object, then the only one object has impossible to cause N+1 query.
9
9
  def to_a
10
10
  records = origin_to_a
11
- if records.size > 1
12
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
13
- Bullet::Detector::CounterCache.add_possible_objects(records)
14
- elsif records.size == 1
15
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
16
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
11
+ if Bullet.start?
12
+ if records.first.class.name !~ /^HABTM_/
13
+ if records.size > 1
14
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
15
+ Bullet::Detector::CounterCache.add_possible_objects(records)
16
+ elsif records.size == 1
17
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
18
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
19
+ end
20
+ end
17
21
  end
18
22
  records
19
23
  end
20
24
  end
21
25
 
22
26
  ::ActiveRecord::Associations::Preloader.class_eval do
23
- # include query for one to many associations.
24
- # keep this eager loadings.
25
- alias_method :origin_initialize, :initialize
26
- def initialize
27
- origin_initialize
28
- records = [@records].flatten.compact.uniq
29
- return if records.empty?
30
- records.each do |record|
31
- Bullet::Detector::Association.add_object_associations(record, associations)
27
+ alias_method :origin_preloaders_on, :preloaders_on
28
+
29
+ def preloaders_on(association, records, scope)
30
+ if Bullet.start?
31
+ records.compact!
32
+ if records.first.class.name !~ /^HABTM_/
33
+ records.each do |record|
34
+ Bullet::Detector::Association.add_object_associations(record, association)
35
+ end
36
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
37
+ end
32
38
  end
33
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
39
+ origin_preloaders_on(association, records, scope)
34
40
  end
35
41
  end
36
42
 
@@ -38,29 +44,50 @@ module Bullet
38
44
  # add includes in scope
39
45
  alias_method :origin_find_with_associations, :find_with_associations
40
46
  def find_with_associations
41
- if block_given?
42
- origin_find_with_associations { |r| yield r }
43
- else
44
- records = origin_find_with_associations
47
+ return origin_find_with_associations { |r| yield r } if block_given?
48
+ records = origin_find_with_associations
49
+ if Bullet.start?
45
50
  associations = (eager_load_values + includes_values).uniq
46
51
  records.each do |record|
47
52
  Bullet::Detector::Association.add_object_associations(record, associations)
48
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
49
53
  end
50
54
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
51
- records
52
55
  end
56
+ records
53
57
  end
54
58
  end
55
59
 
56
60
  ::ActiveRecord::Associations::JoinDependency.class_eval do
61
+ alias_method :origin_instantiate, :instantiate
57
62
  alias_method :origin_construct_model, :construct_model
63
+
64
+ def instantiate(result_set, aliases)
65
+ @bullet_eager_loadings = {}
66
+ records = origin_instantiate(result_set, aliases)
67
+
68
+ if Bullet.start?
69
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
70
+ objects = eager_loadings_hash.keys
71
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
72
+ end
73
+ end
74
+ records
75
+ end
76
+
58
77
  # call join associations
59
- def construct_model(record, join, row)
60
- associations = join.reflection.name
61
- Bullet::Detector::Association.add_object_associations(record, associations)
62
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
63
- origin_construct_model(record, join, row)
78
+ def construct_model(record, node, row, model_cache, id, aliases)
79
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
80
+
81
+ if Bullet.start?
82
+ associations = node.reflection.name
83
+ Bullet::Detector::Association.add_object_associations(record, associations)
84
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
85
+ @bullet_eager_loadings[record.class] ||= {}
86
+ @bullet_eager_loadings[record.class][record] ||= Set.new
87
+ @bullet_eager_loadings[record.class][record] << associations
88
+ end
89
+
90
+ result
64
91
  end
65
92
  end
66
93
 
@@ -68,9 +95,27 @@ module Bullet
68
95
  # call one to many associations
69
96
  alias_method :origin_load_target, :load_target
70
97
  def load_target
71
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
98
+ if Bullet.start?
99
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
100
+ end
72
101
  origin_load_target
73
102
  end
103
+
104
+ alias_method :origin_empty?, :empty?
105
+ def empty?
106
+ if Bullet.start?
107
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
108
+ end
109
+ origin_empty?
110
+ end
111
+
112
+ alias_method :origin_include?, :include?
113
+ def include?(object)
114
+ if Bullet.start?
115
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
116
+ end
117
+ origin_include?(object)
118
+ end
74
119
  end
75
120
 
76
121
  ::ActiveRecord::Associations::SingularAssociation.class_eval do
@@ -78,28 +123,24 @@ module Bullet
78
123
  alias_method :origin_reader, :reader
79
124
  def reader(force_reload = false)
80
125
  result = origin_reader(force_reload)
81
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
82
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
83
- result
84
- end
85
- end
86
-
87
- ::ActiveRecord::Associations::Association.class_eval do
88
- alias_method :origin_set_inverse_instance, :set_inverse_instance
89
- def set_inverse_instance(record)
90
- if record && invertible_for?(record)
91
- Bullet::Detector::NPlusOneQuery.add_impossible_object(record)
126
+ if Bullet.start?
127
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
128
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
129
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
130
+ end
92
131
  end
93
- origin_set_inverse_instance(record)
132
+ result
94
133
  end
95
134
  end
96
135
 
97
136
  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
98
137
  alias_method :origin_has_cached_counter?, :has_cached_counter?
99
138
 
100
- def has_cached_counter?(reflection = reflection)
139
+ def has_cached_counter?(reflection = reflection())
101
140
  result = origin_has_cached_counter?(reflection)
102
- Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
141
+ if Bullet.start?
142
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
143
+ end
103
144
  result
104
145
  end
105
146
  end
@@ -0,0 +1,195 @@
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::Relation.class_eval do
25
+ alias_method :origin_to_a, :to_a
26
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
27
+ # if select only one object, then the only one object has impossible to cause N+1 query.
28
+ def to_a
29
+ records = origin_to_a
30
+ if Bullet.start?
31
+ if records.first.class.name !~ /^HABTM_/
32
+ if records.size > 1
33
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
34
+ Bullet::Detector::CounterCache.add_possible_objects(records)
35
+ elsif records.size == 1
36
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
37
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
38
+ end
39
+ end
40
+ end
41
+ records
42
+ end
43
+ end
44
+
45
+ ::ActiveRecord::Associations::Preloader.class_eval do
46
+ alias_method :origin_preloaders_on, :preloaders_on
47
+
48
+ def preloaders_on(association, records, scope)
49
+ if Bullet.start?
50
+ records.compact!
51
+ if records.first.class.name !~ /^HABTM_/
52
+ records.each do |record|
53
+ Bullet::Detector::Association.add_object_associations(record, association)
54
+ end
55
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
56
+ end
57
+ end
58
+ origin_preloaders_on(association, records, scope)
59
+ end
60
+ end
61
+
62
+ ::ActiveRecord::FinderMethods.class_eval do
63
+ # add includes in scope
64
+ alias_method :origin_find_with_associations, :find_with_associations
65
+ def find_with_associations
66
+ return origin_find_with_associations { |r| yield r } if block_given?
67
+ records = origin_find_with_associations
68
+ if Bullet.start?
69
+ associations = (eager_load_values + includes_values).uniq
70
+ records.each do |record|
71
+ Bullet::Detector::Association.add_object_associations(record, associations)
72
+ end
73
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
74
+ end
75
+ records
76
+ end
77
+ end
78
+
79
+ ::ActiveRecord::Associations::JoinDependency.class_eval do
80
+ alias_method :origin_instantiate, :instantiate
81
+ alias_method :origin_construct_model, :construct_model
82
+
83
+ def instantiate(result_set, aliases)
84
+ @bullet_eager_loadings = {}
85
+ records = origin_instantiate(result_set, aliases)
86
+
87
+ if Bullet.start?
88
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
89
+ objects = eager_loadings_hash.keys
90
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
91
+ end
92
+ end
93
+ records
94
+ end
95
+
96
+ # call join associations
97
+ def construct_model(record, node, row, model_cache, id, aliases)
98
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
99
+
100
+ if Bullet.start?
101
+ associations = node.reflection.name
102
+ Bullet::Detector::Association.add_object_associations(record, associations)
103
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
104
+ @bullet_eager_loadings[record.class] ||= {}
105
+ @bullet_eager_loadings[record.class][record] ||= Set.new
106
+ @bullet_eager_loadings[record.class][record] << associations
107
+ end
108
+
109
+ result
110
+ end
111
+ end
112
+
113
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
114
+ # call one to many associations
115
+ alias_method :origin_load_target, :load_target
116
+ def load_target
117
+ records = origin_load_target
118
+
119
+ if Bullet.start?
120
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
121
+ if records.first.class.name !~ /^HABTM_/
122
+ if records.size > 1
123
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
124
+ Bullet::Detector::CounterCache.add_possible_objects(records)
125
+ elsif records.size == 1
126
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
127
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
128
+ end
129
+ end
130
+ end
131
+ records
132
+ end
133
+
134
+ alias_method :origin_empty?, :empty?
135
+ def empty?
136
+ if Bullet.start?
137
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
138
+ end
139
+ origin_empty?
140
+ end
141
+
142
+ alias_method :origin_include?, :include?
143
+ def include?(object)
144
+ if Bullet.start?
145
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
146
+ end
147
+ origin_include?(object)
148
+ end
149
+ end
150
+
151
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
152
+ # call has_one and belongs_to associations
153
+ alias_method :origin_reader, :reader
154
+ def reader(force_reload = false)
155
+ result = origin_reader(force_reload)
156
+ if Bullet.start?
157
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
158
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
159
+ if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
160
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
161
+ else
162
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
163
+ end
164
+ end
165
+ end
166
+ result
167
+ end
168
+ end
169
+
170
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
171
+ alias_method :origin_many_empty?, :empty?
172
+ def empty?
173
+ Thread.current[:bullet_collection_empty] = true
174
+ result = origin_many_empty?
175
+ Thread.current[:bullet_collection_empty] = nil
176
+ if Bullet.start?
177
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
178
+ end
179
+ result
180
+ end
181
+
182
+ alias_method :origin_has_cached_counter?, :has_cached_counter?
183
+ def has_cached_counter?(reflection = reflection())
184
+ result = origin_has_cached_counter?(reflection)
185
+ if Bullet.start?
186
+ if !result && !Thread.current[:bullet_collection_empty]
187
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
188
+ end
189
+ end
190
+ result
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -22,6 +22,8 @@ module Bullet
22
22
  'active_record4'
23
23
  elsif active_record41?
24
24
  'active_record41'
25
+ elsif active_record42?
26
+ 'active_record42'
25
27
  end
26
28
  end
27
29
  end
@@ -66,6 +68,10 @@ module Bullet
66
68
  active_record4? && ::ActiveRecord::VERSION::MINOR == 1
67
69
  end
68
70
 
71
+ def active_record42?
72
+ active_record4? && ::ActiveRecord::VERSION::MINOR == 2
73
+ end
74
+
69
75
  def mongoid2x?
70
76
  mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
71
77
  end
@@ -5,7 +5,7 @@ module Bullet
5
5
  def add_object_associations(object, associations)
6
6
  return unless Bullet.start?
7
7
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
8
- return unless object.id
8
+ return unless object.primary_key_value
9
9
 
10
10
  Bullet.debug("Detector::Association#add_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
11
11
  object_associations.add(object.bullet_key, associations)
@@ -14,12 +14,28 @@ module Bullet
14
14
  def add_call_object_associations(object, associations)
15
15
  return unless Bullet.start?
16
16
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
17
- return unless object.id
17
+ return unless object.primary_key_value
18
18
 
19
19
  Bullet.debug("Detector::Association#add_call_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
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,21 +54,11 @@ 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
- # Notice: impossible_objects are not accurate,
52
- # if find collection returns only one object, then the object is impossible object,
53
- # impossible_objects are used to avoid treating 1+1 query to N+1 query.
54
- def impossible_objects
55
- Thread.current[:bullet_impossible_objects]
57
+ # inversed_objects keeps object relationships
58
+ # that association is inversed.
59
+ # e.g. { "Comment:1" => ["post"] }
60
+ def inversed_objects
61
+ Thread.current[:bullet_inversed_objects]
56
62
  end
57
63
 
58
64
  # eager_loadings keep the object relationships
@@ -5,10 +5,10 @@ module Bullet
5
5
  def add_counter_cache(object, associations)
6
6
  return unless Bullet.start?
7
7
  return unless Bullet.counter_cache_enable?
8
- return unless object.id
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
@@ -17,7 +17,7 @@ module Bullet
17
17
  return unless Bullet.start?
18
18
  return unless Bullet.counter_cache_enable?
19
19
  objects = Array(object_or_objects)
20
- return if objects.map(&:id).compact.empty?
20
+ return if objects.map(&:primary_key_value).compact.empty?
21
21
 
22
22
  Bullet.debug("Detector::CounterCache#add_possible_objects", "objects: #{objects.map(&:bullet_key).join(', ')}")
23
23
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -26,12 +26,24 @@ module Bullet
26
26
  def add_impossible_object(object)
27
27
  return unless Bullet.start?
28
28
  return unless Bullet.counter_cache_enable?
29
- return unless object.id
29
+ return unless object.primary_key_value
30
30
 
31
31
  Bullet.debug("Detector::CounterCache#add_impossible_object", "object: #{object.bullet_key}")
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
@@ -10,11 +10,12 @@ module Bullet
10
10
  # if it is, keeps this unpreload associations and caller.
11
11
  def call_association(object, associations)
12
12
  return unless Bullet.start?
13
- return unless object.id
13
+ return unless object.primary_key_value
14
+ return if inversed_objects.include?(object.bullet_key, associations)
14
15
  add_call_object_associations(object, associations)
15
16
 
16
17
  Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_key}, associations: #{associations}")
17
- if conditions_met?(object.bullet_key, associations)
18
+ if conditions_met?(object, associations)
18
19
  Bullet.debug("detect n + 1 query", "object: #{object.bullet_key}, associations: #{associations}")
19
20
  create_notification caller_in_project, object.class.to_s, associations
20
21
  end
@@ -24,7 +25,7 @@ module Bullet
24
25
  return unless Bullet.start?
25
26
  return unless Bullet.n_plus_one_query_enable?
26
27
  objects = Array(object_or_objects)
27
- return if objects.map(&:id).compact.empty?
28
+ return if objects.map(&:primary_key_value).compact.empty?
28
29
 
29
30
  Bullet.debug("Detector::NPlusOneQuery#add_possible_objects", "objects: #{objects.map(&:bullet_key).join(', ')}")
30
31
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -33,12 +34,50 @@ module Bullet
33
34
  def add_impossible_object(object)
34
35
  return unless Bullet.start?
35
36
  return unless Bullet.n_plus_one_query_enable?
36
- return unless object.id
37
+ return unless object.primary_key_value
37
38
 
38
39
  Bullet.debug("Detector::NPlusOneQuery#add_impossible_object", "object: #{object.bullet_key}")
39
40
  impossible_objects.add object.bullet_key
40
41
  end
41
42
 
43
+ def add_inversed_object(object, association)
44
+ return unless Bullet.start?
45
+ return unless Bullet.n_plus_one_query_enable?
46
+ return unless object.primary_key_value
47
+
48
+ Bullet.debug("Detector::NPlusOneQuery#add_inversed_object", "object: #{object.bullet_key}, association: #{association}")
49
+ inversed_objects.add object.bullet_key, association
50
+ end
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.has_key?(associations) : associations == v
74
+ return true if result
75
+ end
76
+ end
77
+
78
+ false
79
+ end
80
+
42
81
  private
43
82
  def create_notification(callers, klazz, associations)
44
83
  notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
@@ -49,11 +88,6 @@ module Bullet
49
88
  end
50
89
  end
51
90
 
52
- # decide whether the object.associations is unpreloaded or not.
53
- def conditions_met?(bullet_key, associations)
54
- possible?(bullet_key) && !impossible?(bullet_key) && !association?(bullet_key, associations)
55
- end
56
-
57
91
  def caller_in_project
58
92
  app_root = rails? ? Rails.root.to_s : Dir.pwd
59
93
  vendor_root = app_root + "/vendor"
@@ -62,27 +96,6 @@ module Bullet
62
96
  Bullet.stacktrace_includes.any? { |include| c.include?(include) }
63
97
  end
64
98
  end
65
-
66
- def possible?(bullet_key)
67
- possible_objects.include? bullet_key
68
- end
69
-
70
- def impossible?(bullet_key)
71
- impossible_objects.include? bullet_key
72
- end
73
-
74
- # check if object => associations already exists in object_associations.
75
- def association?(bullet_key, associations)
76
- value = object_associations[bullet_key]
77
- if value
78
- value.each do |v|
79
- result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
80
- return true if result
81
- end
82
- end
83
-
84
- return false
85
- end
86
99
  end
87
100
  end
88
101
  end
@@ -22,7 +22,7 @@ module Bullet
22
22
  def add_eager_loadings(objects, associations)
23
23
  return unless Bullet.start?
24
24
  return unless Bullet.unused_eager_loading_enable?
25
- return if objects.map(&:id).compact.empty?
25
+ return if objects.map(&:primary_key_value).compact.empty?
26
26
 
27
27
  Bullet.debug("Detector::UnusedEagerLoading#add_eager_loadings", "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
28
28
  bullet_keys = objects.map(&:bullet_key)
@@ -47,7 +47,7 @@ module Bullet
47
47
  end
48
48
  end
49
49
 
50
- eager_loadings.add *to_add if to_add
50
+ eager_loadings.add(*to_add) if to_add
51
51
  to_merge.each { |k,val| eager_loadings.merge k, val }
52
52
  to_delete.each { |k| eager_loadings.delete k }
53
53
 
@@ -1,9 +1,13 @@
1
1
  class Object
2
2
  def bullet_key
3
+ "#{self.class}:#{self.primary_key_value}"
4
+ end
5
+
6
+ def primary_key_value
3
7
  if self.class.respond_to?(:primary_key) && self.class.primary_key
4
- "#{self.class}:#{self.send self.class.primary_key}"
8
+ self.send self.class.primary_key
5
9
  else
6
- "#{self.class}:#{self.id}"
10
+ self.id
7
11
  end
8
12
  end
9
13
  end