bullet 4.13.1 → 4.14.10

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +29 -1
  3. data/CHANGELOG.md +53 -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 +1 -3
  13. data/Gemfile.mongoid-5.0 +17 -0
  14. data/Gemfile.rails-3.0 +1 -3
  15. data/Gemfile.rails-3.1 +1 -3
  16. data/Gemfile.rails-3.2 +1 -3
  17. data/Gemfile.rails-4.0 +1 -3
  18. data/Gemfile.rails-4.1 +1 -3
  19. data/Gemfile.rails-4.2 +17 -0
  20. data/README.md +29 -2
  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 +62 -30
  25. data/lib/bullet/active_record41.rb +63 -32
  26. data/lib/bullet/active_record42.rb +214 -0
  27. data/lib/bullet/dependency.rb +12 -0
  28. data/lib/bullet/detector/association.rb +16 -16
  29. data/lib/bullet/detector/counter_cache.rb +13 -13
  30. data/lib/bullet/detector/n_plus_one_query.rb +30 -27
  31. data/lib/bullet/detector/unused_eager_loading.rb +2 -2
  32. data/lib/bullet/ext/object.rb +4 -2
  33. data/lib/bullet/mongoid5x.rb +56 -0
  34. data/lib/bullet/notification/base.rb +7 -11
  35. data/lib/bullet/notification/n_plus_one_query.rb +6 -4
  36. data/lib/bullet/rack.rb +20 -13
  37. data/lib/bullet/version.rb +1 -1
  38. data/lib/bullet.rb +24 -11
  39. data/spec/bullet/detector/counter_cache_spec.rb +8 -8
  40. data/spec/bullet/detector/n_plus_one_query_spec.rb +27 -27
  41. data/spec/bullet/ext/object_spec.rb +6 -0
  42. data/spec/bullet/notification/base_spec.rb +4 -29
  43. data/spec/bullet/notification/n_plus_one_query_spec.rb +3 -3
  44. data/spec/bullet/notification/unused_eager_loading_spec.rb +1 -1
  45. data/spec/bullet/rack_spec.rb +3 -3
  46. data/spec/bullet_spec.rb +47 -0
  47. data/spec/integration/active_record3/association_spec.rb +11 -2
  48. data/spec/integration/active_record4/association_spec.rb +58 -3
  49. data/spec/integration/counter_cache_spec.rb +8 -1
  50. data/spec/models/category.rb +4 -1
  51. data/spec/models/comment.rb +2 -0
  52. data/spec/models/post.rb +7 -2
  53. data/spec/models/reply.rb +3 -0
  54. data/spec/models/submission.rb +1 -1
  55. data/spec/spec_helper.rb +3 -2
  56. data/spec/support/mongo_seed.rb +13 -0
  57. data/spec/support/sqlite_seed.rb +14 -6
  58. data/test.sh +2 -0
  59. data/update.sh +15 -0
  60. metadata +14 -7
@@ -8,29 +8,43 @@ 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.first.class.name !~ /^HABTM_/
12
- if records.size > 1
13
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
14
- Bullet::Detector::CounterCache.add_possible_objects(records)
15
- elsif records.size == 1
16
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
17
- 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
18
20
  end
19
21
  end
20
22
  records
21
23
  end
22
24
  end
23
25
 
26
+ ::ActiveRecord::Persistence.class_eval do
27
+ def save_with_bullet(*args, &proc)
28
+ was_new_record = new_record?
29
+ save_without_bullet(*args, &proc).tap do |result|
30
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
31
+ end
32
+ end
33
+ alias_method_chain :save, :bullet
34
+ end
35
+
24
36
  ::ActiveRecord::Associations::Preloader.class_eval do
25
37
  alias_method :origin_preloaders_on, :preloaders_on
26
38
 
27
39
  def preloaders_on(association, records, scope)
28
- records.compact!
29
- if records.first.class.name !~ /^HABTM_/
30
- records.each do |record|
31
- Bullet::Detector::Association.add_object_associations(record, association)
40
+ if Bullet.start?
41
+ records.compact!
42
+ if records.first.class.name !~ /^HABTM_/
43
+ records.each do |record|
44
+ Bullet::Detector::Association.add_object_associations(record, association)
45
+ end
46
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
32
47
  end
33
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
34
48
  end
35
49
  origin_preloaders_on(association, records, scope)
36
50
  end
@@ -40,12 +54,15 @@ module Bullet
40
54
  # add includes in scope
41
55
  alias_method :origin_find_with_associations, :find_with_associations
42
56
  def find_with_associations
57
+ return origin_find_with_associations { |r| yield r } if block_given?
43
58
  records = origin_find_with_associations
