bullet 6.1.5 → 7.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +16 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +20 -18
- data/lib/bullet/active_record52.rb +11 -17
- data/lib/bullet/active_record60.rb +11 -17
- data/lib/bullet/active_record61.rb +11 -17
- data/lib/bullet/active_record70.rb +275 -0
- data/lib/bullet/bullet_xhr.js +3 -3
- data/lib/bullet/dependency.rb +10 -0
- data/lib/bullet/mongoid7x.rb +26 -9
- data/lib/bullet/rack.rb +2 -1
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +0 -45
- data/lib/generators/bullet/install_generator.rb +0 -1
- data/spec/bullet/notification/base_spec.rb +4 -4
- data/spec/bullet/rack_spec.rb +5 -6
- data/spec/bullet_spec.rb +0 -29
- data/spec/integration/active_record/association_spec.rb +10 -0
- data/spec/integration/counter_cache_spec.rb +1 -1
- data/spec/models/role.rb +7 -0
- data/spec/models/user.rb +1 -0
- data/spec/support/sqlite_seed.rb +18 -0
- data/test.sh +1 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9190b7377e0184967fec123b7926cc2287755989ceea1a4c90a949721d29f411
|
4
|
+
data.tar.gz: 9ae038298d2367a5862eeb6480565f9ac9d104887b97f417e390266e0bfbdb8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b8679cc1801319b7527c472be94d75a5df7a3e03a16f2ef444d3708de584f1f8985ceedd079c35c43cc9ffb39ee5a1664b5c12d38d3d194a686692495873612
|
7
|
+
data.tar.gz: 6309d7925415f5a6339ab4e180a6ef0cd3d27c3308f3a39f6bbfa120c4cb0825f6c2728365942f71e810cb967504b72ad6fa2893334adb4ce6597299cbc4abbd
|
data/.github/workflows/main.yml
CHANGED
@@ -64,3 +64,19 @@ jobs:
|
|
64
64
|
bundler-cache: true
|
65
65
|
- name: Run tests
|
66
66
|
run: bundle exec rake
|
67
|
+
test_rails_7:
|
68
|
+
runs-on: ubuntu-latest
|
69
|
+
strategy:
|
70
|
+
matrix:
|
71
|
+
gemfile: ['Gemfile.rails-7.0']
|
72
|
+
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
73
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
74
|
+
steps:
|
75
|
+
- uses: actions/checkout@v2
|
76
|
+
- name: Set up Ruby
|
77
|
+
uses: ruby/setup-ruby@v1
|
78
|
+
with:
|
79
|
+
ruby-version: 3.1
|
80
|
+
bundler-cache: true
|
81
|
+
- name: Run tests
|
82
|
+
run: bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
+
## 7.0.2 (05/31/2022)
|
4
|
+
|
5
|
+
* Drop growl support
|
6
|
+
* Do not check html tag in Bullet::Rack anymore
|
7
|
+
|
8
|
+
## 7.0.1 (01/15/2022)
|
9
|
+
|
10
|
+
* Get rid of *_whitelist methods
|
11
|
+
* Hack ActiveRecord::Associations::Preloader::Batch in rails 7
|
12
|
+
|
13
|
+
## 7.0.0 (12/18/2021)
|
14
|
+
|
15
|
+
* Support rails 7
|
16
|
+
* Fix Mongoid 7 view iteration
|
17
|
+
* Move CI from Travis to Github Actions
|
18
|
+
|
3
19
|
## 6.1.5 (08/16/2021)
|
4
20
|
|
5
21
|
* Rename whitelist to safelist
|
data/Gemfile.rails-7.0
ADDED
data/README.md
CHANGED
@@ -49,7 +49,7 @@ mongoid.
|
|
49
49
|
|
50
50
|
## Configuration
|
51
51
|
|
52
|
-
Bullet won't
|
52
|
+
Bullet won't enable any notification systems unless you tell it to explicitly. Append to
|
53
53
|
`config/environments/development.rb` initializer with the following code:
|
54
54
|
|
55
55
|
```ruby
|
@@ -59,7 +59,6 @@ config.after_initialize do
|
|
59
59
|
Bullet.alert = true
|
60
60
|
Bullet.bullet_logger = true
|
61
61
|
Bullet.console = true
|
62
|
-
Bullet.growl = true
|
63
62
|
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
64
63
|
:password => 'bullets_password_for_jabber',
|
65
64
|
:receiver => 'your_account@jabber.org',
|
@@ -85,7 +84,6 @@ The code above will enable all of the Bullet notification systems:
|
|
85
84
|
* `Bullet.alert`: pop up a JavaScript alert in the browser
|
86
85
|
* `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
|
87
86
|
* `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
|
88
|
-
* `Bullet.growl`: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
|
89
87
|
* `Bullet.xmpp`: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the Bullet account may start to annoy - in this case set :show_online_status to false; you will still get notifications, but the Bullet account won't announce it's online status anymore.
|
90
88
|
* `Bullet.rails_logger`: add warnings directly to the Rails log
|
91
89
|
* `Bullet.honeybadger`: add notifications to Honeybadger
|
@@ -156,25 +154,26 @@ The Bullet log `log/bullet.log` will look something like this:
|
|
156
154
|
* N+1 Query:
|
157
155
|
|
158
156
|
```
|
159
|
-
2009-08-25 20:40:17[INFO]
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
|
164
|
-
/Users/richard/Downloads/test/app/
|
165
|
-
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
|
157
|
+
2009-08-25 20:40:17[INFO] USE eager loading detected:
|
158
|
+
Post => [:comments]·
|
159
|
+
Add to your query: .includes([:comments])
|
160
|
+
2009-08-25 20:40:17[INFO] Call stack
|
161
|
+
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
|
162
|
+
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
|
166
163
|
```
|
167
164
|
|
168
|
-
The first
|
165
|
+
The first log entry is a notification that N+1 queries have been encountered. The remaining entry is a stack trace so you can find exactly where the queries were invoked in your code, and fix them.
|
169
166
|
|
170
167
|
* Unused eager loading:
|
171
168
|
|
172
169
|
```
|
173
|
-
2009-08-25 20:53:56[INFO]
|
174
|
-
|
170
|
+
2009-08-25 20:53:56[INFO] AVOID eager loading detected
|
171
|
+
Post => [:comments]·
|
172
|
+
Remove from your query: .includes([:comments])
|
173
|
+
2009-08-25 20:53:56[INFO] Call stack
|
175
174
|
```
|
176
175
|
|
177
|
-
These
|
176
|
+
These lines are notifications that unused eager loadings have been encountered.
|
178
177
|
|
179
178
|
* Need counter cache:
|
180
179
|
|
@@ -183,10 +182,14 @@ These two lines are notifications that unused eager loadings have been encounter
|
|
183
182
|
Post => [:comments]
|
184
183
|
```
|
185
184
|
|
186
|
-
##
|
185
|
+
## XMPP/Jabber and Airbrake Support
|
187
186
|
|
188
187
|
see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
|
189
188
|
|
189
|
+
## Growl Support
|
190
|
+
|
191
|
+
Growl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.
|
192
|
+
|
190
193
|
## Important
|
191
194
|
|
192
195
|
If you find Bullet does not work for you, *please disable your browser's cache*.
|
@@ -270,8 +273,7 @@ Bullet outputs some details info, to enable debug mode, set
|
|
270
273
|
## Demo
|
271
274
|
|
272
275
|
Bullet is designed to function as you browse through your application in development. To see it in action,
|
273
|
-
you can
|
274
|
-
follow these steps to create, detect, and fix example query problems.
|
276
|
+
you can follow these steps to create, detect, and fix example query problems.
|
275
277
|
|
276
278
|
1\. Create an example application
|
277
279
|
|
@@ -482,4 +484,4 @@ Meanwhile, there's a line appended to `log/bullet.log`
|
|
482
484
|
Post => [:comments]
|
483
485
|
```
|
484
486
|
|
485
|
-
Copyright (c) 2009 -
|
487
|
+
Copyright (c) 2009 - 2022 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
@@ -75,23 +75,6 @@ module Bullet
|
|
75
75
|
end
|
76
76
|
)
|
77
77
|
|
78
|
-
::ActiveRecord::FinderMethods.prepend(
|
79
|
-
Module.new do
|
80
|
-
# add includes in scope
|
81
|
-
def find_with_associations
|
82
|
-
return super { |r| yield r } if block_given?
|
83
|
-
|
84
|
-
records = super
|
85
|
-
if Bullet.start?
|
86
|
-
associations = (eager_load_values + includes_values).uniq
|
87
|
-
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
|
88
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
89
|
-
end
|
90
|
-
records
|
91
|
-
end
|
92
|
-
end
|
93
|
-
)
|
94
|
-
|
95
78
|
::ActiveRecord::Associations::JoinDependency.prepend(
|
96
79
|
Module.new do
|
97
80
|
def instantiate(result_set, &block)
|
@@ -149,6 +132,17 @@ module Bullet
|
|
149
132
|
end
|
150
133
|
)
|
151
134
|
|
135
|
+
::ActiveRecord::Associations::Association.prepend(
|
136
|
+
Module.new do
|
137
|
+
def inversed_from(record)
|
138
|
+
if Bullet.start?
|
139
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
140
|
+
end
|
141
|
+
super
|
142
|
+
end
|
143
|
+
end
|
144
|
+
)
|
145
|
+
|
152
146
|
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
153
147
|
Module.new do
|
154
148
|
def load_target
|
@@ -102,23 +102,6 @@ module Bullet
|
|
102
102
|
end
|
103
103
|
)
|
104
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
105
|
::ActiveRecord::Associations::JoinDependency.prepend(
|
123
106
|
Module.new do
|
124
107
|
def instantiate(result_set, &block)
|
@@ -176,6 +159,17 @@ module Bullet
|
|
176
159
|
end
|
177
160
|
)
|
178
161
|
|
162
|
+
::ActiveRecord::Associations::Association.prepend(
|
163
|
+
Module.new do
|
164
|
+
def inversed_from(record)
|
165
|
+
if Bullet.start?
|
166
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
167
|
+
end
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end
|
171
|
+
)
|
172
|
+
|
179
173
|
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
180
174
|
Module.new do
|
181
175
|
def load_target
|
@@ -102,23 +102,6 @@ module Bullet
|
|
102
102
|
end
|
103
103
|
)
|
104
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
105
|
::ActiveRecord::Associations::JoinDependency.prepend(
|
123
106
|
Module.new do
|
124
107
|
def instantiate(result_set, strict_loading_value, &block)
|
@@ -176,6 +159,17 @@ module Bullet
|
|
176
159
|
end
|
177
160
|
)
|
178
161
|
|
162
|
+
::ActiveRecord::Associations::Association.prepend(
|
163
|
+
Module.new do
|
164
|
+
def inversed_from(record)
|
165
|
+
if Bullet.start?
|
166
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
167
|
+
end
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end
|
171
|
+
)
|
172
|
+
|
179
173
|
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
180
174
|
Module.new do
|
181
175
|
def load_target
|
@@ -0,0 +1,275 @@
|
|
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::Batch.prepend(
|
64
|
+
Module.new do
|
65
|
+
def call
|
66
|
+
if Bullet.start?
|
67
|
+
@preloaders.each do |preloader|
|
68
|
+
preloader.records.each { |record| Bullet::Detector::Association.add_object_associations(record, preloader.associations) }
|
69
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
)
|
76
|
+
|
77
|
+
::ActiveRecord::Associations::Preloader::Branch.prepend(
|
78
|
+
Module.new do
|
79
|
+
def preloaders_for_reflection(reflection, reflection_records)
|
80
|
+
if Bullet.start?
|
81
|
+
reflection_records.compact!
|
82
|
+
if reflection_records.first.class.name !~ /^HABTM_/
|
83
|
+
reflection_records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
|
84
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
)
|
91
|
+
|
92
|
+
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
|
93
|
+
Module.new do
|
94
|
+
def preloaded_records
|
95
|
+
if Bullet.start? && !defined?(@preloaded_records)
|
96
|
+
source_preloaders.each do |source_preloader|
|
97
|
+
reflection_name = source_preloader.send(:reflection).name
|
98
|
+
source_preloader.send(:owners).each do |owner|
|
99
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
)
|
107
|
+
|
108
|
+
::ActiveRecord::Associations::JoinDependency.prepend(
|
109
|
+
Module.new do
|
110
|
+
def instantiate(result_set, strict_loading_value, &block)
|
111
|
+
@bullet_eager_loadings = {}
|
112
|
+
records = super
|
113
|
+
|
114
|
+
if Bullet.start?
|
115
|
+
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
116
|
+
objects = eager_loadings_hash.keys
|
117
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
|
118
|
+
objects,
|
119
|
+
eager_loadings_hash[objects.first].to_a
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
records
|
124
|
+
end
|
125
|
+
|
126
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
127
|
+
if Bullet.start?
|
128
|
+
unless ar_parent.nil?
|
129
|
+
parent.children.each do |node|
|
130
|
+
key = aliases.column_alias(node, node.primary_key)
|
131
|
+
id = row[key]
|
132
|
+
next unless id.nil?
|
133
|
+
|
134
|
+
associations = node.reflection.name
|
135
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
136
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
137
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
138
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
139
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
# call join associations
|
148
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
149
|
+
result = super
|
150
|
+
|
151
|
+
if Bullet.start?
|
152
|
+
associations = node.reflection.name
|
153
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
154
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
155
|
+
@bullet_eager_loadings[record.class] ||= {}
|
156
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
157
|
+
@bullet_eager_loadings[record.class][record] << associations
|
158
|
+
end
|
159
|
+
|
160
|
+
result
|
161
|
+
end
|
162
|
+
end
|
163
|
+
)
|
164
|
+
|
165
|
+
::ActiveRecord::Associations::Association.prepend(
|
166
|
+
Module.new do
|
167
|
+
def inversed_from(record)
|
168
|
+
if Bullet.start?
|
169
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
170
|
+
end
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
174
|
+
)
|
175
|
+
|
176
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
177
|
+
Module.new do
|
178
|
+
def load_target
|
179
|
+
records = super
|
180
|
+
|
181
|
+
if Bullet.start?
|
182
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
183
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
184
|
+
association = owner.association(reflection.through_reflection.name)
|
185
|
+
Array(association.target).each do |through_record|
|
186
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
187
|
+
end
|
188
|
+
|
189
|
+
if reflection.through_reflection != through_reflection
|
190
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
194
|
+
if records.first.class.name !~ /^HABTM_/
|
195
|
+
if records.size > 1
|
196
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
197
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
198
|
+
elsif records.size == 1
|
199
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
200
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
records
|
205
|
+
end
|
206
|
+
|
207
|
+
def empty?
|
208
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
209
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
210
|
+
end
|
211
|
+
super
|
212
|
+
end
|
213
|
+
|
214
|
+
def include?(object)
|
215
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
|
216
|
+
super
|
217
|
+
end
|
218
|
+
end
|
219
|
+
)
|
220
|
+
|
221
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(
|
222
|
+
Module.new do
|
223
|
+
# call has_one and belongs_to associations
|
224
|
+
def reader
|
225
|
+
result = super
|
226
|
+
|
227
|
+
if Bullet.start?
|
228
|
+
if owner.class.name !~ /^HABTM_/
|
229
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
230
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
231
|
+
association = owner.association(reflection.through_reflection.name)
|
232
|
+
Array(association.target).each do |through_record|
|
233
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
234
|
+
end
|
235
|
+
|
236
|
+
if reflection.through_reflection != through_reflection
|
237
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
241
|
+
|
242
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
243
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
244
|
+
else
|
245
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
result
|
250
|
+
end
|
251
|
+
end
|
252
|
+
)
|
253
|
+
|
254
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
255
|
+
Module.new do
|
256
|
+
def empty?
|
257
|
+
result = super
|
258
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
259
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
260
|
+
end
|
261
|
+
result
|
262
|
+
end
|
263
|
+
|
264
|
+
def count_records
|
265
|
+
result = reflection.has_cached_counter?
|
266
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
267
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
268
|
+
end
|
269
|
+
super
|
270
|
+
end
|
271
|
+
end
|
272
|
+
)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
data/lib/bullet/bullet_xhr.js
CHANGED
@@ -20,7 +20,7 @@
|
|
20
20
|
if (this.onload) {
|
21
21
|
this._storedOnload = this.onload;
|
22
22
|
}
|
23
|
-
this.onload = null
|
23
|
+
this.onload = null;
|
24
24
|
this.addEventListener("load", bulletXHROnload);
|
25
25
|
return Reflect.apply(oldSend, this, arguments);
|
26
26
|
}
|
@@ -31,7 +31,7 @@
|
|
31
31
|
) {
|
32
32
|
var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
|
33
33
|
if (bulletFooterText) {
|
34
|
-
setTimeout(function() {
|
34
|
+
setTimeout(function () {
|
35
35
|
var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
|
36
36
|
var header = oldHtml[0];
|
37
37
|
oldHtml = oldHtml.slice(1, oldHtml.length);
|
@@ -42,7 +42,7 @@
|
|
42
42
|
}
|
43
43
|
var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
|
44
44
|
if (bulletConsoleText && typeof console !== "undefined" && console.log) {
|
45
|
-
setTimeout(function() {
|
45
|
+
setTimeout(function () {
|
46
46
|
JSON.parse(bulletConsoleText).forEach((message) => {
|
47
47
|
if (console.groupCollapsed && console.groupEnd) {
|
48
48
|
console.groupCollapsed("Uniform Notifier");
|
data/lib/bullet/dependency.rb
CHANGED
@@ -29,6 +29,8 @@ module Bullet
|
|
29
29
|
'active_record60'
|
30
30
|
elsif active_record61?
|
31
31
|
'active_record61'
|
32
|
+
elsif active_record70?
|
33
|
+
'active_record70'
|
32
34
|
else
|
33
35
|
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
|
34
36
|
end
|
@@ -64,6 +66,10 @@ module Bullet
|
|
64
66
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
65
67
|
end
|
66
68
|
|
69
|
+
def active_record7?
|
70
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 7
|
71
|
+
end
|
72
|
+
|
67
73
|
def active_record40?
|
68
74
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
69
75
|
end
|
@@ -96,6 +102,10 @@ module Bullet
|
|
96
102
|
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
|
97
103
|
end
|
98
104
|
|
105
|
+
def active_record70?
|
106
|
+
active_record7? && ::ActiveRecord::VERSION::MINOR == 0
|
107
|
+
end
|
108
|
+
|
99
109
|
def mongoid4x?
|
100
110
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
101
111
|
end
|
data/lib/bullet/mongoid7x.rb
CHANGED
@@ -23,16 +23,33 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
return to_enum unless block_given?
|
27
|
+
|
28
|
+
first_document = nil
|
29
|
+
document_count = 0
|
30
|
+
|
31
|
+
origin_each do |document|
|
32
|
+
document_count += 1
|
33
|
+
|
34
|
+
if document_count == 1
|
35
|
+
first_document = document
|
36
|
+
elsif document_count == 2
|
37
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
38
|
+
yield(first_document)
|
39
|
+
first_document = nil
|
40
|
+
yield(document)
|
41
|
+
else
|
42
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
43
|
+
yield(document)
|
44
|
+
end
|
34
45
|
end
|
35
|
-
|
46
|
+
|
47
|
+
if document_count == 1
|
48
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
49
|
+
yield(first_document)
|
50
|
+
end
|
51
|
+
|
52
|
+
self
|
36
53
|
end
|
37
54
|
|
38
55
|
def eager_load(docs)
|
data/lib/bullet/rack.rb
CHANGED
@@ -42,6 +42,7 @@ module Bullet
|
|
42
42
|
def empty?(response)
|
43
43
|
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
44
44
|
# those should not happen if the status is 200
|
45
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
45
46
|
body = response_body(response)
|
46
47
|
body.nil? || body.empty?
|
47
48
|
end
|
@@ -78,7 +79,7 @@ module Bullet
|
|
78
79
|
end
|
79
80
|
|
80
81
|
def html_request?(headers, response)
|
81
|
-
headers['Content-Type']&.include?('text/html')
|
82
|
+
headers['Content-Type']&.include?('text/html')
|
82
83
|
end
|
83
84
|
|
84
85
|
def response_body(response)
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
@@ -120,51 +120,6 @@ module Bullet
|
|
120
120
|
@safelist = nil
|
121
121
|
end
|
122
122
|
|
123
|
-
def add_whitelist(options)
|
124
|
-
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
125
|
-
add_whitelist is deprecated in favor of add_safelist. It will be removed from the next major release.
|
126
|
-
WARN
|
127
|
-
)
|
128
|
-
|
129
|
-
add_safelist(options)
|
130
|
-
end
|
131
|
-
|
132
|
-
def delete_whitelist(options)
|
133
|
-
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
134
|
-
delete_whitelist is deprecated in favor of delete_safelist. It will be removed from the next major release.
|
135
|
-
WARN
|
136
|
-
)
|
137
|
-
|
138
|
-
delete_safelist(options)
|
139
|
-
end
|
140
|
-
|
141
|
-
def get_whitelist_associations(type, class_name)
|
142
|
-
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
143
|
-
get_whitelist_associations is deprecated in favor of get_safelist_associations. It will be removed from the next major release.
|
144
|
-
WARN
|
145
|
-
)
|
146
|
-
|
147
|
-
get_safelist_associations(type, class_name)
|
148
|
-
end
|
149
|
-
|
150
|
-
def reset_whitelist
|
151
|
-
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
152
|
-
reset_whitelist is deprecated in favor of reset_safelist. It will be removed from the next major release.
|
153
|
-
WARN
|
154
|
-
)
|
155
|
-
|
156
|
-
reset_safelist
|
157
|
-
end
|
158
|
-
|
159
|
-
def clear_whitelist
|
160
|
-
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
161
|
-
clear_whitelist is deprecated in favor of clear_safelist. It will be removed from the next major release.
|
162
|
-
WARN
|
163
|
-
)
|
164
|
-
|
165
|
-
clear_safelist
|
166
|
-
end
|
167
|
-
|
168
123
|
def bullet_logger=(active)
|
169
124
|
if active
|
170
125
|
require 'fileutils'
|
@@ -74,8 +74,8 @@ module Bullet
|
|
74
74
|
it 'should send full_notice to notifier' do
|
75
75
|
notifier = double
|
76
76
|
allow(subject).to receive(:notifier).and_return(notifier)
|
77
|
-
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
78
|
-
expect(notifier).to receive(:inline_notify).with(foo: :bar)
|
77
|
+
allow(subject).to receive(:notification_data).and_return({ foo: :bar })
|
78
|
+
expect(notifier).to receive(:inline_notify).with({ foo: :bar })
|
79
79
|
subject.notify_inline
|
80
80
|
end
|
81
81
|
end
|
@@ -84,8 +84,8 @@ module Bullet
|
|
84
84
|
it 'should send full_out_of_channel to notifier' do
|
85
85
|
notifier = double
|
86
86
|
allow(subject).to receive(:notifier).and_return(notifier)
|
87
|
-
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
88
|
-
expect(notifier).to receive(:out_of_channel_notify).with(foo: :bar)
|
87
|
+
allow(subject).to receive(:notification_data).and_return({ foo: :bar })
|
88
|
+
expect(notifier).to receive(:out_of_channel_notify).with({ foo: :bar })
|
89
89
|
subject.notify_out_of_channel
|
90
90
|
end
|
91
91
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -31,12 +31,6 @@ module Bullet
|
|
31
31
|
response = double(body: '<html><head></head><body></body></html>')
|
32
32
|
expect(middleware).not_to be_html_request(headers, response)
|
33
33
|
end
|
34
|
-
|
35
|
-
it "should be false if response body doesn't contain html tag" do
|
36
|
-
headers = { 'Content-Type' => 'text/html' }
|
37
|
-
response = double(body: '<div>Partial</div>')
|
38
|
-
expect(middleware).not_to be_html_request(headers, response)
|
39
|
-
end
|
40
34
|
end
|
41
35
|
|
42
36
|
context 'empty?' do
|
@@ -54,6 +48,11 @@ module Bullet
|
|
54
48
|
response = double(body: '')
|
55
49
|
expect(middleware).to be_empty(response)
|
56
50
|
end
|
51
|
+
|
52
|
+
it 'should be true if no response body' do
|
53
|
+
response = double()
|
54
|
+
expect(middleware).to be_empty(response)
|
55
|
+
end
|
57
56
|
end
|
58
57
|
|
59
58
|
context '#call' do
|
data/spec/bullet_spec.rb
CHANGED
@@ -83,15 +83,6 @@ describe Bullet, focused: true do
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
describe '#add_whitelist' do
|
87
|
-
context "for 'special' class names" do
|
88
|
-
it 'is added to the safelist successfully' do
|
89
|
-
Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
90
|
-
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
86
|
describe '#delete_safelist' do
|
96
87
|
context "for 'special' class names" do
|
97
88
|
it 'is deleted from the safelist successfully' do
|
@@ -112,26 +103,6 @@ describe Bullet, focused: true do
|
|
112
103
|
end
|
113
104
|
end
|
114
105
|
|
115
|
-
describe '#delete_whitelist' do
|
116
|
-
context "for 'special' class names" do
|
117
|
-
it 'is deleted from the safelist successfully' do
|
118
|
-
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
119
|
-
Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
120
|
-
expect(Bullet.safelist[:n_plus_one_query]).to eq({})
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
context 'when exists multiple definitions' do
|
125
|
-
it 'is deleted from the safelist successfully' do
|
126
|
-
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
127
|
-
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
128
|
-
Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
129
|
-
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
130
|
-
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
106
|
describe '#perform_out_of_channel_notifications' do
|
136
107
|
let(:notification) { double }
|
137
108
|
|
@@ -451,6 +451,16 @@ if active_record?
|
|
451
451
|
expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)
|
452
452
|
end
|
453
453
|
end
|
454
|
+
|
455
|
+
context 'user => roles' do
|
456
|
+
it 'should detect preload associations' do
|
457
|
+
User.first.roles.includes(:resource).each { |role| role.resource }
|
458
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
459
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
460
|
+
|
461
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
462
|
+
end
|
463
|
+
end
|
454
464
|
end
|
455
465
|
|
456
466
|
describe Bullet::Detector::Association, 'has_many :through' do
|
@@ -28,7 +28,7 @@ if !mongoid? && active_record?
|
|
28
28
|
expect(Bullet.collected_counter_cache_notifications).to be_empty
|
29
29
|
end
|
30
30
|
|
31
|
-
if
|
31
|
+
if ActiveRecord::VERSION::MAJOR > 4
|
32
32
|
it 'should not need counter cache for has_many through' do
|
33
33
|
Client.all.each { |client| client.firms.size }
|
34
34
|
expect(Bullet.collected_counter_cache_notifications).to be_empty
|
data/spec/models/role.rb
ADDED
data/spec/models/user.rb
CHANGED
data/spec/support/sqlite_seed.rb
CHANGED
@@ -92,9 +92,16 @@ module Support
|
|
92
92
|
page3 = Page.create(name: 'page3', parent_id: folder2.id, author_id: author2.id)
|
93
93
|
page4 = Page.create(name: 'page4', parent_id: folder2.id, author_id: author2.id)
|
94
94
|
|
95
|
+
role1 = Role.create(name: 'Amdin')
|
96
|
+
role2 = Role.create(name: 'User')
|
97
|
+
|
95
98
|
user1 = User.create(name: 'user1', category: category1)
|
96
99
|
user2 = User.create(name: 'user2', category: category1)
|
97
100
|
|
101
|
+
user1.roles << role1
|
102
|
+
user1.roles << role2
|
103
|
+
user2.roles << role2
|
104
|
+
|
98
105
|
submission1 = user1.create_submission(name: 'submission1')
|
99
106
|
submission2 = user2.create_submission(name: 'submission2')
|
100
107
|
|
@@ -246,6 +253,17 @@ module Support
|
|
246
253
|
t.column :submission_id, :integer
|
247
254
|
end
|
248
255
|
|
256
|
+
create_table :roles do |t|
|
257
|
+
t.column :name, :string
|
258
|
+
t.column :resource_id, :integer
|
259
|
+
t.column :resource_type, :string
|
260
|
+
end
|
261
|
+
|
262
|
+
create_table :roles_users do |t|
|
263
|
+
t.column :role_id, :integer
|
264
|
+
t.column :user_id, :integer
|
265
|
+
end
|
266
|
+
|
249
267
|
create_table :submissions do |t|
|
250
268
|
t.column :name, :string
|
251
269
|
t.column :user_id, :integer
|
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-7.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-7.0 bundle exec rspec spec
|
3
4
|
BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle exec rspec spec
|
4
5
|
BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec
|
5
6
|
BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 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:
|
4
|
+
version: 7.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- Gemfile.rails-5.2
|
64
64
|
- Gemfile.rails-6.0
|
65
65
|
- Gemfile.rails-6.1
|
66
|
+
- Gemfile.rails-7.0
|
66
67
|
- Guardfile
|
67
68
|
- Hacking.md
|
68
69
|
- MIT-LICENSE
|
@@ -78,6 +79,7 @@ files:
|
|
78
79
|
- lib/bullet/active_record52.rb
|
79
80
|
- lib/bullet/active_record60.rb
|
80
81
|
- lib/bullet/active_record61.rb
|
82
|
+
- lib/bullet/active_record70.rb
|
81
83
|
- lib/bullet/bullet_xhr.js
|
82
84
|
- lib/bullet/dependency.rb
|
83
85
|
- lib/bullet/detector.rb
|
@@ -158,6 +160,7 @@ files:
|
|
158
160
|
- spec/models/post.rb
|
159
161
|
- spec/models/relationship.rb
|
160
162
|
- spec/models/reply.rb
|
163
|
+
- spec/models/role.rb
|
161
164
|
- spec/models/student.rb
|
162
165
|
- spec/models/submission.rb
|
163
166
|
- spec/models/teacher.rb
|
@@ -192,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
195
|
- !ruby/object:Gem::Version
|
193
196
|
version: 1.3.6
|
194
197
|
requirements: []
|
195
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.3.7
|
196
199
|
signing_key:
|
197
200
|
specification_version: 4
|
198
201
|
summary: help to kill N+1 queries and unused eager loading.
|
@@ -247,6 +250,7 @@ test_files:
|
|
247
250
|
- spec/models/post.rb
|
248
251
|
- spec/models/relationship.rb
|
249
252
|
- spec/models/reply.rb
|
253
|
+
- spec/models/role.rb
|
250
254
|
- spec/models/student.rb
|
251
255
|
- spec/models/submission.rb
|
252
256
|
- spec/models/teacher.rb
|