bullet 5.9.0 → 6.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fefb038c3c46f100cc22cdd95c417c781aff2943
4
- data.tar.gz: ad23b5755c4956788bad2b3ae98323d6e24c2bb8
2
+ SHA256:
3
+ metadata.gz: 7f255102db3ae1250632db2b30a9e6f1bce87dab49b4dabee73709ffde82a4e0
4
+ data.tar.gz: 025ab0e3513546a56e984aa83a0b925d27e631bdfe61feb694b3e21ecfb6ffda
5
5
  SHA512:
6
- metadata.gz: 79f4e0bb1d4e62df6460fd668f0789936e3e0b1c36d078bb13ec2788f9d69f46e0dd3615e1983019bc4c83d6e7d2b2195377c5216a23038d270aee2ddfb6e77c
7
- data.tar.gz: 504905ca503f0727e71c1c476ab058eab57618bc6a4a9084411c479ebd63913c75ac178cf27566d05e800887e1d6f1e709aa89733632743c7216db810d410fb8
6
+ metadata.gz: 23d701691dc1f2286ec11ffb26f548878bfa7ddd2ea8f20d97bb4c64df4e3a7a10f54adb16fa7cacd8bb8807dd1804e349c1b889e32530cbb7c57432ceec17da
7
+ data.tar.gz: 281a4a6619695a7db4098f1ed62066cd4e81cf9c2c3bf33fa83f87a205a5c4779e9c8125143a78ca482247ed2a271465b3011af84a5dfc67753cbad2357ee2ae
@@ -2,11 +2,30 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.0
5
+ - 2.6.0
5
6
  gemfile:
7
+ - Gemfile.rails-6.0
8
+ - Gemfile.rails-5.2
6
9
  - Gemfile.rails-5.1
7
10
  - Gemfile.rails-5.0
8
11
  - Gemfile.rails-4.2
9
12
  - Gemfile.rails-4.1
10
13
  - Gemfile.rails-4.0
14
+ matrix:
15
+ exclude:
16
+ - rvm: 2.3.0
17
+ gemfile: Gemfile.rails-6.0
18
+ - rvm: 2.6.0
19
+ gemfile: Gemfile.rails-5.2
20
+ - rvm: 2.6.0
21
+ gemfile: Gemfile.rails-5.1
22
+ - rvm: 2.6.0
23
+ gemfile: Gemfile.rails-5.0
24
+ - rvm: 2.6.0
25
+ gemfile: Gemfile.rails-4.2
26
+ - rvm: 2.6.0
27
+ gemfile: Gemfile.rails-4.1
28
+ - rvm: 2.6.0
29
+ gemfile: Gemfile.rails-4.0
11
30
  env:
12
31
  - DB=sqlite
@@ -1,5 +1,16 @@
1
1
  ## Next Release
2
2
 
3
+ ## 6.0.1 (06/26/2019)
4
+
5
+ * Add Bullet::ActiveJob
6
+ * Prevent "Maximum call stack exceeded" errors when used with Turbolinks
7
+
8
+ ## 6.0.0 (04/25/2019)
9
+
10
+ * Add XHR support to Bullet
11
+ * Support Rails 6.0
12
+ * Handle case where ID is manually set on unpersisted record
13
+
3
14
  ## 5.9.0 (11/11/2018)
4
15
 
5
16
  * Require Ruby 2.3+
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 4.0.0'
6
- gem 'sqlite3', platforms: [:ruby]
6
+ gem 'sqlite3', '~> 1.3.6', platforms: [:ruby]
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
  gem 'tins', '~> 1.6.0', platforms: [:ruby_19]
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 4.1.0'
6
- gem 'sqlite3'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
  gem 'tins', '~> 1.6.0', platforms: [:ruby_19]
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 4.2.0'
6
- gem 'sqlite3'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
  gem 'tins', '~> 1.6.0', platforms: [:ruby_19]
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 5.0.0'
6
- gem 'sqlite3'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
 
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 5.1.0'
6
- gem 'sqlite3'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
 
@@ -3,7 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'rails', '~> 5.2.0'
6
- gem 'sqlite3'
6
+ gem 'sqlite3', '~> 1.3.6'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
9
9
 
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '6.0.0.rc1'
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
@@ -181,15 +181,27 @@ If you find Bullet does not work for you, *please disable your browser's cache*.
181
181
 
182
182
  ## Advanced
183
183
 
184
- ### Profile a job
184
+ ### Work with ActiveJob
185
185
 
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
186
+ Include `Bullet::ActiveJob` in your `ApplicationJob`.
187
187
 
188
188
  ```ruby
189
- Bullet.profile do
190
- # do anything
189
+ class ApplicationJob < ActiveJob::Base
190
+ include Bullet::ActiveJob if Rails.env.development?
191
+ end
192
+ ```
193
+
194
+ ### Work with other background job solution
191
195
 
192
- warnings = Bullet.warnings
196
+ Use the Bullet.profile method.
197
+
198
+ ```ruby
199
+ class ApplicationJob < ActiveJob::Base
200
+ around_perform do |_job, block|
201
+ Bullet.profile do
202
+ block.call
203
+ end
204
+ end
193
205
  end
194
206
  ```
195
207
 
@@ -221,7 +233,7 @@ end
221
233
  Then wrap each test in Bullet api.
222
234
 
223
235
  ```ruby
224
- # spec/spec_helper.rb
236
+ # spec/rails_helper.rb
225
237
  if Bullet.enable?
226
238
  config.before(:each) do
227
239
  Bullet.start_request
@@ -458,4 +470,4 @@ Meanwhile, there's a line appended to `log/bullet.log`
458
470
  Post => [:comments]
459
471
  ```
460
472
 
461
- Copyright (c) 2009 - 2016 Richard Huang (flyerhzm@gmail.com), released under the MIT license
473
+ Copyright (c) 2009 - 2019 Richard Huang (flyerhzm@gmail.com), released under the MIT license
@@ -14,6 +14,7 @@ module Bullet
14
14
  autoload :ActiveRecord, "bullet/#{active_record_version}" if active_record?
15
15
  autoload :Mongoid, "bullet/#{mongoid_version}" if mongoid?
16
16
  autoload :Rack, 'bullet/rack'
17
+ autoload :ActiveJob, 'bullet/active_job'
17
18
  autoload :Notification, 'bullet/notification'
18
19
  autoload :Detector, 'bullet/detector'
19
20
  autoload :Registry, 'bullet/registry'
@@ -63,6 +64,10 @@ module Bullet
63
64
  !!@enable
64
65
  end
65
66
 
67
+ def app_root
68
+ (defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
69
+ end
70
+
66
71
  def n_plus_one_query_enable?
67
72
  enable? && !!@n_plus_one_query_enable
68
73
  end
@@ -111,9 +116,8 @@ module Bullet
111
116
  def bullet_logger=(active)
112
117
  if active
113
118
  require 'fileutils'
114
- root_path = (rails? ? Rails.root.to_s : Dir.pwd).to_s
115
- FileUtils.mkdir_p(root_path + '/log')
116
- bullet_log_file = File.open("#{root_path}/log/bullet.log", 'a+')
119
+ FileUtils.mkdir_p(app_root + '/log')
120
+ bullet_log_file = File.open("#{app_root}/log/bullet.log", 'a+')
117
121
  bullet_log_file.sync = true
118
122
  UniformNotifier.customized_logger = bullet_log_file
119
123
  end
@@ -192,6 +196,14 @@ module Bullet
192
196
  info
193
197
  end
194
198
 
199
+ def text_notifications
200
+ info = []
201
+ notification_collector.collection.each do |notification|
202
+ info << notification.notification_data.values.compact.join("\n")
203
+ end
204
+ info
205
+ end
206
+
195
207
  def warnings
196
208
  notification_collector.collection.each_with_object({}) do |notification, warnings|
197
209
  warning_type = notification.class.to_s.split(':').last.tableize
@@ -219,6 +231,10 @@ module Bullet
219
231
  return_value
220
232
  end
221
233
 
234
+ def console_enabled?
235
+ UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
236
+ end
237
+
222
238
  private
223
239
 
224
240
  def for_each_active_notifier_with_notification
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module ActiveJob
5
+ def self.included(base)
6
+ base.class_eval do
7
+ around_perform do |_job, block|
8
+ Bullet.profile do
9
+ block.call
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -181,7 +181,6 @@ module Bullet
181
181
  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
182
182
  alias_method :origin_has_cached_counter?, :has_cached_counter?
183
183
 
184
- # rubocop:disable Style/MethodCallWithoutArgsParentheses
185
184
  def has_cached_counter?(reflection = reflection())
186
185
  result = origin_has_cached_counter?(reflection)
187
186
  if Bullet.start? && !result
@@ -189,15 +188,7 @@ module Bullet
189
188
  end
190
189
  result
191
190
  end
192
- # rubocop:enable Style/MethodCallWithoutArgsParentheses
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
191
+
201
192
  end
202
193
  end
203
194
  end
@@ -179,14 +179,6 @@ module Bullet
179
179
  origin_count_records
180
180
  end
181
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
182
  end
191
183
  end
192
184
  end
@@ -240,14 +240,6 @@ module Bullet
240
240
  origin_count_records
241
241
  end
242
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
251
243
  end
252
244
  end
253
245
  end
@@ -171,9 +171,7 @@ module Bullet
171
171
  end
172
172
 
173
173
  if refl.through_reflection?
174
- while refl.through_reflection?
175
- refl = refl.through_reflection
176
- end
174
+ refl = refl.through_reflection while refl.through_reflection?
177
175
 
178
176
  Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
179
177
  end
@@ -242,13 +240,6 @@ module Bullet
242
240
  super
243
241
  end
244
242
  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)
