bullet 5.7.5 → 6.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 (51) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +17 -14
  4. data/Gemfile.mongoid-7.0 +15 -0
  5. data/Gemfile.rails-4.0 +1 -1
  6. data/Gemfile.rails-4.1 +1 -1
  7. data/Gemfile.rails-4.2 +1 -1
  8. data/Gemfile.rails-5.0 +1 -1
  9. data/Gemfile.rails-5.1 +1 -1
  10. data/Gemfile.rails-5.2 +2 -2
  11. data/Gemfile.rails-6.0 +15 -0
  12. data/README.md +8 -5
  13. data/Rakefile +1 -1
  14. data/bullet.gemspec +8 -3
  15. data/lib/bullet/active_record4.rb +5 -13
  16. data/lib/bullet/active_record41.rb +5 -13
  17. data/lib/bullet/active_record42.rb +6 -13
  18. data/lib/bullet/active_record5.rb +15 -13
  19. data/lib/bullet/active_record52.rb +12 -13
  20. data/lib/bullet/active_record60.rb +245 -0
  21. data/lib/bullet/bullet_xhr.js +58 -0
  22. data/lib/bullet/dependency.rb +17 -5
  23. data/lib/bullet/detector/association.rb +4 -4
  24. data/lib/bullet/detector/counter_cache.rb +4 -3
  25. data/lib/bullet/detector/n_plus_one_query.rb +13 -13
  26. data/lib/bullet/detector/unused_eager_loading.rb +2 -1
  27. data/lib/bullet/ext/object.rb +5 -3
  28. data/lib/bullet/ext/string.rb +1 -1
  29. data/lib/bullet/mongoid4x.rb +1 -0
  30. data/lib/bullet/mongoid5x.rb +1 -0
  31. data/lib/bullet/mongoid6x.rb +5 -4
  32. data/lib/bullet/mongoid7x.rb +61 -0
  33. data/lib/bullet/notification/base.rb +1 -1
  34. data/lib/bullet/rack.rb +45 -27
  35. data/lib/bullet/stack_trace_filter.rb +43 -25
  36. data/lib/bullet/version.rb +1 -1
  37. data/lib/bullet.rb +21 -5
  38. data/lib/generators/bullet/install_generator.rb +3 -3
  39. data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
  40. data/spec/bullet/ext/object_spec.rb +9 -4
  41. data/spec/bullet/rack_spec.rb +12 -3
  42. data/spec/integration/active_record/association_spec.rb +52 -3
  43. data/spec/integration/counter_cache_spec.rb +1 -1
  44. data/spec/models/client.rb +2 -0
  45. data/spec/models/firm.rb +1 -0
  46. data/spec/models/group.rb +4 -0
  47. data/spec/models/post.rb +15 -0
  48. data/spec/support/sqlite_seed.rb +9 -2
  49. data/test.sh +2 -0
  50. data/update.sh +1 -0
  51. metadata +16 -8
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module SaveWithBulletSupport
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
+ yield(self) if block_given?
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActiveRecord
14
+ def self.enable
15
+ require 'active_record'
16
+ ::ActiveRecord::Base.extend(Module.new do
17
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
18
+ result = super
19
+ if Bullet.start?
20
+ if result.is_a? Array
21
+ if result.size > 1
22
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
23
+ Bullet::Detector::CounterCache.add_possible_objects(result)
24
+ elsif result.size == 1
25
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
26
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
27
+ end
28
+ elsif result.is_a? ::ActiveRecord::Base
29
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
30
+ Bullet::Detector::CounterCache.add_impossible_object(result)
31
+ end
32
+ end
33
+ result
34
+ end
35
+ end)
36
+
37
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
38
+
39
+ ::ActiveRecord::Relation.prepend(Module.new do
40
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
41
+ # if select only one object, then the only one object has impossible to cause N+1 query.
42
+ def records
43
+ result = super
44
+ if Bullet.start?
45
+ if result.first.class.name !~ /^HABTM_/
46
+ if result.size > 1
47
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
48
+ Bullet::Detector::CounterCache.add_possible_objects(result)
49
+ elsif result.size == 1
50
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
51
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
52
+ end
53
+ end
54
+ end
55
+ result
56
+ end
57
+ end)
58
+
59
+ ::ActiveRecord::Associations::Preloader.prepend(Module.new do
60
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
61
+ if Bullet.start?
62
+ records.compact!
63
+ if records.first.class.name !~ /^HABTM_/
64
+ records.each do |record|
65
+ Bullet::Detector::Association.add_object_associations(record, association)
66
+ end
67
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
68
+ end
69
+ end
70
+ super
71
+ end
72
+
73
+ def preloaders_for_reflection(reflection, records, scope)
74
+ if Bullet.start?
75
+ records.compact!
76
+ if records.first.class.name !~ /^HABTM_/
77
+ records.each do |record|
78
+ Bullet::Detector::Association.add_object_associations(record, reflection.name)
79
+ end
80
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
81
+ end
82
+ end
83
+ super
84
+ end
85
+ end)
86
+
87
+ ::ActiveRecord::FinderMethods.prepend(Module.new do
88
+ # add includes in scope
89
+ def find_with_associations
90
+ return super { |r| yield r } if block_given?
91
+
92
+ records = super
93
+ if Bullet.start?
94
+ associations = (eager_load_values + includes_values).uniq
95
+ records.each do |record|
96
+ Bullet::Detector::Association.add_object_associations(record, associations)
97
+ end
98
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
99
+ end
100
+ records
101
+ end
102
+ end)
103
+
104
+ ::ActiveRecord::Associations::JoinDependency.prepend(Module.new do
105
+ def instantiate(result_set, &block)
106
+ @bullet_eager_loadings = {}
107
+ records = super
108
+
109
+ if Bullet.start?
110
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
111
+ objects = eager_loadings_hash.keys
112
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
113
+ end
114
+ end
115
+ records
116
+ end
117
+
118
+ def construct(ar_parent, parent, row, seen, model_cache)
119
+ if Bullet.start?
120
+ unless ar_parent.nil?
121
+ parent.children.each do |node|
122
+ key = aliases.column_alias(node, node.primary_key)
123
+ id = row[key]
124
+ next unless id.nil?
125
+
126
+ associations = node.reflection.name
127
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
128
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
129
+ @bullet_eager_loadings[ar_parent.class] ||= {}
130
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
131
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
132
+ end
133
+ end
134
+ end
135
+
136
+ super
137
+ end
138
+
139
+ # call join associations
140
+ def construct_model(record, node, row, model_cache, id)
141
+ result = super
142
+
143
+ if Bullet.start?
144
+ associations = node.reflection.name
145
+ Bullet::Detector::Association.add_object_associations(record, associations)
146
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
147
+ @bullet_eager_loadings[record.class] ||= {}
148
+ @bullet_eager_loadings[record.class][record] ||= Set.new
149
+ @bullet_eager_loadings[record.class][record] << associations
150
+ end
151
+
152
+ result
153
+ end
154
+ end)
155
+
156
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(Module.new do
157
+ def load_target
158
+ records = super
159
+
160
+ if Bullet.start?
161
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
162
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
163
+ association = owner.association reflection.through_reflection.name
164
+ Array(association.target).each do |through_record|
165
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
166
+ end
167
+
168
+ if reflection.through_reflection != through_reflection
169
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
170
+ end
171
+ end
172
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
173
+ if records.first.class.name !~ /^HABTM_/
174
+ if records.size > 1
175
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
176
+ Bullet::Detector::CounterCache.add_possible_objects(records)
177
+ elsif records.size == 1
178
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
179
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
180
+ end
181
+ end
182
+ end
183
+ records
184
+ end
185
+
186
+ def empty?
187
+ if Bullet.start? && !reflection.has_cached_counter?
188
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
189
+ end
190
+ super
191
+ end
192
+
193
+ def include?(object)
194
+ if Bullet.start?
195
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
196
+ end
197
+ super
198
+ end
199
+ end)
200
+
201
+ ::ActiveRecord::Associations::SingularAssociation.prepend(Module.new do
202
+ # call has_one and belongs_to associations
203
+ def target
204
+ result = super()
205
+ if Bullet.start?
206
+ if owner.class.name !~ /^HABTM_/ && !@inversed
207
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
208
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
209
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
210
+ else
211
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
212
+ end
213
+ end
214
+ end
215
+ result
216
+ end
217
+ end)
218
+
219
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(Module.new do
220
+ def empty?
221
+ result = super
222
+ if Bullet.start? && !reflection.has_cached_counter?
223
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
224
+ end
225
+ result
226
+ end
227
+
228
+ def count_records
229
+ result = reflection.has_cached_counter?
230
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
231
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
232
+ end
233
+ super
234
+ end
235
+ end)
236
+
237
+ ::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
238
+ def writer(record)
239
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
240
+ super
241
+ end
242
+ end)
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,58 @@
1
+ (function() {
2
+ var oldOpen = window.XMLHttpRequest.prototype.open;
3
+ var oldSend = window.XMLHttpRequest.prototype.send;
4
+ function newOpen(method, url, async, user, password) {
5
+ this._storedUrl = url;
6
+ return oldOpen.apply(this, arguments);
7
+ }
8
+ function newSend(data) {
9
+ if (this.onload) {
10
+ this._storedOnload = this.onload;
11
+ }
12
+ this.onload = newOnload;
13
+ return oldSend.apply(this, arguments);
14
+ }
15
+ function newOnload() {
16
+ if (
17
+ this._storedUrl.startsWith(
18
+ window.location.protocol + '//' + window.location.host,
19
+ ) ||
20
+ !this._storedUrl.startsWith('http') // For relative paths
21
+ ) {
22
+ var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
23
+ if (bulletFooterText) {
24
+ setTimeout(() => {
25
+ var oldHtml = document
26
+ .getElementById('bullet-footer')
27
+ .innerHTML.split('<br>');
28
+ var header = oldHtml[0];
29
+ oldHtml = oldHtml.slice(1, oldHtml.length);
30
+ var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
31
+ newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
32
+ document.getElementById(
33
+ 'bullet-footer',
34
+ ).innerHTML = `${header}<br>${newHtml.join('<br>')}`;
35
+ }, 0);
36
+ }
37
+ var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
38
+ if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
39
+ setTimeout(() => {
40
+ JSON.parse(bulletConsoleText).forEach(message => {
41
+ if (console.groupCollapsed && console.groupEnd) {
42
+ console.groupCollapsed('Uniform Notifier');
43
+ console.log(message);
44
+ console.groupEnd();
45
+ } else {
46
+ console.log(message);
47
+ }
48
+ });
49
+ }, 0);
50
+ }
51
+ }
52
+ if (this._storedOnload) {
53
+ return this._storedOnload.apply(this, arguments);
54
+ }
55
+ }
56
+ window.XMLHttpRequest.prototype.open = newOpen;
57
+ window.XMLHttpRequest.prototype.send = newSend;
58
+ })();
@@ -10,10 +10,6 @@ module Bullet
10
10
  @active_record ||= defined? ::ActiveRecord
11
11
  end
12
12
 
13
- def rails?
14
- @rails ||= defined? ::Rails
15
- end
16
-
17
13
  def active_record_version
18
14
  @active_record_version ||= begin
19
15
  if active_record40?
@@ -28,8 +24,10 @@ module Bullet
28
24
  'active_record5'
29
25
  elsif active_record52?
30
26
  'active_record52'
27
+ elsif active_record60?
28
+ 'active_record60'
31
29
  else
32
- raise "Bullet does not support active_record #{::ActiveRecord::VERSION} yet"
30
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
33
31
  end
34
32
  end
35
33
  end
@@ -42,6 +40,8 @@ module Bullet
42
40
  'mongoid5x'
43
41
  elsif mongoid6x?
44
42
  'mongoid6x'
43
+ elsif mongoid7x?
44
+ 'mongoid7x'
45
45
  else
46
46
  raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
47
47
  end
@@ -56,6 +56,10 @@ module Bullet
56
56
  active_record? && ::ActiveRecord::VERSION::MAJOR == 5
57
57
  end
58
58
 
59
+ def active_record6?
60
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 6
61
+ end
62
+
59
63
  def active_record40?
60
64
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
61
65
  end
@@ -80,6 +84,10 @@ module Bullet
80
84
  active_record5? && ::ActiveRecord::VERSION::MINOR == 2
