bullet 6.1.0 → 6.1.1

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
  SHA256:
3
- metadata.gz: dd668fdfd675ea8437eee0242974d7cb1cfacc0479be4cc5f3f58ee7d05a6d63
4
- data.tar.gz: 3d841fb8471fe18b66135fa087ee3d1874f412c114d09ba05ba150e2daaf9775
3
+ metadata.gz: 6d02240600f13580ed16200edd5c39563bec1cb49a46ccbc82c57cff73a46e2f
4
+ data.tar.gz: 0f40a4a1fe6157dabec88cb0ecc817168eda794aa1177c41ede1df351695d76a
5
5
  SHA512:
6
- metadata.gz: 714c614f2cf44f332f4f9996638c10dca565c92a2279316509256fefba2ca824fc9c71d2a3c3e4ca28edfc45849f76b61e521158e0051c365f5b8a81f41e9d8d
7
- data.tar.gz: a3b1a7e58b6e5554a641aa4b8edf16dbb4983b2f7a072c5b7275ee04a7251e4d095fed37f82d2945ef759603c8aeaf8a394d9e7fcf9f70a723e6235ab5f3e646
6
+ metadata.gz: 1d3d3ce81767ce81091ddd7412dd8f341190ffac085477cde3ea04fa1ed696661f84be64d9c1fb6e6a0de276788e56f18aaada213df712cb51e6efb0a577ec37
7
+ data.tar.gz: a3633af514a31e0ad6ff317a98616ed2c6c3ce070109288b8514c80277d4098359ed25a60bd5bcc4cd0ebf469f156751fa707f8d410f010bfcb8828e206e08a3
@@ -1,5 +1,10 @@
1
1
  ## Next Release
2
2
 
3
+ ## 6.1.1 (12/12/2020)
4
+
5
+ * Add support Rails 6.1
6
+ * Make whitelist thread safe
7
+
3
8
  ## 6.1.0 (12/28/2019)
4
9
 
5
10
  * Add skip_html_injection flag
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rails', '6.0.0'
5
+ gem 'rails', '~> 6.0.0'
6
6
  gem 'sqlite3'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '~> 6.1.0'
6
+ gem 'sqlite3'
7
+ gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
+ gem 'activerecord-import'
9
+
10
+ gem "rspec"
11
+
12
+ platforms :rbx do
13
+ gem 'rubysl', '~> 2.0'
14
+ gem 'rubinius-developer_tools'
15
+ end
data/README.md CHANGED
@@ -37,6 +37,13 @@ or add it into a Gemfile (Bundler):
37
37
  gem 'bullet', group: 'development'
