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
@@ -2,35 +2,81 @@ module Bullet
2
2
  module ActiveRecord
3
3
  def self.enable
4
4
  require 'active_record'
5
+ ::ActiveRecord::Base.class_eval do
6
+ class <<self
7
+ alias_method :origin_find_by_sql, :find_by_sql
8
+ def find_by_sql(sql, binds = [])
9
+ result = origin_find_by_sql(sql, binds)
10
+ if Bullet.start?
11
+ if result.is_a? Array
12
+ if result.size > 1
13
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
14
+ Bullet::Detector::CounterCache.add_possible_objects(result)
15
+ elsif result.size == 1
16
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
17
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
18
+ end
19
+ elsif result.is_a? ::ActiveRecord::Base
20
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
21
+ Bullet::Detector::CounterCache.add_impossible_object(result)
22
+ end
23
+ end
24
+ result
25
+ end
26
+ end
27
+ end
28
+
5
29
  ::ActiveRecord::Relation.class_eval do
6
30
  alias_method :origin_to_a, :to_a
7
31
  # if select a collection of objects, then these objects have possible to cause N+1 query.
8
32
  # if select only one object, then the only one object has impossible to cause N+1 query.
9
33
  def to_a
10
34
  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)
35
+ if Bullet.start?
36
+ if records.first.class.name !~ /^HABTM_/
37
+ if records.size > 1
38
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
39
+ Bullet::Detector::CounterCache.add_possible_objects(records)
40
+ elsif records.size == 1
41
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
42
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
43
+ end
18
44
  end
19
45
  end
20
46
  records
21
47
  end
22
48
  end
23
49
 
50
+ ::ActiveRecord::Persistence.class_eval do
51
+ def save_with_bullet(*args, &proc)
52
+ was_new_record = new_record?
53
+ save_without_bullet(*args, &proc).tap do |result|
54
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
55
+ end
56
+ end
57
+ alias_method_chain :save, :bullet
58
+
59
+ def save_with_bullet!(*args, &proc)
60
+ was_new_record = new_record?
61
+ save_without_bullet!(*args, &proc).tap do |result|
62
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
63
+ end
64
+ end
65
+ alias_method_chain :save!, :bullet
66
+ end
67
+
24
68
  ::ActiveRecord::Associations::Preloader.class_eval do
25
69
  alias_method :origin_preloaders_on, :preloaders_on
26
70
 
27
71
  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)
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)
32
79
  end
33
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
34
80
  end
35
81
  origin_preloaders_on(association, records, scope)
36
82
  end
@@ -40,12 +86,15 @@ module Bullet
40
86
  # add includes in scope
41
87
  alias_method :origin_find_with_associations, :find_with_associations
42
88
  def find_with_associations
89
+ return origin_find_with_associations { |r| yield r } if block_given?
43
90
  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)
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)
47
97
  end
48
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
49
98
  records
50
99
  end
51
100
  end
@@ -58,9 +107,11 @@ module Bullet
58
107
  @bullet_eager_loadings = {}
59
108
  records = origin_instantiate(result_set, aliases)
60
109
 
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)
110
+ if Bullet.start?
111
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
112
+ objects = eager_loadings_hash.keys
113
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
114
+ end
64
115
  end
65
116
  records
66
117
  end
@@ -69,12 +120,14 @@ module Bullet
69
120
  def construct_model(record, node, row, model_cache, id, aliases)
70
121
  result = origin_construct_model(record, node, row, model_cache, id, aliases)
71
122
 
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
123
+ if Bullet.start?
124
+ associations = node.reflection.name
125
+ Bullet::Detector::Association.add_object_associations(record, associations)
126
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
127
+ @bullet_eager_loadings[record.class] ||= {}
128
+ @bullet_eager_loadings[record.class][record] ||= Set.new
129
+ @bullet_eager_loadings[record.class][record] << associations
130
+ end
78
131
 
79
132
  result
80
133
  end
@@ -84,19 +137,25 @@ module Bullet
84
137
  # call one to many associations
85
138
  alias_method :origin_load_target, :load_target
86
139
  def load_target