44
- associations = (eager_load_values + includes_values).uniq
45
- records.each do |record|
46
- Bullet::Detector::Association.add_object_associations(record, associations)
59
+ if Bullet.start?
60
+ associations = (eager_load_values + includes_values).uniq
61
+ records.each do |record|
62
+ Bullet::Detector::Association.add_object_associations(record, associations)
63
+ end
64
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
47
65
  end
48
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
49
66
  records
50
67
  end
51
68
  end
@@ -58,9 +75,11 @@ module Bullet
58
75
  @bullet_eager_loadings = {}
59
76
  records = origin_instantiate(result_set, aliases)
60
77
 
61
- @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
62
- objects = eager_loadings_hash.keys
63
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
78
+ if Bullet.start?
79
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
80
+ objects = eager_loadings_hash.keys
81
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
82
+ end
64
83
  end
65
84
  records
66
85
  end
@@ -69,12 +88,14 @@ module Bullet
69
88
  def construct_model(record, node, row, model_cache, id, aliases)
70
89
  result = origin_construct_model(record, node, row, model_cache, id, aliases)
71
90
 
72
- associations = node.reflection.name
73
- Bullet::Detector::Association.add_object_associations(record, associations)
74
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
75
- @bullet_eager_loadings[record.class] ||= {}
76
- @bullet_eager_loadings[record.class][record] ||= Set.new
77
- @bullet_eager_loadings[record.class][record] << associations
91
+ if Bullet.start?
92
+ associations = node.reflection.name
93
+ Bullet::Detector::Association.add_object_associations(record, associations)
94
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
95
+ @bullet_eager_loadings[record.class] ||= {}
96
+ @bullet_eager_loadings[record.class][record] ||= Set.new
97
+ @bullet_eager_loadings[record.class][record] << associations
98
+ end
78
99
 
79
100
  result
80
101
  end
@@ -84,19 +105,25 @@ module Bullet
84
105
  # call one to many associations
85
106
  alias_method :origin_load_target, :load_target
86
107
  def load_target
87
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
108
+ if Bullet.start?
109
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
110
+ end
88
111
  origin_load_target
89
112
  end
90
113
 
91
114
  alias_method :origin_empty?, :empty?
92
115
  def empty?
93
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
116
+ if Bullet.start?
117
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
118
+ end
94
119
  origin_empty?
95
120
  end
96
121
 
97
122
  alias_method :origin_include?, :include?
98
123
  def include?(object)
99
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
124
+ if Bullet.start?
125
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
126
+ end
100
127
  origin_include?(object)
101
128
  end
102
129
  end
@@ -106,9 +133,11 @@ module Bullet
106
133
  alias_method :origin_reader, :reader
107
134
  def reader(force_reload = false)
108
135
  result = origin_reader(force_reload)
109
- if @owner.class.name !~ /^HABTM_/
110
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
111
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
136
+ if Bullet.start?
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
112
141
  end
113
142
  result
114
143
  end
@@ -119,7 +148,9 @@ module Bullet
119
148
 
120
149
  def has_cached_counter?(reflection = reflection())
121
150
  result = origin_has_cached_counter?(reflection)
122
- Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
151
+ if Bullet.start? && !result
152
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
153
+ end
123
154
  result
124
155
  end
125
156
  end
@@ -0,0 +1,214 @@
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::Preloader.class_eval do
56
+ alias_method :origin_preloaders_on, :preloaders_on
57
+
58
+ def preloaders_on(association, records, scope)
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)
66
+ end
67
+ end
68
+ origin_preloaders_on(association, records, scope)
69
+ end
70
+ end
71
+
72
+ ::ActiveRecord::FinderMethods.class_eval do
73
+ # add includes in scope
74
+ alias_method :origin_find_with_associations, :find_with_associations
75
+ def find_with_associations
76
+ return origin_find_with_associations { |r| yield r } if block_given?
77
+ records = origin_find_with_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)
84
+ end
85
+ records
86
+ end
87
+ end
88
+
89
+ ::ActiveRecord::Associations::JoinDependency.class_eval do
90
+ alias_method :origin_instantiate, :instantiate
91
+ alias_method :origin_construct_model, :construct_model
92
+
93
+ def instantiate(result_set, aliases)
94
+ @bullet_eager_loadings = {}
95
+ records = origin_instantiate(result_set, aliases)
96
+
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
102
+ end
103
+ records
104
+ end
105
+
106
+ # call join associations
107
+ def construct_model(record, node, row, model_cache, id, aliases)
108
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
109
+
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
118
+
119
+ result
120
+ end
121
+ end
122
+
123
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
124
+ # call one to many associations
125
+ alias_method :origin_load_target, :load_target
126
+ def load_target
127
+ records = origin_load_target
128
+
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
139
+ end
140
+ end
141
+ records
142
+ end
143
+
144
+ alias_method :origin_empty?, :empty?
145
+ def empty?
146
+ if Bullet.start?
147
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
148
+ end
149
+ origin_empty?
150
+ end
151
+
152
+ alias_method :origin_include?, :include?
153
+ def include?(object)
154
+ if Bullet.start?
155
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
156
+ end
157
+ origin_include?(object)
158
+ end
159
+ end
160
+
161
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
162
+ # call has_one and belongs_to associations
163
+ alias_method :origin_reader, :reader
164
+ def reader(force_reload = false)
165
+ result = origin_reader(force_reload)
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
175
+ end
176
+ result
177
+ end
178
+ end
179
+
180
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
181
+ alias_method :origin_many_empty?, :empty?
182
+ def empty?
183
+ Thread.current[:bullet_collection_empty] = true
184
+ result = origin_many_empty?
185
+ Thread.current[:bullet_collection_empty] = nil
186
+ if Bullet.start?
187
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
188
+ end
189
+ result
190
+ end
191
+
192
+ alias_method :origin_has_cached_counter?, :has_cached_counter?
193
+ def has_cached_counter?(reflection = reflection())
194
+ result = origin_has_cached_counter?(reflection)
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]
207
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
208
+ end
209
+ result
210
+ end
211
+ end
212
+ end
213
+ end
214
+ 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
@@ -34,6 +36,8 @@ module Bullet
34
36
  'mongoid3x'
35
37
  elsif mongoid4x?
36
38
  'mongoid4x'
39
+ elsif mongoid5x?
40
+ 'mongoid5x'
37
41
  end
38
42
  end
39
43
  end
@@ -66,6 +70,10 @@ module Bullet
66
70
  active_record4? && ::ActiveRecord::VERSION::MINOR == 1
67
71
  end
68
72
 
73
+ def active_record42?
74
+ active_record4? && ::ActiveRecord::VERSION::MINOR == 2
75
+ end
76
+
69
77
  def mongoid2x?
70
78
  mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
71
79
  end
@@ -77,5 +85,9 @@ module Bullet
77
85
  def mongoid4x?
78
86
  mongoid? && ::Mongoid::VERSION =~ /\A4/
79
87
  end
88
+
89
+ def mongoid5x?
90
+ mongoid? && ::Mongoid::VERSION =~ /\A5/
91
+ end
80
92
  end
81
93
  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 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"
@@ -72,27 +96,6 @@ module Bullet
72
96
  Bullet.stacktrace_includes.any? { |include| c.include?(include) }
73
97
  end
74
98
  end
75
-
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
92
- end
93
-
94
- return false
95
- end
96
99
  end
97
100
  end
98
101
  end
@@ -47,8 +47,8 @@ module Bullet
47
47
  end
48
48
  end
49
49
 
50
- eager_loadings.add *to_add if to_add
51
- to_merge.each { |k,val| eager_loadings.merge k, val }
50
+ eager_loadings.add(*to_add) if to_add
51
+ to_merge.each { |k, val| eager_loadings.merge k, val }
52
52
  to_delete.each { |k| eager_loadings.delete k }
53
53
 
54
54
  eager_loadings.add bullet_keys, associations unless bullet_keys.empty?
@@ -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
@@ -0,0 +1,56 @@
1
+ module Bullet
2
+ module Mongoid
3
+ def self.enable
4
+ require 'mongoid'
5
+ ::Mongoid::Contextual::Mongo.class_eval do
6
+ alias_method :origin_first, :first
7
+ alias_method :origin_last, :last
8
+ alias_method :origin_each, :each
9
+ alias_method :origin_eager_load, :eager_load
10
+
11
+ def first
12
+ result = origin_first
13
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
14
+ result
15
+ end
16
+
17
+ def last
18
+ result = origin_last
19
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
20
+ result
21
+ end
22
+
23
+ def each(&block)
24
+ records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
25
+ if records.length > 1
26
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
27
+ elsif records.size == 1
28
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
29
+ end
30
+ origin_each(&block)
31
+ end
32
+
33
+ def eager_load(docs)
34
+ associations = criteria.inclusions.map(&:name)
35
+ docs.each do |doc|
36
+ Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
37
+ end
38
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
39
+ origin_eager_load(docs)
40
+ end
41
+ end
42
+
43
+ ::Mongoid::Relations::Accessors.class_eval do
44
+ alias_method :origin_get_relation, :get_relation
45
+
46
+ def get_relation(name, metadata, object, reload = false)
47
+ result = origin_get_relation(name, metadata, object, reload)
48
+ if metadata.macro !~ /embed/
49
+ Bullet::Detector::NPlusOneQuery.call_association(self, name)
50
+ end
51
+ result
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end