38
38
  ```
39
39
 
40
+ enable the Bullet gem with generate command
41
+
42
+ ```ruby
43
+ bundle exec rails g bullet:install
44
+ ```
45
+ The generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.
46
+
40
47
  **Note**: make sure `bullet` gem is added after activerecord (rails) and
41
48
  mongoid.
42
49
 
@@ -38,7 +38,6 @@ module Bullet
38
38
  :stacktrace_includes,
39
39
  :stacktrace_excludes,
40
40
  :skip_html_injection
41
- attr_reader :whitelist
42
41
  attr_accessor :add_footer, :orm_patches_applied
43
42
 
44
43
  available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
@@ -98,27 +97,27 @@ module Bullet
98
97
 
99
98
  def add_whitelist(options)
100
99
  reset_whitelist
101
- @whitelist[options[:type]][options[:class_name]] ||= []
102
- @whitelist[options[:type]][options[:class_name]] << options[:association].to_sym
100
+ Thread.current[:whitelist][options[:type]][options[:class_name]] ||= []
101
+ Thread.current[:whitelist][options[:type]][options[:class_name]] << options[:association].to_sym
103
102
  end
104
103
 
105
104
  def delete_whitelist(options)
106
105
  reset_whitelist
107
- @whitelist[options[:type]][options[:class_name]] ||= []
108
- @whitelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
109
- @whitelist[options[:type]].delete_if { |_key, val| val.empty? }
106
+ Thread.current[:whitelist][options[:type]][options[:class_name]] ||= []
107
+ Thread.current[:whitelist][options[:type]][options[:class_name]].delete(options[:association].to_sym)
108
+ Thread.current[:whitelist][options[:type]].delete_if { |_key, val| val.empty? }
110
109
  end
111
110
 
112
111
  def get_whitelist_associations(type, class_name)
113
- Array(@whitelist[type][class_name])
112
+ Array(Thread.current[:whitelist][type][class_name])
114
113
  end
115
114
 
116
115
  def reset_whitelist
117
- @whitelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
116
+ Thread.current[:whitelist] ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
118
117
  end
119
118
 
120
119
  def clear_whitelist
121
- @whitelist = nil
120
+ Thread.current[:whitelist] = nil
122
121
  end
123
122
 
124
123
  def bullet_logger=(active)
@@ -240,8 +239,8 @@ module Bullet
240
239
  UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
241
240
  end
242
241
 
243
- def skip_html_injection?
244
- @skip_html_injection || false
242
+ def inject_into_page?
243
+ !@skip_html_injection && (console_enabled? || add_footer)
245
244
  end
246
245
 
247
246
  private
@@ -3,7 +3,11 @@
3
3
  module Bullet
4
4
  module ActiveJob
5
5
  def self.included(base)
6
- base.class_eval { around_perform { |_job, block| Bullet.profile { block.call } } }
6
+ base.class_eval do
7
+ around_perform do |_job, block|
8
+ Bullet.profile { block.call }
9
+ end
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -0,0 +1,267 @@
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(
17
+ Module.new do
18
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
19
+ result = super
20
+ if Bullet.start?
21
+ if result.is_a? Array
22
+ if result.size > 1
23
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
24
+ Bullet::Detector::CounterCache.add_possible_objects(result)
25
+ elsif result.size == 1
26
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
27
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
28
+ end
29
+ elsif result.is_a? ::ActiveRecord::Base
30
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
31
+ Bullet::Detector::CounterCache.add_impossible_object(result)
32
+ end
33
+ end
34
+ result
35
+ end
36
+ end
37
+ )
38
+
39
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
40
+
41
+ ::ActiveRecord::Relation.prepend(
42
+ Module.new do
43
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
44
+ # if select only one object, then the only one object has impossible to cause N+1 query.
45
+ def records
46
+ result = super
47
+ if Bullet.start?
48
+ if result.first.class.name !~ /^HABTM_/
49
+ if result.size > 1
50
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
51
+ Bullet::Detector::CounterCache.add_possible_objects(result)
52
+ elsif result.size == 1
53
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
54
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
55
+ end
56
+ end
57
+ end
58
+ result
59
+ end
60
+ end
61
+ )
62
+
63
+ ::ActiveRecord::Associations::Preloader.prepend(
64
+ Module.new do
65
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
66
+ if Bullet.start?
67
+ records.compact!
68
+ if records.first.class.name !~ /^HABTM_/
69
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
70
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
71
+ end
72
+ end
73
+ super
74
+ end
75
+
76
+ def preloaders_for_reflection(reflection, records, scope)
77
+ if Bullet.start?
78
+ records.compact!
79
+ if records.first.class.name !~ /^HABTM_/
80
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
81
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
82
+ end
83
+ end
84
+ super
85
+ end
86
+ end
87
+ )
88
+
89
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
90
+ Module.new do
91
+ def preloaded_records
92
+ if Bullet.start? && !defined?(@preloaded_records)
93
+ source_preloaders.each do |source_preloader|
94
+ reflection_name = source_preloader.send(:reflection).name
95
+ source_preloader.send(:owners).each do |owner|
96
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
97
+ end
98
+ end
99
+ end
100
+ super
101
+ end
102
+ end
103
+ )
104
+
105
+ ::ActiveRecord::FinderMethods.prepend(
106
+ Module.new do
107
+ # add includes in scope
108
+ def find_with_associations
109
+ return super { |r| yield r } if block_given?
110
+
111
+ records = super
112
+ if Bullet.start?
113
+ associations = (eager_load_values + includes_values).uniq
114
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
115
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
116
+ end
117
+ records
118
+ end
119
+ end
120
+ )
121
+
122
+ ::ActiveRecord::Associations::JoinDependency.prepend(
123
+ Module.new do
124
+ def instantiate(result_set, strict_loading_value, &block)
125
+ @bullet_eager_loadings = {}
126
+ records = super
127
+
128
+ if Bullet.start?
129
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
130
+ objects = eager_loadings_hash.keys
131
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
132
+ objects,
133
+ eager_loadings_hash[objects.first].to_a
134
+ )
135
+ end
136
+ end
137
+ records
138
+ end
139
+
140
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
141
+ if Bullet.start?
142
+ unless ar_parent.nil?
143
+ parent.children.each do |node|
144
+ key = aliases.column_alias(node, node.primary_key)
145
+ id = row[key]
146
+ next unless id.nil?
147
+
148
+ associations = node.reflection.name
149
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
150
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
151
+ @bullet_eager_loadings[ar_parent.class] ||= {}
152
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
153
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
154
+ end
155
+ end
156
+ end
157
+
158
+ super
159
+ end
160
+
161
+ # call join associations
162
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
163
+ result = super
164
+
165
+ if Bullet.start?
166
+ associations = node.reflection.name
167
+ Bullet::Detector::Association.add_object_associations(record, associations)
168
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
169
+ @bullet_eager_loadings[record.class] ||= {}
170
+ @bullet_eager_loadings[record.class][record] ||= Set.new
171
+ @bullet_eager_loadings[record.class][record] << associations
172
+ end
173
+
174
+ result
175
+ end
176
+ end
177
+ )
178
+
179
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
+ Module.new do
181
+ def load_target
182
+ records = super
183
+
184
+ if Bullet.start?
185
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
186
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
187
+ association = owner.association(reflection.through_reflection.name)
188
+ Array(association.target).each do |through_record|
189
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
190
+ end
191
+
192
+ if reflection.through_reflection != through_reflection
193
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
194
+ end
195
+ end
196
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
197
+ if records.first.class.name !~ /^HABTM_/
198
+ if records.size > 1
199
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
200
+ Bullet::Detector::CounterCache.add_possible_objects(records)
201
+ elsif records.size == 1
202
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
203
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
204
+ end
205
+ end
206
+ end
207
+ records
208
+ end
209
+
210
+ def empty?
211
+ if Bullet.start? && !reflection.has_cached_counter?
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
213
+ end
214
+ super
215
+ end
216
+
217
+ def include?(object)
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
219
+ super
220
+ end
221
+ end
222
+ )
223
+
224
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
225
+ Module.new do
226
+ # call has_one and belongs_to associations
227
+ def target
228
+ result = super()
229
+
230
+ if Bullet.start?
231
+ if owner.class.name !~ /^HABTM_/ && !@inversed
232
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
233
+
234
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
235
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
236
+ else
237
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
238
+ end
239
+ end
240
+ end
241
+ result
242
+ end
243
+ end
244
+ )
245
+
246
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
247
+ Module.new do
248
+ def empty?
249
+ result = super
250
+ if Bullet.start? && !reflection.has_cached_counter?
251
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
252
+ end
253
+ result
254
+ end
255
+
256
+ def count_records
257
+ result = reflection.has_cached_counter?
258
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
259
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
260
+ end
261
+ super
262
+ end
263
+ end
264
+ )
265
+ end
266
+ end
267
+ end
@@ -1,4 +1,4 @@
1
- (function() {
1
+ (function () {
2
2
  var oldOpen = window.XMLHttpRequest.prototype.open;
3
3
  var oldSend = window.XMLHttpRequest.prototype.send;
4
4
 
@@ -10,41 +10,41 @@
10
10
  if (isBulletInitiated()) return;
11
11
 
12
12
  function isBulletInitiated() {
13
- return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
13
+ return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
14
14
  }
15
15
  function bulletXHROpen(_, url) {
16
16
  this._storedUrl = url;
17
- return oldOpen.apply(this, arguments);
17
+ return Reflect.apply(oldOpen, this, arguments);
18
18
  }
19
19
  function bulletXHRSend() {
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
- this.addEventListener('load', bulletXHROnload);
24
- return oldSend.apply(this, arguments);
23
+ this.addEventListener("load", bulletXHROnload);
24
+ return Reflect.apply(oldSend, this, arguments);
25
25
  }
26
26
  function bulletXHROnload() {
27
27
  if (
28
- this._storedUrl.startsWith(window.location.protocol + '//' + window.location.host) ||
29
- !this._storedUrl.startsWith('http') // For relative paths
28
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
29
+ !this._storedUrl.startsWith("http") // For relative paths
30
30
  ) {
31
- var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
31
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
32
32
  if (bulletFooterText) {
33
- setTimeout(() => {
34
- var oldHtml = document.getElementById('bullet-footer').innerHTML.split('<br>');
33
+ setTimeout(function() {
34
+ var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
35
35
  var header = oldHtml[0];
36
36
  oldHtml = oldHtml.slice(1, oldHtml.length);
37
37
  var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
38
38
  newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
39
- document.getElementById('bullet-footer').innerHTML = `${header}<br>${newHtml.join('<br>')}`;
39
+ document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
40
40
  }, 0);
41
41
  }
42
- var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
43
- if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
44
- setTimeout(() => {
45
- JSON.parse(bulletConsoleText).forEach(message => {
42
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
43
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
44
+ setTimeout(function() {
45
+ JSON.parse(bulletConsoleText).forEach((message) => {
46
46
  if (console.groupCollapsed && console.groupEnd) {
47
- console.groupCollapsed('Uniform Notifier');
47
+ console.groupCollapsed("Uniform Notifier");
48
48
  console.log(message);
49
49
  console.groupEnd();
50
50
  } else {
@@ -55,7 +55,7 @@
55
55
  }
56
56
  }
57
57
  if (this._storedOnload) {
58
- return this._storedOnload.apply(this, arguments);
58
+ return Reflect.apply(this._storedOnload, this, arguments);
59
59
  }
60
60
  }
61
61
  window.XMLHttpRequest.prototype.open = bulletXHROpen;
@@ -27,6 +27,8 @@ module Bullet
27
27
  'active_record52'
28
28
  elsif active_record60?
29
29
  'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
30
32
  else
31
33
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
32
34
  end
@@ -90,6 +92,10 @@ module Bullet
90
92
  active_record6? && ::ActiveRecord::VERSION::MINOR == 0
91
93
  end
92
94
 
95
+ def active_record61?
96
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
97
+ end
98
+
93
99
  def mongoid4x?
94
100
  mongoid? && ::Mongoid::VERSION =~ /\A4/
95
101
  end
@@ -17,7 +17,7 @@ module Bullet
17
17
  response_body = nil
18
18
 
19
19
  if Bullet.notification?
20
- if !Bullet.skip_html_injection? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
20
+ if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
21
21
  if html_request?(headers, response)
22
22
  response_body = response_body(response)
23
23
  response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
@@ -46,6 +46,7 @@ module Bullet
46
46
 
47
47
  def append_to_html_body(response_body, content)
48
48
  body = response_body.dup
49
+ content = content.html_safe if content.respond_to?(:html_safe)
49
50
  if body.include?('</body>')
50
51
  position = body.rindex('</body>')
51
52
  body.insert(position, content)
@@ -55,14 +56,14 @@ module Bullet
55
56
  end
56
57
 
57
58
  def footer_note
58
- "<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
59
+ "<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
59
60
  end
60
61
 
61
62
  def set_header(headers, header_name, header_array)
62
63
  # Many proxy applications such as Nginx and AWS ELB limit
63
64
  # the size a header to 8KB, so truncate the list of reports to
64
65
  # be under that limit
65
- header_array.pop while header_array.to_json.length > 8 * 1_024
66
+ header_array.pop while header_array.to_json.length > 8 * 1024
66
67
  headers[header_name] = header_array.to_json
67
68
  end
68
69
 
@@ -88,23 +89,28 @@ module Bullet
88
89
 
89
90
  private
90
91
 
91
- def footer_div_attributes
92
+ def details_attributes
92
93
  <<~EOF
93
- id="bullet-footer" 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);
94
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
95
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
96
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
97
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
94
+ id="bullet-footer" data-is-bullet-footer
95
+ style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
98
96
  EOF
99
97
  end
100
98
 
101
- def footer_header
102
- cancel_button =
103
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
99
+ def summary_attributes
100
+ <<~EOF
101
+ style="font-weight: 600; padding: 2px 8px"
102
+ EOF
103
+ end
104
+
105
+ def footer_content_attributes
106
+ <<~EOF
107
+ style="padding: 8px; border-top: 1px solid #9b1c1c;"
108
+ EOF
109
+ end
110
+
111
+ def footer_console_message
104
112
  if Bullet.console_enabled?
105
- "<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
106
- else
107
- cancel_button
113
+ "<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
108
114
  end
109
115
  end
110
116
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.0'
4
+ VERSION = '6.1.1'
5
5
  end
@@ -10,40 +10,38 @@ module Bullet
10
10
 
11
11
  def enable_in_development
12
12
  environment(nil, env: 'development') do
13
- <<-"FILE"
14
-
15
- config.after_initialize do
16
- Bullet.enable = true
17
- Bullet.alert = true
18
- Bullet.bullet_logger = true
19
- Bullet.console = true
20
- # Bullet.growl = true
21
- Bullet.rails_logger = true
22
- Bullet.add_footer = true
23
- end
13
+ <<~FILE
14
+ config.after_initialize do
15
+ Bullet.enable = true
16
+ Bullet.alert = true
17
+ Bullet.bullet_logger = true
18
+ Bullet.console = true
19
+ # Bullet.growl = true
20
+ Bullet.rails_logger = true
21
+ Bullet.add_footer = true
22
+ end
23
+
24
24
  FILE
25
- .strip
26
25
  end
27
26
 
28
27
  say 'Enabled bullet in config/environments/development.rb'
29
28
  end
30
29
 
31
30
  def enable_in_test
32
- if yes?('Would you like to enable bullet in test environment? (y/n)')
33
- environment(nil, env: 'test') do
34
- <<-"FILE"
35
-
36
- config.after_initialize do
37
- Bullet.enable = true
38
- Bullet.bullet_logger = true
39
- Bullet.raise = true # raise an error if n+1 query occurs
40
- end
41
- FILE
42
- .strip
43
- end
31
+ return unless yes?('Would you like to enable bullet in test environment? (y/n)')
44
32
 
45
- say 'Enabled bullet in config/environments/test.rb'
33
+ environment(nil, env: 'test') do
34
+ <<~FILE
35
+ config.after_initialize do
36
+ Bullet.enable = true
37
+ Bullet.bullet_logger = true
38
+ Bullet.raise = true # raise an error if n+1 query occurs
39
+ end
40
+
41
+ FILE
46
42
  end
43
+
44
+ say 'Enabled bullet in config/environments/test.rb'
47
45
  end
48
46
  end
49
47
  end
@@ -47,7 +47,7 @@ module Bullet
47
47
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
48
48
  end
49
49
 
50
- it 'should be true when object is possible, and impossible' do
50
+ it 'should be false when object is possible, and impossible' do
51
51
  CounterCache.add_possible_objects(@post1)
52
52
  CounterCache.add_impossible_object(@post1)
53
53
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
@@ -13,35 +13,31 @@ module Bullet
13
13
 
14
14
  context '.call_associations' do
15
15
  it 'should get empty array if eager_loadings' do
16
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
16
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
17
17
  end
18
18
 
19
19
  it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
20
20
  UnusedEagerLoading.add_eager_loadings([@post], :association)
21
21
  UnusedEagerLoading.add_call_object_associations(@post, :association)
22
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
23
- %i[association]
24
- )
22
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
25
23
  end
26
24
 
27
25
  it 'should not get call associations if not exist in call_object_associations' do
28
26
  UnusedEagerLoading.add_eager_loadings([@post], :association)
29
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
27
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
30
28
  end
31
29
  end
32
30
 
33
31
  context '.diff_object_associations' do
34
32
  it 'should return associations not exist in call_association' do
35
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
36
- %i[association]
37
- )
33
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
38
34
  end
39
35
 
40
36
  it 'should return empty if associations exist in call_association' do
41
37
  UnusedEagerLoading.add_eager_loadings([@post], :association)
42
38
  UnusedEagerLoading.add_call_object_associations(@post, :association)
43
39
  expect(
44
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
40
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
45
41
  ).to be_empty
46
42
  end
47
43
  end
@@ -51,7 +47,7 @@ module Bullet
51
47
  it 'should create notification if object_association_diff is not empty' do
52
48
  UnusedEagerLoading.add_object_associations(@post, :association)
53
49
  allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
54
- expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', %i[association])
50
+ expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])
55
51
  UnusedEagerLoading.check_unused_preload_associations
56
52
  end
57
53
 
@@ -60,9 +56,9 @@ module Bullet
60
56
  UnusedEagerLoading.add_eager_loadings([@post], :association)
61
57
  UnusedEagerLoading.add_call_object_associations(@post, :association)
62
58
  expect(
63
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
59
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
64
60
  ).to be_empty
65
- expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', %i[association])
61
+ expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
66
62
  UnusedEagerLoading.check_unused_preload_associations
67
63
  end
68
64
  end
@@ -21,9 +21,7 @@ module Bullet
21
21
  )
22
22
  end
23
23
  it do
24
- expect(subject.body).to eq(
25
- " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])"
26
- )
24
+ expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])")
27
25
  end
28
26
  it { expect(subject.title).to eq('USE eager loading in path') }
29
27
  end
@@ -67,7 +67,7 @@ module Bullet
67
67
 
68
68
  it 'should change response body if notification is active' do
69
69
  expect(Bullet).to receive(:notification?).and_return(true)
70
- allow(Bullet).to receive(:skip_html_injection?).and_return(false)
70
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
71
71
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
72
72
  expect(middleware).to receive(:xhr_script).and_return('')
73
73
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
@@ -81,11 +81,67 @@ module Bullet
81
81
  response.body = '<html><head></head><body>é</body></html>'
82
82
  app.response = response
83
83
  expect(Bullet).to receive(:notification?).and_return(true)
84
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
84
85
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
85
86
  _, headers, response = middleware.call('Content-Type' => 'text/html')
86
87
  expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
87
88
  end
88
89
 
90
+ context 'with injection notifiers' do
91
+ before do
92
+ expect(Bullet).to receive(:notification?).and_return(true)
93
+ allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
94
+ allow(middleware).to receive(:xhr_script).and_return('')
95
+ allow(middleware).to receive(:footer_note).and_return('footer')
96
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
97
+ end
98
+
99
+ it 'should change response body if add_footer is true' do
100
+ expect(Bullet).to receive(:add_footer).twice.and_return(true)
101
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
102
+
103
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
104
+ expect(response.first).to start_with('<html><head></head><body>')
105
+ expect(response.first).to include('<bullet></bullet><')
106
+ end
107
+
108
+ it 'should change response body for html safe string if add_footer is true' do
109
+ expect(Bullet).to receive(:add_footer).twice.and_return(true)
110
+ app.response = Support::ResponseDouble.new.tap do |response|
111
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
112
+ end
113
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
114
+
115
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
116
+ expect(response.first).to start_with('<html><head></head><body>')
117
+ expect(response.first).to include('<bullet></bullet><')
118
+ end
119
+
120
+ it 'should change response body if console_enabled is true' do
121
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
122
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
123
+ expect(headers['Content-Length']).to eq('56')
124
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
125
+ end
126
+
127
+ it 'should change response body for html safe string if console_enabled is true' do
128
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
129
+ app.response = Support::ResponseDouble.new.tap do |response|
130
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
131
+ end
132
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
133
+ expect(headers['Content-Length']).to eq('56')
134
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
135
+ end
136
+
137
+ it "shouldn't change response body unnecessarily" do
138
+ expected_response = Support::ResponseDouble.new 'Actual body'
139
+ app.response = expected_response
140
+ _, _, response = middleware.call({})
141
+ expect(response).to eq(expected_response)
142
+ end
143
+ end
144
+
89
145
  context 'when skip_html_injection is enabled' do
90
146
  it 'should not try to inject html' do
91
147
  expected_response = Support::ResponseDouble.new 'Actual body'
@@ -88,7 +88,7 @@ describe Bullet, focused: true do
88
88
  it 'is deleted from the whitelist successfully' do
89
89
  Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
90
90
  Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
91
+ expect(Thread.current[:whitelist][:n_plus_one_query]).to eq({})
92
92
  end
93
93
  end
94
94
 
@@ -377,8 +377,7 @@ if active_record?
377
377
  it 'should detect unused preload with comment => author' do
378
378
  Comment.includes([:author, { post: :writer }]).where(['base_users.id = ?', BaseUser.first]).references(
379
379
  :base_users
380
- )
381
- .each { |comment| comment.post.writer.name }
380
+ ).each { |comment| comment.post.writer.name }
382
381
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
383
382
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
384
383
 
@@ -73,9 +73,9 @@ if mongoid?
73
73
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
74
74
 
75
75
  expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(
76
- Mongoid::Category,
77
- :posts
78
- )
76
+ Mongoid::Category,
77
+ :posts
78
+ )
79
79
  expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Category, :entries)
80
80
  end
81
81
 
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: 6.1.0
4
+ version: 6.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-28 00:00:00.000000000 Z
11
+ date: 2020-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -62,6 +62,7 @@ files:
62
62
  - Gemfile.rails-5.1
63
63
  - Gemfile.rails-5.2
64
64
  - Gemfile.rails-6.0
65
+ - Gemfile.rails-6.1
65
66
  - Guardfile
66
67
  - Hacking.md
67
68
  - MIT-LICENSE
@@ -76,6 +77,7 @@ files:
76
77
  - lib/bullet/active_record5.rb
77
78
  - lib/bullet/active_record52.rb
78
79
  - lib/bullet/active_record60.rb
80
+ - lib/bullet/active_record61.rb
79
81
  - lib/bullet/bullet_xhr.js
80
82
  - lib/bullet/dependency.rb
81
83
  - lib/bullet/detector.rb
@@ -173,7 +175,7 @@ licenses:
173
175
  metadata:
174
176
  changelog_uri: https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md
175
177
  source_code_uri: https://github.com/flyerhzm/bullet
176
- post_install_message:
178
+ post_install_message:
177
179
  rdoc_options: []
178
180
  require_paths:
179
181
  - lib
@@ -188,8 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
190
  - !ruby/object:Gem::Version
189
191
  version: 1.3.6
190
192
  requirements: []
191
- rubygems_version: 3.0.3
192
- signing_key:
193
+ rubygems_version: 3.1.4
194
+ signing_key:
193
195
  specification_version: 4
194
196
  summary: help to kill N+1 queries and unused eager loading.
195
197
  test_files: