bullet 5.7.5 → 5.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb27c10f2796dc6cc38899be2b88943f6c124d80
4
- data.tar.gz: c89d95e08212b3bf8da49f2ce4c542ec2aa3021d
3
+ metadata.gz: 0b65855ae5365567a18d1fb6ffdee5046b8cec35
4
+ data.tar.gz: 8df6b7afd18400dd2fe9bf39ec4a537fa04228af
5
5
  SHA512:
6
- metadata.gz: 8f4d67bebf52a6730fa623bb150717e56c052605fc339f37d89d915af0981e442200e2bfee5c3405e6269d0907dc4e4f89fd2965000e637555aa8614580837c0
7
- data.tar.gz: 3fbfc65e37e50444107e153050b33f66fe51037fd9d325438015d51c71f4a5ccb387e9b76ba28a2c753c9f9227653686535c857f457c80d62213cdda070b9c18
6
+ metadata.gz: 212cf02f1520dfaf752f794e42e9b2611fa0eca8b786bf5ef281c36c416ddf24016a524029614447c6241ebb734a2491ab554fa505544d7a7bc6f2e5b767527d
7
+ data.tar.gz: d2d71ceb54033f58cb6d675ea88268f130a0ea8bcfd7b4987486e374a2d2a3fc9169691ff20a9ca0fd9cf2787656dacc19649a8473879e9d1d68484597ccc18b
@@ -1,27 +1,19 @@
1
1
  ## Next Release
2
2
 
3
- ## 5.7.5 (12/03/2018)
4
-
5
- * Fix duplicate logs in mongoid 4.x and 5.x version
6
- * Add magic comment frozen_string_literal: true
7
-
8
- ## 5.7.4 (10/03/2018)
3
+ ## 5.8.0 (10/29/2018)
9
4
 
5
+ * Fix through reflection for rails 5.x
6
+ * Fix false positive in after_save/after_create callbacks
7
+ * Don't triger a preload error on "manual" preloads
10
8
  * Avoid Bullet from making extra queries in mongoid6
9
+ * Support option for #first and #last on mongoid6.x
10
+ * Fix duplicate logs in mongoid 4.x and 5.x version
11
11
  * Use caller for ruby 1.9 while caller_locations for 2.0+
12
-
13
- ## 5.7.3 (17/02/2018)
14
-
12
+ * Extend stacktrace matching for sub-file precision
15
13
  * Exclude configured bundler path in addition to '/vendor'
16
- * Support rails 5.1.5
17
-
18
- ## 5.7.2 (18/01/2018)
19
-
20
14
  * Fix `caller_path` in `excluded_stacktrace_path`
21
-
22
- ## 5.7.1 (07/01/2017)
23
-
24
15
  * Update `uniform_notifier` dependency to add Sentry support
16
+ * Integrate awesomecode.io and refactor code
25
17
 
26
18
  ## 5.7.0 (12/03/2017)