252
243
  end
253
244
  end
254
245
  end
@@ -220,13 +220,6 @@ module Bullet
220
220
  super
221
221
  end
222
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)
230
223
  end
231
224
  end
232
225
  end
@@ -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,69 @@
1
+ (function() {
2
+ var oldOpen = window.XMLHttpRequest.prototype.open;
3
+ var oldSend = window.XMLHttpRequest.prototype.send;
4
+
5
+ /**
6
+ * Return early if we've already extended prototype. This prevents
7
+ * "maximum call stack exceeded" errors when used with Turbolinks.
8
+ * See https://github.com/flyerhzm/bullet/issues/454
9
+ */
10
+ if (isBulletInitiated()) return;
11
+
12
+ function isBulletInitiated() {
13
+ return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
14
+ }
15
+ function bulletXHROpen(_, url) {
16
+ this._storedUrl = url;
17
+ return oldOpen.apply(this, arguments);
18
+ }
19
+ function bulletXHRSend() {
20
+ if (this.onload) {
21
+ this._storedOnload = this.onload;
22
+ }
23
+ this.addEventListener('load', bulletXHROnload);
24
+ return oldSend.apply(this, arguments);
25
+ }
26
+ function bulletXHROnload() {
27
+ if (
28
+ this._storedUrl.startsWith(
29
+ window.location.protocol + '//' + window.location.host
30
+ ) ||
31
+ !this._storedUrl.startsWith('http') // For relative paths
32
+ ) {
33
+ var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
34
+ if (bulletFooterText) {
35
+ setTimeout(() => {
36
+ var oldHtml = document
37
+ .getElementById('bullet-footer')
38
+ .innerHTML.split('<br>');
39
+ var header = oldHtml[0];
40
+ oldHtml = oldHtml.slice(1, oldHtml.length);
41
+ var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
42
+ newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
43
+ document.getElementById(
44
+ 'bullet-footer'
45
+ ).innerHTML = `${header}<br>${newHtml.join('<br>')}`;
46
+ }, 0);
47
+ }
48
+ var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
49
+ if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
50
+ setTimeout(() => {
51
+ JSON.parse(bulletConsoleText).forEach(message => {
52
+ if (console.groupCollapsed && console.groupEnd) {
53
+ console.groupCollapsed('Uniform Notifier');
54
+ console.log(message);
55
+ console.groupEnd();
56
+ } else {
57
+ console.log(message);
58
+ }
59
+ });
60
+ }, 0);
61
+ }
62
+ }
63
+ if (this._storedOnload) {
64
+ return this._storedOnload.apply(this, arguments);
65
+ }
66
+ }
67
+ window.XMLHttpRequest.prototype.open = bulletXHROpen;
68
+ window.XMLHttpRequest.prototype.send = bulletXHRSend;
69
+ })();
@@ -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,6 +24,8 @@ 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
30
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
33
31
  end
@@ -58,6 +56,10 @@ module Bullet
58
56
  active_record? && ::ActiveRecord::VERSION::MAJOR == 5
59
57
  end
60
58
 
59
+ def active_record6?
60
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 6
61
+ end
62
+
61
63
  def active_record40?
62
64
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
63
65
  end
@@ -82,6 +84,10 @@ module Bullet
82
84
  active_record5? && ::ActiveRecord::VERSION::MINOR == 2
83
85
  end
84
86
 
87
+ def active_record60?
88
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 0
89
+ end
90
+
85
91
  def mongoid4x?
86
92
  mongoid? && ::Mongoid::VERSION =~ /\A4/
87
93
  end
@@ -7,7 +7,7 @@ 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
12
  Bullet.debug('Detector::Association#add_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
13
13
  object_associations.add(object.bullet_key, associations)
@@ -16,7 +16,7 @@ module Bullet
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
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)
@@ -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)
@@ -20,7 +20,7 @@ module Bullet
20
20
  return unless Bullet.counter_cache_enable?
21
21
 
22
22
  objects = Array(object_or_objects)
23
- return if objects.map(&:primary_key_value).compact.empty?
23
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
24
24
 
25
25
  Bullet.debug('Detector::CounterCache#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
26
26
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -29,7 +29,7 @@ module Bullet
29
29
  def add_impossible_object(object)
30
30
  return unless Bullet.start?
31
31
  return unless Bullet.counter_cache_enable?
32
- return unless object.primary_key_value
32
+ return unless object.bullet_primary_key_value
33
33
 
34
34
  Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
35
35
  impossible_objects.add object.bullet_key
@@ -14,7 +14,7 @@ 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
20
  add_call_object_associations(object, associations)
@@ -31,7 +31,7 @@ module Bullet
31
31
  return unless Bullet.n_plus_one_query_enable?
32
32
 
33
33
  objects = Array(object_or_objects)
34
- return if objects.map(&:primary_key_value).compact.empty?
34
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
35
35
 
36
36
  Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
37
37
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -40,7 +40,7 @@ module Bullet
40
40
  def add_impossible_object(object)
41
41
  return unless Bullet.start?
42
42
  return unless Bullet.n_plus_one_query_enable?
43
- return unless object.primary_key_value
43
+ return unless object.bullet_primary_key_value
44
44
 
45
45
  Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
46
46
  impossible_objects.add object.bullet_key
@@ -49,7 +49,7 @@ module Bullet
49
49
  def add_inversed_object(object, association)
50
50
  return unless Bullet.start?
51
51
  return unless Bullet.n_plus_one_query_enable?
52
- return unless object.primary_key_value
52
+ return unless object.bullet_primary_key_value
53
53
 
54
54
  Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
55
55
  inversed_objects.add object.bullet_key, association
@@ -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)
@@ -2,10 +2,12 @@
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
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
@@ -16,12 +16,17 @@ module Bullet
16
16
 
17
17
  response_body = nil
18
18
  if Bullet.notification?
19
- if !file?(headers) && !sse?(headers) && !empty?(response) &&
20
- status == 200 && html_request?(headers, response)
21
- response_body = response_body(response)
22
- response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
23
- response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
24
- headers['Content-Length'] = response_body.bytesize.to_s
19
+ if !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
20
+ if html_request?(headers, response)
21
+ response_body = response_body(response)
22
+ response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
23
+ response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
24
+ response_body = append_to_html_body(response_body, xhr_script)
25
+ headers['Content-Length'] = response_body.bytesize.to_s
26
+ else
27
+ set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
28
+ set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
29
+ end
25
30
  end
26
31
  Bullet.perform_out_of_channel_notifications(env)
27
32
  end
@@ -32,16 +37,10 @@ module Bullet
32
37
 
33
38
  # fix issue if response's body is a Proc
34
39
  def empty?(response)
35
- # response may be ["Not Found"], ["Move Permanently"], etc.
36
- if rails?
37
- (response.is_a?(Array) && response.size <= 1) ||
38
- !response.respond_to?(:body) ||
39
- !response_body(response).respond_to?(:empty?) ||
40
- response_body(response).empty?
41
- else
42
- body = response_body(response)
43
- body.nil? || body.empty?
44
- end
40
+ # response may be ["Not Found"], ["Move Permanently"], etc, but
41
+ # those should not happen if the status is 200
42
+ body = response_body(response)
43
+ body.nil? || body.empty?
45
44
  end
46
45
 
47
46
  def append_to_html_body(response_body, content)
@@ -55,7 +54,15 @@ module Bullet
55
54
  end
56
55
 
57
56
  def footer_note
58
- "<div #{footer_div_attributes}>" + footer_close_button + Bullet.footer_info.uniq.join('<br>') + '</div>'
57
+ "<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
58
+ end
59
+
60
+ def set_header(headers, header_name, header_array)
61
+ # Many proxy applications such as Nginx and AWS ELB limit
62
+ # the size a header to 8KB, so truncate the list of reports to
63
+ # be under that limit
64
+ header_array.pop while header_array.to_json.length > 8 * 1024
65
+ headers[header_name] = header_array.to_json
59
66
  end
60
67
 
61
68
  def file?(headers)
@@ -82,7 +89,7 @@ module Bullet
82
89
 
83
90
  def footer_div_attributes
84
91
  <<~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);
92
+ 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);
86
93
  -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
87
94
  -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
88
95
  padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
@@ -90,8 +97,18 @@ module Bullet
90
97
  EOF
91
98
  end
92
99
 
93
- def footer_close_button
94
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
100
+ def footer_header
101
+ cancel_button = "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
102
+ if Bullet.console_enabled?
103
+ "<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
104
+ else
105
+ cancel_button
106
+ end
107
+ end
108
+
109
+ # Make footer work for XHR requests by appending data to the footer
110
+ def xhr_script
111
+ "<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
95
112
  end
96
113
  end
97
114
  end
@@ -5,12 +5,11 @@ module Bullet
5
5
  VENDOR_PATH = '/vendor'
6
6
 
7
7
  def caller_in_project
8
- app_root = rails? ? Rails.root.to_s : Dir.pwd
9
- vendor_root = app_root + VENDOR_PATH
8
+ vendor_root = Bullet.app_root + VENDOR_PATH
10
9
  bundler_path = Bundler.bundle_path.to_s
11
10
  select_caller_locations do |location|
12
11
  caller_path = location_as_path(location)
13
- caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
12
+ caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
14
13
  Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
15
14
  end
16
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '5.9.0'
4
+ VERSION = '6.0.1'
5
5
  end
@@ -17,23 +17,28 @@ describe Object do
17
17
  end
18
18
  end
19
19
 
20
- context 'primary_key_value' do
20
+ context 'bullet_primary_key_value' do
21
21
  it 'should return id' do
22
22
  post = Post.first
23
- expect(post.primary_key_value).to eq(post.id)
23
+ expect(post.bullet_primary_key_value).to eq(post.id)
24
24
  end
25
25
 
26
26
  it 'should return primary key value' do
27
27
  post = Post.first
28
28
  Post.primary_key = 'name'
29
- expect(post.primary_key_value).to eq(post.name)
29
+ expect(post.bullet_primary_key_value).to eq(post.name)
30
30
  Post.primary_key = 'id'
31
31
  end
32
32
 
33
33
  it 'should return value for multiple primary keys' do
34
34
  post = Post.first
35
35
  allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])
36
- expect(post.primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
36
+ expect(post.bullet_primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
37
+ end
38
+
39
+ it 'it should return nil for unpersisted records' do
40
+ post = Post.new(id: 123)
41
+ expect(post.bullet_primary_key_value).to be_nil
37
42
  end
38
43
  end
39
44
  end
@@ -45,9 +45,9 @@ module Bullet
45
45
  expect(middleware).not_to be_empty(response)
46
46
  end
47
47
 
48
- it 'should be true if response is not found' do
48
+ it 'should be false if response is not found' do
49
49
  response = ['Not Found']
50
- expect(middleware).to be_empty(response)
50
+ expect(middleware).not_to be_empty(response)
51
51
  end
52
52
 
53
53
  it 'should be true if response body is empty' do
@@ -68,6 +68,7 @@ module Bullet
68
68
  it 'should change response body if notification is active' do
69
69
  expect(Bullet).to receive(:notification?).and_return(true)
70
70
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
71
+ expect(middleware).to receive(:xhr_script).and_return('')
71
72
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
72
73
  status, headers, response = middleware.call('Content-Type' => 'text/html')
73
74
  expect(headers['Content-Length']).to eq('56')
@@ -81,7 +82,7 @@ module Bullet
81
82
  expect(Bullet).to receive(:notification?).and_return(true)
82
83
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
83
84
  status, headers, response = middleware.call('Content-Type' => 'text/html')
84
- expect(headers['Content-Length']).to eq('58')
85
+ expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
85
86
  end
86
87
  end
87
88
 
@@ -95,6 +96,14 @@ module Bullet
95
96
  end
96
97
  end
97
98
 
99
+ context '#set_header' do
100
+ it 'should truncate headers to under 8kb' do
101
+ long_header = ['a' * 1024] * 10
102
+ expected_res = (['a' * 1024] * 7).to_json
103
+ expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
104
+ end
105
+ end
106
+
98
107
  describe '#response_body' do
99
108
  let(:response) { double }
100
109
  let(:body_string) { '<html><body>My Body</body></html>' }
@@ -246,7 +246,7 @@ if active_record?
246
246
  category = Category.first
247
247
  category.draft_post.destroy!
248
248
  post = category.draft_post
249
- post.update_attributes!(link: true)
249
+ post.update!(link: true)
250
250
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
251
251
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
252
252
 
@@ -331,7 +331,7 @@ if active_record?
331
331
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
332
332
  end
333
333
 
334
- it 'should dtect preload with comment => post' do
334
+ it 'should detect preload with comment => post' do
335
335
  Comment.includes(:post).each do |comment|
336
336
  comment.post.name
337
337
  end
@@ -362,22 +362,11 @@ if active_record?
362
362
 
363
363
  new_post.trigger_after_save = true
364
364
  new_post.save!
365
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
365
366
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
366
367
 
367
368
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
368
369
  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
381
370
  end
382
371
 
383
372
  context 'comment => post => category' do
@@ -38,7 +38,7 @@ if !mongoid? && active_record?
38
38
  expect(Bullet.collected_counter_cache_notifications).to be_empty
39
39
  end
40
40
 
41
- if active_record5?
41
+ if active_record5? || active_record6?
42
42
  it 'should not need counter cache for has_many through' do
43
43
  Client.all.each do |client|
44
44
  client.firms.size
data/test.sh CHANGED
@@ -1,5 +1,6 @@
1
1
  #bundle update rails && bundle exec rspec spec
2
2
  #BUNDLE_GEMFILE=Gemfile.mongoid bundle update mongoid && BUNDLE_GEMFILE=Gemfile.mongoid bundle exec rspec spec
3
+ BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec
3
4
  BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec
4
5
  BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec
5
6
  BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle exec rspec spec
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.9.0
4
+ version: 6.0.1
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-11-11 00:00:00.000000000 Z
11
+ date: 2019-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -61,6 +61,7 @@ files:
61
61
  - Gemfile.rails-5.0
62
62
  - Gemfile.rails-5.1
63
63
  - Gemfile.rails-5.2
64
+ - Gemfile.rails-6.0
64
65
  - Guardfile
65
66
  - Hacking.md
66
67
  - MIT-LICENSE
@@ -68,11 +69,14 @@ files:
68
69
  - Rakefile
69
70
  - bullet.gemspec
70
71
  - lib/bullet.rb
72
+ - lib/bullet/active_job.rb
71
73
  - lib/bullet/active_record4.rb
72
74
  - lib/bullet/active_record41.rb
73
75
  - lib/bullet/active_record42.rb
74
76
  - lib/bullet/active_record5.rb
75
77
  - lib/bullet/active_record52.rb
78
+ - lib/bullet/active_record60.rb
79
+ - lib/bullet/bullet_xhr.js
76
80
  - lib/bullet/dependency.rb
77
81
  - lib/bullet/detector.rb
78
82
  - lib/bullet/detector/association.rb
@@ -184,8 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
188
  - !ruby/object:Gem::Version
185
189
  version: 1.3.6
186
190
  requirements: []
187
- rubyforge_project:
188
- rubygems_version: 2.6.14
191
+ rubygems_version: 3.0.3
189
192
  signing_key:
190
193
  specification_version: 4
191
194
  summary: help to kill N+1 queries and unused eager loading.