81
85
  end
82
86
 
87
+ def active_record60?
88
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 0
89
+ end
90
+
83
91
  def mongoid4x?
84
92
  mongoid? && ::Mongoid::VERSION =~ /\A4/
85
93
  end
@@ -91,5 +99,9 @@ module Bullet
91
99
  def mongoid6x?
92
100
  mongoid? && ::Mongoid::VERSION =~ /\A6/
93
101
  end
102
+
103
+ def mongoid7x?
104
+ mongoid? && ::Mongoid::VERSION =~ /\A7/
105
+ end
94
106
  end
95
107
  end
@@ -7,18 +7,18 @@ module Bullet
7
7
  def add_object_associations(object, associations)
8
8
  return unless Bullet.start?
9
9
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
10
- return unless object.primary_key_value
10
+ return unless object.bullet_primary_key_value
11
11
 
12
- Bullet.debug('Detector::Association#add_object_associations'.freeze, "object: #{object.bullet_key}, associations: #{associations}")
12
+ Bullet.debug('Detector::Association#add_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
13
13
  object_associations.add(object.bullet_key, associations)
14
14
  end
15
15
 
16
16
  def add_call_object_associations(object, associations)
17
17
  return unless Bullet.start?
18
18
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
19
- return unless object.primary_key_value
19
+ return unless object.bullet_primary_key_value
20
20
 
21
- Bullet.debug('Detector::Association#add_call_object_associations'.freeze, "object: #{object.bullet_key}, associations: #{associations}")
21
+ Bullet.debug('Detector::Association#add_call_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
22
22
  call_object_associations.add(object.bullet_key, associations)
23
23
  end
24
24
 
@@ -7,7 +7,7 @@ module Bullet
7
7
  def add_counter_cache(object, associations)
8
8
  return unless Bullet.start?
9
9
  return unless Bullet.counter_cache_enable?
10
- return unless object.primary_key_value
10
+ return unless object.bullet_primary_key_value
11
11
 
12
12
  Bullet.debug('Detector::CounterCache#add_counter_cache', "object: #{object.bullet_key}, associations: #{associations}")
13
13
  if conditions_met?(object, associations)
@@ -18,8 +18,9 @@ module Bullet
18
18
  def add_possible_objects(object_or_objects)
19
19
  return unless Bullet.start?
20
20
  return unless Bullet.counter_cache_enable?
21
+
21
22
  objects = Array(object_or_objects)
22
- return if objects.map(&:primary_key_value).compact.empty?
23
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
23
24
 
24
25
  Bullet.debug('Detector::CounterCache#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
25
26
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -28,7 +29,7 @@ module Bullet
28
29
  def add_impossible_object(object)
29
30
  return unless Bullet.start?
30
31
  return unless Bullet.counter_cache_enable?
31
- return unless object.primary_key_value
32
+ return unless object.bullet_primary_key_value
32
33
 
33
34
  Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
34
35
  impossible_objects.add object.bullet_key
@@ -14,11 +14,12 @@ module Bullet
14
14
  def call_association(object, associations)
15
15
  return unless Bullet.start?
16
16
  return unless Bullet.n_plus_one_query_enable?
17
- return unless object.primary_key_value
17
+ return unless object.bullet_primary_key_value
18
18
  return if inversed_objects.include?(object.bullet_key, associations)
19
+
19
20
  add_call_object_associations(object, associations)
20
21
 
21
- Bullet.debug('Detector::NPlusOneQuery#call_association'.freeze, "object: #{object.bullet_key}, associations: #{associations}")
22
+ Bullet.debug('Detector::NPlusOneQuery#call_association', "object: #{object.bullet_key}, associations: #{associations}")
22
23
  if !excluded_stacktrace_path? && conditions_met?(object, associations)
23
24
  Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
24
25
  create_notification caller_in_project, object.class.to_s, associations
@@ -28,28 +29,29 @@ module Bullet
28
29
  def add_possible_objects(object_or_objects)
29
30
  return unless Bullet.start?
30
31
  return unless Bullet.n_plus_one_query_enable?
32
+
31
33
  objects = Array(object_or_objects)
32
- return if objects.map(&:primary_key_value).compact.empty?
34
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
33
35
 
34
- Bullet.debug('Detector::NPlusOneQuery#add_possible_objects'.freeze, "objects: #{objects.map(&:bullet_key).join(', '.freeze)}")
36
+ Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
35
37
  objects.each { |object| possible_objects.add object.bullet_key }
36
38
  end
37
39
 
38
40
  def add_impossible_object(object)
39
41
  return unless Bullet.start?
40
42
  return unless Bullet.n_plus_one_query_enable?
41
- return unless object.primary_key_value
43
+ return unless object.bullet_primary_key_value
42
44
 
43
- Bullet.debug('Detector::NPlusOneQuery#add_impossible_object'.freeze, "object: #{object.bullet_key}")
45
+ Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
44
46
  impossible_objects.add object.bullet_key
45
47
  end
46
48
 
47
49
  def add_inversed_object(object, association)
48
50
  return unless Bullet.start?
49
51
  return unless Bullet.n_plus_one_query_enable?
50
- return unless object.primary_key_value
52
+ return unless object.bullet_primary_key_value
51
53
 
52
- Bullet.debug('Detector::NPlusOneQuery#add_inversed_object'.freeze, "object: #{object.bullet_key}, association: #{association}")
54
+ Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
53
55
  inversed_objects.add object.bullet_key, association
54
56
  end
55
57
 
@@ -69,14 +71,12 @@ module Bullet
69
71
  # check if object => associations already exists in object_associations.
70
72
  def association?(object, associations)
71
73
  value = object_associations[object.bullet_key]
72
- if value
73
- value.each do |v|
74
+ value&.each do |v|
74
75
  # associations == v comparison order is important here because
75
76
  # v variable might be a squeel node where :== method is redefined,
76
77
  # so it does not compare values at all and return unexpected results
77
- result = v.is_a?(Hash) ? v.key?(associations) : associations == v
78
- return true if result
79
- end
78
+ result = v.is_a?(Hash) ? v.key?(associations) : associations == v
79
+ return true if result
80
80
  end
81
81
 
82
82
  false
@@ -27,7 +27,7 @@ module Bullet
27
27
  def add_eager_loadings(objects, associations)
28
28
  return unless Bullet.start?
29
29
  return unless Bullet.unused_eager_loading_enable?
30
- return if objects.map(&:primary_key_value).compact.empty?
30
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
31
31
 
32
32
  Bullet.debug('Detector::UnusedEagerLoading#add_eager_loadings', "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
33
33
  bullet_keys = objects.map(&:bullet_key)
@@ -75,6 +75,7 @@ module Bullet
75
75
  eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
76
76
  coa = call_object_associations[related_bullet_key]
77
77
  next if coa.nil?
78
+
78
79
  all.merge coa
79
80
  end
80
81
  all.to_a
@@ -2,12 +2,14 @@
2
2
 
3
3
  class Object
4
4
  def bullet_key
5
- "#{self.class}:#{primary_key_value}"
5
+ "#{self.class}:#{bullet_primary_key_value}"
6
6
  end
7
7
 
8
- def primary_key_value
8
+ def bullet_primary_key_value
9
+ return if respond_to?(:persisted?) && !persisted?
10
+
9
11
  if self.class.respond_to?(:primary_keys) && self.class.primary_keys
10
- self.class.primary_keys.map { |primary_key| send primary_key }.join(','.freeze)
12
+ self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
11
13
  elsif self.class.respond_to?(:primary_key) && self.class.primary_key
12
14
  send self.class.primary_key
13
15
  else
@@ -2,6 +2,6 @@
2
2
 
3
3
  class String
4
4
  def bullet_class_name
5
- sub(/:[^:]*?$/, ''.freeze)
5
+ sub(/:[^:]*?$/, '')
6
6
  end
7
7
  end
@@ -24,6 +24,7 @@ module Bullet
24
24
 
25
25
  def each(&block)
26
26
  return to_enum unless block_given?
27
+
27
28
  records = []
28
29
  origin_each { |record| records << record }
29
30
  if records.length > 1
@@ -24,6 +24,7 @@ module Bullet
24
24
 
25
25
  def each(&block)
26
26
  return to_enum unless block_given?
27
+
27
28
  records = []
28
29
  origin_each { |record| records << record }
29
30
  if records.length > 1
@@ -10,20 +10,21 @@ module Bullet
10
10
  alias_method :origin_each, :each
11
11
  alias_method :origin_eager_load, :eager_load
12
12
 
13
- def first
14
- result = origin_first
13
+ def first(opt = {})
14
+ result = origin_first(opt)
15
15
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
16
16
  result
17
17
  end
18
18
 
19
- def last
20
- result = origin_last
19
+ def last(opt = {})
20
+ result = origin_last(opt)
21
21
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
22
22
  result
23
23
  end
24
24
 
25
25
  def each(&block)
26
26
  return to_enum unless block_given?
27
+
27
28
  records = []
28
29
  origin_each { |record| records << record }
29
30
  if records.length > 1
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module Mongoid
5
+ def self.enable
6
+ require 'mongoid'
7
+ ::Mongoid::Contextual::Mongo.class_eval do
8
+ alias_method :origin_first, :first
9
+ alias_method :origin_last, :last
10
+ alias_method :origin_each, :each
11
+ alias_method :origin_eager_load, :eager_load
12
+
13
+ def first(opts = {})
14
+ result = origin_first(opts)
15
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
16
+ result
17
+ end
18
+
19
+ def last(opts = {})
20
+ result = origin_last(opts)
21
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
22
+ result
23
+ end
24
+
25
+ def each(&block)
26
+ return to_enum unless block_given?
27
+
28
+ records = []
29
+ origin_each { |record| records << record }
30
+ if records.length > 1
31
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
32
+ elsif records.size == 1
33
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
34
+ end
35
+ records.each(&block)
36
+ end
37
+
38
+ def eager_load(docs)
39
+ associations = criteria.inclusions.map(&:name)
40
+ docs.each do |doc|
41
+ Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
42
+ end
43
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
44
+ origin_eager_load(docs)
45
+ end
46
+ end
47
+
48
+ ::Mongoid::Association::Accessors.class_eval do
49
+ alias_method :origin_get_relation, :get_relation
50
+
51
+ def get_relation(name, association, object, reload = false)
52
+ result = origin_get_relation(name, association, object, reload)
53
+ unless association.embedded?
54
+ Bullet::Detector::NPlusOneQuery.call_association(self, name)
55
+ end
56
+ result
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -73,7 +73,7 @@ module Bullet
73
73
  protected
74
74
 
75
75
  def klazz_associations_str
76
- " #{@base_class} => [#{@associations.map(&:inspect).join(', '.freeze)}]"
76
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
77
77
  end
78
78
 
79
79
  def associations_str