27
19
 
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rails', '~> 5.2.0.beta2'
5
+ gem 'rails', '~> 5.2.0'
6
6
  gem 'sqlite3'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/bullet.png)](http://badge.fury.io/rb/bullet)
4
4
  [![Build Status](https://secure.travis-ci.org/flyerhzm/bullet.png)](http://travis-ci.org/flyerhzm/bullet)
5
- [![AwesomeCode Status](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/projects/2)
5
+ [![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)
6
6
  [![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
7
7
 
8
8
  The Bullet gem is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you're using eager loading that isn't necessary and when you should use counter cache.
@@ -64,7 +64,7 @@ config.after_initialize do
64
64
  Bullet.rollbar = true
65
65
  Bullet.add_footer = true
66
66
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
67
- Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
67
+ Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
68
68
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
69
69
  end
70
70
  ```
@@ -87,6 +87,8 @@ The code above will enable all of the Bullet notification systems:
87
87
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
88
88
  * `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
89
89
  * `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
90
+ Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
91
+ item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
90
92
  * `Bullet.slack`: add notifications to slack
91
93
  * `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
92
94
 
@@ -126,10 +128,11 @@ class ApplicationController < ActionController::Base
126
128
  around_action :skip_bullet
127
129
 
128
130
  def skip_bullet
131
+ previous_value = Bullet.enable?
129
132
  Bullet.enable = false
130
133
  yield
131
134
  ensure
132
- Bullet.enable = true
135
+ Bullet.enable = previous_value
133
136
  end
134
137
  end
135
138
  ```
@@ -180,7 +183,7 @@ If you find Bullet does not work for you, *please disable your browser's cache*.
180
183
 
181
184
  ### Profile a job
182
185
 
183
- The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use use profile method and fetch warnings
186
+ The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use the profile method and fetch warnings
184
187
 
185
188
  ```ruby
186
189
  Bullet.profile do
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
1
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
2
2
  require 'bundler'
3
3
  Bundler.setup
4
4
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib/', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require 'bullet/version'
@@ -14,13 +14,17 @@ Gem::Specification.new do |s|
14
14
  s.homepage = 'https://github.com/flyerhzm/bullet'
15
15
  s.summary = 'help to kill N+1 queries and unused eager loading.'
16
16
  s.description = 'help to kill N+1 queries and unused eager loading.'
17
+ s.metadata = {
18
+ 'changelog_uri' => 'https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md',
19
+ 'source_code_uri' => 'https://github.com/flyerhzm/bullet'
20
+ }
17
21
 
18
- s.license = 'MIT'
22
+ s.license = 'MIT'
19
23
 
20
24
  s.required_rubygems_version = '>= 1.3.6'
21
25
 
22
26
  s.add_runtime_dependency 'activesupport', '>= 3.0.0'
23
- s.add_runtime_dependency 'uniform_notifier', '~> 1.11.0'
27
+ s.add_runtime_dependency 'uniform_notifier', '~> 1.11'
24
28
 
25
29
  s.files = `git ls-files`.split("\n")
26
30
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -19,8 +19,8 @@ module Bullet
19
19
  autoload :Registry, 'bullet/registry'
20
20
  autoload :NotificationCollector, 'bullet/notification_collector'
21
21
 
22
- BULLET_DEBUG = 'BULLET_DEBUG'.freeze
23
- TRUE = 'true'.freeze
22
+ BULLET_DEBUG = 'BULLET_DEBUG'
23
+ TRUE = 'true'
24
24
 
25
25
  if defined? Rails::Railtie
26
26
  class BulletRailtie < Rails::Railtie
@@ -163,6 +163,7 @@ module Bullet
163
163
 
164
164
  def notification?
165
165
  return unless start?
166
+
166
167
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
167
168
  notification_collector.notifications_present?
168
169
  end
@@ -48,21 +48,12 @@ module Bullet
48
48
  end
49
49
 
50
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
51
+ def _create_record_with_bullet(*args)
52
+ _create_record_without_bullet(*args).tap do
53
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
55
54
  end
56
55
  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
56
+ alias_method_chain :_create_record, :bullet
66
57
  end
67
58
 
68
59
  ::ActiveRecord::Associations::Preloader.class_eval do
@@ -74,6 +65,7 @@ module Bullet
74
65
  if Bullet.start?
75
66
  records = [records].flatten.compact.uniq
76
67
  return if records.empty?
68
+
77
69
  records.each do |record|
78
70
  Bullet::Detector::Association.add_object_associations(record, associations)
79
71
  end
@@ -199,6 +191,14 @@ module Bullet
199
191
  end
200
192
  # rubocop:enable Style/MethodCallWithoutArgsParentheses
201
193
  end
194
+
195
+ ::ActiveRecord::Associations::BelongsToAssociation.class_eval do
196
+ def writer_with_bullet(record)
197
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
198
+ writer_without_bullet(record)
199
+ end
200
+ alias_method_chain :writer, :bullet
201
+ end
202
202
  end
203
203
  end
204
204
  end
@@ -50,21 +50,12 @@ module Bullet
50
50
  end
51
51
 
52
52
  ::ActiveRecord::Persistence.class_eval do
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
53
+ def _create_record_with_bullet(*args)
54
+ _create_record_without_bullet(*args).tap do
55
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
57
56
  end
58
57
  end
59
- alias_method_chain :save, :bullet
60
-
61
- def save_with_bullet!(*args, &proc)
62
- was_new_record = new_record?
63
- save_without_bullet!(*args, &proc).tap do |result|
64
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
65
- end
66
- end
67
- alias_method_chain :save!, :bullet
58
+ alias_method_chain :_create_record, :bullet
68
59
  end
69
60
 
70
61
  ::ActiveRecord::Associations::Preloader.class_eval do
@@ -89,6 +80,7 @@ module Bullet
89
80
  alias_method :origin_find_with_associations, :find_with_associations
90
81
  def find_with_associations
91
82
  return origin_find_with_associations { |r| yield r } if block_given?
83
+
92
84
  records = origin_find_with_associations
93
85
  if Bullet.start?
94
86
  associations = (eager_load_values + includes_values).uniq
@@ -187,6 +179,14 @@ module Bullet
187
179
  origin_count_records
188
180
  end
189
181
  end
182
+
183
+ ::ActiveRecord::Associations::BelongsToAssociation.class_eval do
184
+ def writer_with_bullet(record)
185
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
186
+ writer_without_bullet(record)
187
+ end
188
+ alias_method_chain :writer, :bullet
189
+ end
190
190
  end
191
191
  end
192
192
  end
@@ -44,21 +44,12 @@ module Bullet
44
44
  end
45
45
 
46
46
  ::ActiveRecord::Persistence.class_eval do
47
- def save_with_bullet(*args, &proc)
48
- was_new_record = new_record?
49
- save_without_bullet(*args, &proc).tap do |result|
50
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
47
+ def _create_record_with_bullet(*args)
48
+ _create_record_without_bullet(*args).tap do
49
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
51
50
  end
52
51
  end
53
- alias_method_chain :save, :bullet
54
-
55
- def save_with_bullet!(*args, &proc)
56
- was_new_record = new_record?
57
- save_without_bullet!(*args, &proc).tap do |result|
58
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
59
- end
60
- end
61
- alias_method_chain :save!, :bullet
52
+ alias_method_chain :_create_record, :bullet
62
53
  end
63
54
 
64
55
  ::ActiveRecord::Relation.class_eval do
@@ -104,6 +95,7 @@ module Bullet
104
95
  alias_method :origin_find_with_associations, :find_with_associations
105
96
  def find_with_associations
106
97
  return origin_find_with_associations { |r| yield r } if block_given?
98
+
107
99
  records = origin_find_with_associations
108
100
  if Bullet.start?
109
101
  associations = (eager_load_values + includes_values).uniq
@@ -141,6 +133,7 @@ module Bullet
141
133
  key = aliases.column_alias(node, node.primary_key)
142
134
  id = row[key]
143
135
  next unless id.nil?
136
+
144
137
  associations = node.reflection.name
145
138
  Bullet::Detector::Association.add_object_associations(ar_parent, associations)
146
139
  Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
@@ -247,6 +240,14 @@ module Bullet
247
240
  origin_count_records
248
241
  end
249
242
  end
243
+
244
+ ::ActiveRecord::Associations::BelongsToAssociation.class_eval do
245
+ def writer_with_bullet(record)
246
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
247
+ writer_without_bullet(record)
248
+ end
249
+ alias_method_chain :writer, :bullet
250
+ end
250
251
  end
251
252
  end
252
253
  end
@@ -2,17 +2,10 @@
2
2
 
3
3
  module Bullet
4
4
  module SaveWithBulletSupport
5
- def save(*args)
6
- was_new_record = new_record?
7
- super(*args).tap do |result|
8
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
9
- end
10
- end
11
-
12
- def save!(*args)
13
- was_new_record = new_record?
14
- super(*args).tap do |result|
15
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
+ yield(self) if block_given?
16
9
  end
17
10
  end
18
11
  end
@@ -82,6 +75,7 @@ module Bullet
82
75
  # add includes in scope
83
76
  def find_with_associations
84
77
  return super { |r| yield r } if block_given?
78
+
85
79
  records = super
86
80
  if Bullet.start?
87
81
  associations = (eager_load_values + includes_values).uniq
@@ -132,6 +126,7 @@ module Bullet
132
126
  key = aliases.column_alias(node, node.primary_key)
133
127
  id = row[key]
134
128
  next unless id.nil?
129
+
135
130
  associations = node.reflection.name
136
131
  Bullet::Detector::Association.add_object_associations(ar_parent, associations)
137
132
  Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
@@ -168,11 +163,20 @@ module Bullet
168
163
 
169
164
  if Bullet.start?
170
165
  if is_a? ::ActiveRecord::Associations::ThroughAssociation
171
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
172
- association = owner.association through_reflection.name
166
+ refl = reflection.through_reflection
167
+ Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
168
+ association = owner.association refl.name
173
169
  Array(association.target).each do |through_record|
174
170
  Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
175
171
  end
172
+
173
+ if refl.through_reflection?
174
+ while refl.through_reflection?
175
+ refl = refl.through_reflection
176
+ end
177
+
178
+ Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
179
+ end
176
180
  end
177
181
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
178
182
  if records.first.class.name !~ /^HABTM_/
@@ -238,6 +242,13 @@ module Bullet
238
242
  super
239
243
  end
240
244
  end)
245
+
246
+ ::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
247
+ def writer(record)
248
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
249
+ super
250
+ end
251
+ end)
241
252
  end
242
253
  end
243
254
  end
@@ -2,17 +2,10 @@
2
2
 
3
3
  module Bullet
4
4
  module SaveWithBulletSupport
5
- def save(*args)
6
- was_new_record = new_record?
7
- super(*args).tap do |result|
8
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
9
- end
10
- end
11
-
12
- def save!(*args)
13
- was_new_record = new_record?
14
- super(*args).tap do |result|
15
- Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
+ yield(self) if block_given?
16
9
  end
17
10
  end
18
11
  end
@@ -82,6 +75,7 @@ module Bullet
82
75
  # add includes in scope
83
76
  def find_with_associations
84
77
  return super { |r| yield r } if block_given?
78
+
85
79
  records = super
86
80
  if Bullet.start?
87
81
  associations = (eager_load_values + includes_values).uniq
@@ -115,6 +109,7 @@ module Bullet
115
109
  key = aliases.column_alias(node, node.primary_key)
116
110
  id = row[key]
117
111
  next unless id.nil?
112
+
118
113
  associations = node.reflection.name
119
114
  Bullet::Detector::Association.add_object_associations(ar_parent, associations)
120
115
  Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
@@ -151,11 +146,15 @@ module Bullet
151
146
 
152
147
  if Bullet.start?
153
148
  if is_a? ::ActiveRecord::Associations::ThroughAssociation
154
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
155
- association = owner.association through_reflection.name
149
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
150
+ association = owner.association reflection.through_reflection.name
156
151
  Array(association.target).each do |through_record|
157
152
  Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
158
153
  end
154
+
155
+ if reflection.through_reflection != through_reflection
156
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
157
+ end
159
158
  end
160
159
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
161
160
  if records.first.class.name !~ /^HABTM_/
@@ -221,6 +220,13 @@ module Bullet
221
220
  super
222
221
  end
223
222
  end)
223
+
224
+ ::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
225
+ def writer(record)
226
+ Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
227
+ super
228
+ end
229
+ end)
224
230
  end
225
231
  end
226
232
  end
@@ -29,7 +29,7 @@ module Bullet
29
29
  elsif active_record52?
30
30
  'active_record52'
31
31
  else
32
- raise "Bullet does not support active_record #{::ActiveRecord::VERSION} yet"
32
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
33
33
  end
34
34
  end
35
35
  end
@@ -9,7 +9,7 @@ module Bullet
9
9
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
10
10
  return unless object.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
 
@@ -18,7 +18,7 @@ module Bullet
18
18
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
19
19
  return unless object.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
 
@@ -18,6 +18,7 @@ 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
23
  return if objects.map(&:primary_key_value).compact.empty?
23
24
 
@@ -16,9 +16,10 @@ module Bullet
16
16
  return unless Bullet.n_plus_one_query_enable?
17
17
  return unless object.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,10 +29,11 @@ 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
34
  return if objects.map(&: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
 
@@ -40,7 +42,7 @@ module Bullet
40
42
  return unless Bullet.n_plus_one_query_enable?
41
43
  return unless object.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
 
@@ -49,7 +51,7 @@ module Bullet
49
51
  return unless Bullet.n_plus_one_query_enable?
50
52
  return unless object.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
@@ -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
@@ -7,7 +7,7 @@ class Object
7
7
 
8
8
  def primary_key_value
9
9
  if self.class.respond_to?(:primary_keys) && self.class.primary_keys
10
- self.class.primary_keys.map { |primary_key| send primary_key }.join(','.freeze)
10
+ self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
11
11
  elsif self.class.respond_to?(:primary_key) && self.class.primary_key
12
12
  send self.class.primary_key
13
13
  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
@@ -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
@@ -10,6 +10,7 @@ module Bullet
10
10
 
11
11
  def call(env)
12
12
  return @app.call(env) unless Bullet.enable?
13
+
13
14
  Bullet.start_request
14
15
  status, headers, response = @app.call(env)
15
16
 
@@ -66,7 +67,7 @@ module Bullet
66
67
  end
67
68
 
68
69
  def html_request?(headers, response)
69
- headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response_body(response).include?('<html')
70
+ headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
70
71
  end
71
72
 
72
73
  def response_body(response)
@@ -80,13 +81,13 @@ module Bullet
80
81
  private
81
82
 
82
83
  def footer_div_attributes
83
- <<EOF
84
- data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
85
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
86
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
87
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
88
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
89
- EOF
84
+ <<~EOF
85
+ data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
86
+ -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
87
+ -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
88
+ padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
89
+ color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
90
+ EOF
90
91
  end
91
92
 
92
93
  def footer_close_button
@@ -2,52 +2,71 @@
2
2
 
3
3
  module Bullet
4
4
  module StackTraceFilter
5
- VENDOR_PATH = '/vendor'.freeze
5
+ VENDOR_PATH = '/vendor'
6
6
 
7
7
  def caller_in_project
8
8
  app_root = rails? ? Rails.root.to_s : Dir.pwd
9
9
  vendor_root = app_root + VENDOR_PATH
10
10
  bundler_path = Bundler.bundle_path.to_s
11
- select_caller_locations do |caller_path|
11
+ select_caller_locations do |location|
12
+ caller_path = location_as_path(location)
12
13
  caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
13
- Bullet.stacktrace_includes.any? do |include_pattern|
14
- case include_pattern
15
- when String
16
- caller_path.include?(include_pattern)
17
- when Regexp
18
- caller_path =~ include_pattern
19
- end
20
- end
14
+ Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
21
15
  end
22
16
  end
23
17
 
24
18
  def excluded_stacktrace_path?
25
19
  Bullet.stacktrace_excludes.any? do |exclude_pattern|
26
- caller_in_project.any? do |location|
27
- caller_path = location.absolute_path.to_s
28
- case exclude_pattern
29
- when String
30
- caller_path.include?(exclude_pattern)
31
- when Regexp
32
- caller_path =~ exclude_pattern
33
- end
34
- end
20
+ caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
35
21
  end
36
22
  end
37
23
 
38
24
  private
39
25
 
26
+ def pattern_matches?(location, pattern)
27
+ path = location_as_path(location)
28
+ case pattern
29
+ when Array
30
+ pattern_path = pattern.first
31
+ filter = pattern.last
32
+ return false unless pattern_matches?(location, pattern_path)
33
+
34
+ case filter
35
+ when Range
36
+ filter.include?(location.lineno)
37
+ when Integer
38
+ filter == location.lineno
39
+ when String
40
+ filter == location.base_label
41
+ end
42
+ when String
43
+ path.include?(pattern)
44
+ when Regexp
45
+ path =~ pattern
46
+ end
47
+ end
48
+
49
+ def location_as_path(location)
50
+ ruby_19? ? location : location.absolute_path.to_s
51
+ end
52
+
40
53
  def select_caller_locations
41
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
54
+ if ruby_19?
42
55
  caller.select do |caller_path|
43
56
  yield caller_path
44
57
  end
45
58
  else
46
59
  caller_locations.select do |location|
47
- caller_path = location.absolute_path.to_s
48
- yield caller_path
60
+ yield location
49
61
  end
50
62
  end
51
63
  end
64
+
65
+ def ruby_19?
66
+ if @ruby_19.nil?
67
+ @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
68
+ end
69
+ @ruby_19
70
+ end
52
71
  end
53
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '5.7.5'.freeze
4
+ VERSION = '5.8.0'
5
5
  end
@@ -3,9 +3,9 @@
3
3
  module Bullet
4
4
  module Generators
5
5
  class InstallGenerator < ::Rails::Generators::Base
6
- desc <<-DESC
7
- Description:
8
- Enable bullet in development/test for your application.
6
+ desc <<~DESC
7
+ Description:
8
+ Enable bullet in development/test for your application.
9
9
  DESC
10
10
 
11
11
  def enable_in_development
@@ -101,6 +101,29 @@ module Bullet
101
101
  expect(NPlusOneQuery).to_not receive(:create_notification)
102
102
  NPlusOneQuery.call_association(@post, :association)
103
103
  end
104
+
105
+ # just a sanity spec to make sure the following spec works correctly
106
+ it "should create notification when stacktrace contains methods that aren't in the exclude list" do
107
+ method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
108
+ in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
109
+ excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
110
+
111
+ expect(NPlusOneQuery).to receive(:caller_locations).at_least(1).and_return([in_project, excluded_path])
112
+ expect(NPlusOneQuery).to receive(:conditions_met?).and_return(true)
113
+ expect(NPlusOneQuery).to receive(:create_notification)
114
+ NPlusOneQuery.call_association(@post, :association)
115
+ end
116
+
117
+ it 'should not create notification when stacktrace contains methods that are in the exclude list' do
118
+ method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
119
+ Bullet.stacktrace_excludes = [method]
120
+ in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
121
+ excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
122
+
123
+ expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, excluded_path])
124
+ expect(NPlusOneQuery).to_not receive(:create_notification)
125
+ NPlusOneQuery.call_association(@post, :association)
126
+ end
104
127
  end
105
128
  end
106
129
 
@@ -226,7 +226,7 @@ if active_record?
226
226
  context 'post => comment' do
227
227
  it 'should detect unused preload with post => comments' do
228
228
  Post.includes(:comments).each do |post|
229
- post.comments.first.name if post.comments.first
229
+ post.comments.first&.name
230
230
  end
231
231
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
232
232
  expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
@@ -356,6 +356,28 @@ if active_record?
356
356
 
357
357
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
358
358
  end
359
+
360
+ it 'should not detect newly assigned object in an after_save' do
361
+ new_post = Post.new(category: Category.first)
362
+
363
+ new_post.trigger_after_save = true
364
+ new_post.save!
365
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
366
+
367
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
368
+ end
369
+
370
+ it 'should not detect "manual" preload' do
371
+ comment = Comment.all.to_a.first
372
+ post = Post.find(comment.post_id)
373
+ # "manually" preload with out-of-band data
374
+ comment.post = post
375
+ # loading it should not trigger anything
376
+ comment.post
377
+
378
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
379
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
380
+ end
359
381
  end
360
382
 
361
383
  context 'comment => post => category' do
@@ -528,6 +550,44 @@ if active_record?
528
550
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
529
551
  end
530
552
  end
553
+
554
+ context 'firm => clients => groups' do
555
+ it 'should detect non preload associations' do
556
+ Firm.all.each do |firm|
557
+ firm.groups.map(&:name)
558
+ end
559
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
560
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
561
+
562
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :groups)
563
+ end
564
+
565
+ it 'should detect preload associations' do
566
+ Firm.includes(:groups).each do |firm|
567
+ firm.groups.map(&:name)
568
+ end
569
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
570
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
571
+
572
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
573
+ end
574
+
575
+ it 'should not detect preload associations' do
576
+ Firm.all.map(&:name)
577
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
578
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
579
+
580
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
581
+ end
582
+
583
+ it 'should detect unused preload associations' do
584
+ Firm.includes(:groups).map(&:name)
585
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
586
+ expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :groups)
587
+
588
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
589
+ end
590
+ end
531
591
  end
