bullet 4.14.0 → 5.0.0

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