87
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
140
+ if Bullet.start?
141
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
142
+ end
88
143
  origin_load_target
89
144
  end
90
145
 
91
146
  alias_method :origin_empty?, :empty?
92
147
  def empty?
93
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
148
+ if Bullet.start? && !has_cached_counter?(@reflection)
149
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
150
+ end
94
151
  origin_empty?
95
152
  end
96
153
 
97
154
  alias_method :origin_include?, :include?
98
155
  def include?(object)
99
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
156
+ if Bullet.start?
157
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
158
+ end
100
159
  origin_include?(object)
101
160
  end
102
161
  end
@@ -106,21 +165,24 @@ module Bullet
106
165
  alias_method :origin_reader, :reader
107
166
  def reader(force_reload = false)
108
167
  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)
168
+ if Bullet.start?
169
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
170
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
171
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
172
+ end
112
173
  end
113
174
  result
114
175
  end
115
176
  end
116
177
 
117
178
  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
118
- alias_method :origin_has_cached_counter?, :has_cached_counter?
119
-
120
- def has_cached_counter?(reflection = reflection())
121
- result = origin_has_cached_counter?(reflection)
122
- Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
123
- result
179
+ alias_method :origin_count_records, :count_records
180
+ def count_records
181
+ result = has_cached_counter?
182
+ if Bullet.start? && !result
183
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name)
184
+ end
185
+ origin_count_records
124
186
  end
125
187
  end
126
188
  end
@@ -0,0 +1,251 @@
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
+
22
+ alias_method :origin_find_by_sql, :find_by_sql
23
+ def find_by_sql(sql, binds = [])
24
+ result = origin_find_by_sql(sql, binds)
25
+ if Bullet.start?
26
+ if result.is_a? Array
27
+ if result.size > 1
28
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
29
+ Bullet::Detector::CounterCache.add_possible_objects(result)
30
+ elsif result.size == 1
31
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
32
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
33
+ end
34
+ elsif result.is_a? ::ActiveRecord::Base
35
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
36
+ Bullet::Detector::CounterCache.add_impossible_object(result)
37
+ end
38
+ end
39
+ result
40
+ end
41
+ end
42
+ end
43
+
44
+ ::ActiveRecord::Persistence.class_eval do
45
+ def save_with_bullet(*args, &proc)
46
+ was_new_record = new_record?
47
+ save_without_bullet(*args, &proc).tap do |result|
48
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
49
+ end
50
+ end
51
+ alias_method_chain :save, :bullet
52
+
53
+ def save_with_bullet!(*args, &proc)
54
+ was_new_record = new_record?
55
+ save_without_bullet!(*args, &proc).tap do |result|
56
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
57
+ end
58
+ end
59
+ alias_method_chain :save!, :bullet
60
+ end
61
+
62
+ ::ActiveRecord::Relation.class_eval do
63
+ alias_method :origin_to_a, :to_a
64
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
65
+ # if select only one object, then the only one object has impossible to cause N+1 query.
66
+ def to_a
67
+ records = origin_to_a
68
+ if Bullet.start?
69
+ if records.first.class.name !~ /^HABTM_/
70
+ if records.size > 1
71
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
72
+ Bullet::Detector::CounterCache.add_possible_objects(records)
73
+ elsif records.size == 1
74
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
75
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
76
+ end
77
+ end
78
+ end
79
+ records
80
+ end
81
+ end
82
+
83
+ ::ActiveRecord::Associations::Preloader.class_eval do
84
+ alias_method :origin_preloaders_on, :preloaders_on
85
+
86
+ def preloaders_on(association, records, scope)
87
+ if Bullet.start?
88
+ records.compact!
89
+ if records.first.class.name !~ /^HABTM_/
90
+ records.each do |record|
91
+ Bullet::Detector::Association.add_object_associations(record, association)
92
+ end
93
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
94
+ end
95
+ end
96
+ origin_preloaders_on(association, records, scope)
97
+ end
98
+ end
99
+
100
+ ::ActiveRecord::FinderMethods.class_eval do
101
+ # add includes in scope
102
+ alias_method :origin_find_with_associations, :find_with_associations
103
+ def find_with_associations
104
+ return origin_find_with_associations { |r| yield r } if block_given?
105
+ records = origin_find_with_associations
106
+ if Bullet.start?
107
+ associations = (eager_load_values + includes_values).uniq
108
+ records.each do |record|
109
+ Bullet::Detector::Association.add_object_associations(record, associations)
110
+ end
111
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
112
+ end
113
+ records
114
+ end
115
+ end
116
+
117
+ ::ActiveRecord::Associations::JoinDependency.class_eval do
118
+ alias_method :origin_instantiate, :instantiate
119
+ alias_method :origin_construct, :construct
120
+ alias_method :origin_construct_model, :construct_model
121
+
122
+ def instantiate(result_set, aliases)
123
+ @bullet_eager_loadings = {}
124
+ records = origin_instantiate(result_set, aliases)
125
+
126
+ if Bullet.start?
127
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
128
+ objects = eager_loadings_hash.keys
129
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
130
+ end
131
+ end
132
+ records
133
+ end
134
+
135
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
136
+ if Bullet.start?
137
+ unless ar_parent.nil?
138
+ parent.children.each do |node|
139
+ key = aliases.column_alias(node, node.primary_key)
140
+ id = row[key]
141
+ if id.nil?
142
+ associations = node.reflection.name
143
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
144
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
145
+ @bullet_eager_loadings[ar_parent.class] ||= {}
146
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
147
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
154
+ end
155
+
156
+ # call join associations
157
+ def construct_model(record, node, row, model_cache, id, aliases)
158
+ result = origin_construct_model(record, node, row, model_cache, id, aliases)
159
+
160
+ if Bullet.start?
161
+ associations = node.reflection.name
162
+ Bullet::Detector::Association.add_object_associations(record, associations)
163
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
164
+ @bullet_eager_loadings[record.class] ||= {}
165
+ @bullet_eager_loadings[record.class][record] ||= Set.new
166
+ @bullet_eager_loadings[record.class][record] << associations
167
+ end
168
+
169
+ result
170
+ end
171
+ end
172
+
173
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
174
+ # call one to many associations
175
+ alias_method :origin_load_target, :load_target
176
+ def load_target
177
+ records = origin_load_target
178
+
179
+ if Bullet.start?
180
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
181
+ if records.first.class.name !~ /^HABTM_/
182
+ if records.size > 1
183
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
184
+ Bullet::Detector::CounterCache.add_possible_objects(records)
185
+ elsif records.size == 1
186
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
187
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
188
+ end
189
+ end
190
+ end
191
+ records
192
+ end
193
+
194
+ alias_method :origin_empty?, :empty?
195
+ def empty?
196
+ if Bullet.start? && !has_cached_counter?(@reflection)
197
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
198
+ end
199
+ origin_empty?
200
+ end
201
+
202
+ alias_method :origin_include?, :include?
203
+ def include?(object)
204
+ if Bullet.start?
205
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
206
+ end
207
+ origin_include?(object)
208
+ end
209
+ end
210
+
211
+ ::ActiveRecord::Associations::SingularAssociation.class_eval do
212
+ # call has_one and belongs_to associations
213
+ alias_method :origin_reader, :reader
214
+ def reader(force_reload = false)
215
+ result = origin_reader(force_reload)
216
+ if Bullet.start?
217
+ if @owner.class.name !~ /^HABTM_/ && !@inversed
218
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
219
+ if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
220
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
221
+ else
222
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
223
+ end
224
+ end
225
+ end
226
+ result
227
+ end
228
+ end
229
+
230
+ ::ActiveRecord::Associations::HasManyAssociation.class_eval do
231
+ alias_method :origin_many_empty?, :empty?
232
+ def empty?
233
+ result = origin_many_empty?
234
+ if Bullet.start? && !has_cached_counter?(@reflection)
235
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
236
+ end
237
+ result
238
+ end
239
+
240
+ alias_method :origin_count_records, :count_records
241
+ def count_records
242
+ result = has_cached_counter?
243
+ if Bullet.start? && !result
244
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name)
245
+ end
246
+ origin_count_records
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end