532
592
 
533
593
  describe Bullet::Detector::Association, 'has_one' do
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Client < ActiveRecord::Base
4
+ belongs_to :group
5
+
4
6
  has_many :relationships
5
7
  has_many :firms, through: :relationships
6
8
  end
@@ -3,4 +3,5 @@
3
3
  class Firm < ActiveRecord::Base
4
4
  has_many :relationships
5
5
  has_many :clients, through: :relationships
6
+ has_many :groups, through: :clients
6
7
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Group < ActiveRecord::Base
4
+ end
@@ -14,4 +14,19 @@ class Post < ActiveRecord::Base
14
14
  def link=(*)
15
15
  comments.new
16
16
  end
17
+
18
+ # see association_spec.rb 'should not detect newly assigned object in an after_save'
19
+ attr_accessor :trigger_after_save
20
+ after_save do
21
+ next unless trigger_after_save
22
+
23
+ temp_comment = Comment.new(post: self)
24
+ # this triggers self to be "possible", even though it's
25
+ # not saved yet
26
+ temp_comment.post
27
+
28
+ # category should NOT whine about not being pre-loaded, because
29
+ # it's obviously attached to a new object
30
+ category
31
+ end
17
32
  end
@@ -45,8 +45,10 @@ module Support
45
45
 
46
46
  firm1 = Firm.create(name: 'first')
47
47
  firm2 = Firm.create(name: 'second')
48
- client1 = Client.create(name: 'first')
49
- client2 = Client.create(name: 'second')
48
+ group1 = Group.create(name: 'first')
49
+ group2 = Group.create(name: 'second')
50
+ client1 = Client.create(name: 'first', group: group1)
51
+ client2 = Client.create(name: 'second', group: group2)
50
52
  firm1.clients = [client1, client2]
51
53
  firm2.clients = [client1, client2]
52
54
  client1.firms << firm1
@@ -125,6 +127,7 @@ module Support
125
127
 
126
128
  create_table :clients do |t|
127
129
  t.column :name, :string
130
+ t.column :group_id, :integer
128
131
  end
129
132
 
130
133
  create_table :comments do |t|
@@ -171,6 +174,10 @@ module Support
171
174
  t.column :name, :string
172
175
  end
173
176
 
177
+ create_table :groups do |t|
178
+ t.column :name, :string
179
+ end
180
+
174
181
  create_table :hotels do |t|
175
182
  t.column :name, :string
176
183
  t.column :location_id, :integer
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.7.5
4
+ version: 5.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-12 00:00:00.000000000 Z
11
+ date: 2018-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.11.0
33
+ version: '1.11'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.11.0
40
+ version: '1.11'
41
41
  description: help to kill N+1 queries and unused eager loading.
42
42
  email:
43
43
  - flyerhzm@gmail.com
@@ -133,6 +133,7 @@ files:
133
133
  - spec/models/entry.rb
134
134
  - spec/models/firm.rb
135
135
  - spec/models/folder.rb
136
+ - spec/models/group.rb
136
137
  - spec/models/mongoid/address.rb
137
138
  - spec/models/mongoid/category.rb
138
139
  - spec/models/mongoid/comment.rb
@@ -163,7 +164,9 @@ files:
163
164
  homepage: https://github.com/flyerhzm/bullet
164
165
  licenses:
165
166
  - MIT
166
- metadata: {}
167
+ metadata:
168
+ changelog_uri: https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md
169
+ source_code_uri: https://github.com/flyerhzm/bullet
167
170
  post_install_message:
168
171
  rdoc_options: []
169
172
  require_paths:
@@ -218,6 +221,7 @@ test_files:
218
221
  - spec/models/entry.rb
219
222
  - spec/models/firm.rb
220
223
  - spec/models/folder.rb
224
+ - spec/models/group.rb
221
225
  - spec/models/mongoid/address.rb
222
226
  - spec/models/mongoid/category.rb
223
227
  - spec/models/mongoid/comment.rb